Skip to content

fix: serialize array input when object precedes array in multi-type schema#851

Open
spokodev wants to merge 1 commit into
fastify:mainfrom
spokodev:fix/multitype-object-before-array
Open

fix: serialize array input when object precedes array in multi-type schema#851
spokodev wants to merge 1 commit into
fastify:mainfrom
spokodev:fix/multitype-object-before-array

Conversation

@spokodev

@spokodev spokodev commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

When a schema lists object before array in its type array, array input serializes to {} and every element is lost.

Steps to reproduce

const build = require('fast-json-stringify')

const stringify = build({
  type: ['object', 'array'],
  properties: { a: { type: 'integer' } },
  items: { type: 'integer' }
}, { ajv: { allowUnionTypes: true } })

console.log(stringify([1, 2, 3]))
// ACTUAL:   {}
// EXPECTED: [1,2,3]

Swapping the order to type: ['array', 'object'] produces the correct [1,2,3], so the bug is order dependent.

Root cause

buildMultiTypeSerializer emits one branch per listed type in list order. The object branch is generated by the default case as if (typeof input === "object" || input === null). Because typeof [] === 'object', that guard captures array input first, which makes the later else if (Array.isArray(input)) branch unreachable. The array is then run through the object serializer, which emits only declared properties and returns {}.

Fix

Add an explicit object case that excludes arrays from the object branch, mirroring the object guard already used in buildArrayTypeCondition:

if ((typeof input === "object" && !Array.isArray(input)) || input === null) { ... }

A sibling array type in the same type list can now match array input. Plain objects, toString objects for ['object', 'string'], and ['object', 'null'] null handling are unchanged, since none of those are arrays.

Authority

Round-trip invariant: JSON.parse(serialize(x)) must equal a schema-valid x. [1,2,3] is valid for the array member yet serialized to {}, breaking the invariant.

Tests

Added to test/typesArray.test.js: array input under type: ['object', 'array'] serializes to [1,2,3] (object input still serializes as an object), and a JSON:API-style data field with type: ['object', 'array'] round-trips an array of objects. Both fail on the current base (array serialized as {}) and pass with the fix.

Full suite: 475/475 passing.

…i-type

A schema with type: ['object', 'array'] serialized array input as {},
dropping every element, because the object branch's typeof x === 'object'
guard matches arrays first and makes the trailing Array.isArray branch
unreachable. Exclude arrays from the object branch, mirroring the object
guard already used in buildArrayTypeCondition, so a sibling array type in
the same type list can match.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant