From fd5c93d3422c37c8600b4669118dd5bbfdfa2fd4 Mon Sep 17 00:00:00 2001 From: chuanghiduoc Date: Sun, 23 Aug 2026 12:38:32 +0700 Subject: [PATCH] fix: do not let a string type swallow array input in multi-type schemas A schema with type: ['string', 'array'] serialized array input as a comma-joined string, dropping the JSON structure, because the string branch's duck-typing check (typeof x === 'object' && x.toString !== Object.prototype.toString) also matches arrays and made the sibling array branch unreachable. Exclude arrays from that check, mirroring the object-branch guard added for the same class of bug in #851. --- index.js | 5 +++++ test/typesArray.test.js | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+) 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) +})