diff --git a/index.js b/index.js index e692d5e9..dd0289f8 100644 --- a/index.js +++ b/index.js @@ -919,6 +919,10 @@ function buildMultiTypeSerializer (context, location, input) { ` break case 'string': { + // An array has its own `toString`, so it would otherwise be captured by + // the duck-typing check below and serialized as a comma-joined string + // (dropping the JSON structure). Exclude arrays here so a sibling + // `array` type in the same `type` list can match. code += ` ${statement}( typeof ${input} === "string" || @@ -927,6 +931,7 @@ function buildMultiTypeSerializer (context, location, input) { ${input} instanceof RegExp || ( typeof ${input} === "object" && + !Array.isArray(${input}) && typeof ${input}.toString === "function" && ${input}.toString !== Object.prototype.toString ) diff --git a/test/typesArray.test.js b/test/typesArray.test.js index dc09d9ba..57004bae 100644 --- a/test/typesArray.test.js +++ b/test/typesArray.test.js @@ -607,3 +607,36 @@ test('multi-type [object, array] round-trips an array of objects (JSON:API data) } t.assert.deepEqual(JSON.parse(stringify(objectInput)), objectInput) }) + +test('multi-type [string, array] round-trips an array', (t) => { + t.plan(2) + + const schema = { + type: 'object', + properties: { + data: { + type: ['string', 'array'], + 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 stringInput = { data: 'plain' } + t.assert.deepEqual(JSON.parse(stringify(stringInput)), stringInput) +})