diff --git a/index.js b/index.js index b4400d23..2c1e0208 100644 --- a/index.js +++ b/index.js @@ -901,6 +901,17 @@ function buildMultiTypeSerializer (context, location, input) { ` break } + case 'object': { + // An array is `typeof === 'object'`, so it would otherwise be captured + // by this branch and serialized as an object (dropping its items). Exclude + // arrays here so a sibling `array` type in the same `type` list can match. + code += ` + ${statement}((typeof ${input} === "object" && !Array.isArray(${input})) || ${input} === null) { + ${nestedResult} + } + ` + break + } default: { code += ` ${statement}(typeof ${input} === "${type}" || ${input} === null) { diff --git a/test/typesArray.test.js b/test/typesArray.test.js index 341cc032..dc09d9ba 100644 --- a/test/typesArray.test.js +++ b/test/typesArray.test.js @@ -548,3 +548,62 @@ test('throw an error if none of types matches', (t) => { const stringify = build(schema) t.assert.throws(() => stringify({ data: 'string' }), 'The value "string" does not match schema definition.') }) + +test('multi-type object listed before array serializes array input as an array', (t) => { + t.plan(2) + + const schema = { + type: ['object', 'array'], + properties: { + a: { type: 'integer' } + }, + items: { type: 'integer' } + } + + const stringify = build(schema, { ajv: { allowUnionTypes: true } }) + + // Array input must match the `array` member, not be captured by `object`. + t.assert.equal(stringify([1, 2, 3]), '[1,2,3]') + + // Object input for the same schema still serializes as an object. + t.assert.equal(stringify({ a: 4 }), '{"a":4}') +}) + +test('multi-type [object, array] round-trips an array of objects (JSON:API data)', (t) => { + t.plan(2) + + const schema = { + type: 'object', + properties: { + data: { + type: ['object', 'array'], + properties: { + id: { type: 'string' }, + type: { type: 'string' } + }, + items: { + type: 'object', + properties: { + id: { type: 'string' }, + type: { type: 'string' } + } + } + } + } + } + + const stringify = build(schema, { ajv: { allowUnionTypes: true } }) + + const arrayInput = { + data: [ + { id: '1', type: 'article' }, + { id: '2', type: 'article' } + ] + } + t.assert.deepEqual(JSON.parse(stringify(arrayInput)), arrayInput) + + const objectInput = { + data: { id: '1', type: 'article' } + } + t.assert.deepEqual(JSON.parse(stringify(objectInput)), objectInput) +})