From 740aaff8ff5f756397af3ecff976df60bd0e5003 Mon Sep 17 00:00:00 2001 From: zan mlakar Date: Wed, 7 Oct 2026 19:41:23 +0200 Subject: [PATCH] docs: explain toJSON validation with anyOf and oneOf --- README.md | 39 +++++++++++++++++++++++++++++++++++++++ test/toJSON.test.js | 38 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 77 insertions(+) diff --git a/README.md b/README.md index dd042d73..0e8b9fee 100644 --- a/README.md +++ b/README.md @@ -393,6 +393,45 @@ const stringify = fastJson({ }) ``` +At an *anyOf* or *oneOf* node, branch selection validates the input before +serializing the selected branch. For object schemas, this includes checking +required properties on nested objects before calling their `toJSON()` methods. +An object whose required properties exist only in its `toJSON()` result can +therefore fail to match any branch, even if it serializes successfully with the +same object schema outside *anyOf* or *oneOf*. + +Convert those objects before passing them to `stringify`: + +```javascript +const stringify = fastJson({ + oneOf: [{ + type: 'object', + properties: { + balance: { + type: 'object', + properties: { + value: { type: 'number' } + }, + required: ['value'] + } + }, + required: ['balance'] + }, { + type: 'null' + }] +}) + +const balance = { + amount: 100, + toJSON () { + return { value: this.amount } + } +} + +console.log(stringify({ balance: balance.toJSON() })) // '{"balance":{"value":100}}' +stringify({ balance }) // throws: The value of '#' does not match schema definition. +``` + #### If/then/else `fast-json-stringify` supports `if/then/else` jsonschema feature. See [ajv documentation](https://ajv.js.org/keywords.html#ifthenelse). diff --git a/test/toJSON.test.js b/test/toJSON.test.js index 61010b1b..75a11625 100644 --- a/test/toJSON.test.js +++ b/test/toJSON.test.js @@ -58,6 +58,44 @@ test('use toJSON method on nested object types', (t) => { t.assert.equal('[{"productName":"cola"},{"productName":"sprite"}]', stringify(array)) }) +for (const keyword of ['anyOf', 'oneOf']) { + test(`${keyword} validates nested objects before calling toJSON`, (t) => { + t.plan(4) + + const schema = { + type: 'object', + properties: { + balance: { + type: 'object', + properties: { + value: { type: 'number' } + }, + required: ['value'] + } + }, + required: ['balance'] + } + const stringify = build({ + [keyword]: [schema, { type: 'null' }] + }) + let calls = 0 + const balance = { + amount: 100, + toJSON () { + calls++ + return { value: this.amount } + } + } + + t.assert.throws(() => stringify({ balance }), { + message: "The value of '#' does not match schema definition." + }) + t.assert.equal(calls, 0) + t.assert.equal(stringify({ balance: balance.toJSON() }), '{"balance":{"value":100}}') + t.assert.equal(build(schema)({ balance }), '{"balance":{"value":100}}') + }) +} + test('not use toJSON if does not exist', (t) => { t.plan(1)