diff --git a/lib/compile/jtd/serialize.ts b/lib/compile/jtd/serialize.ts index 7ebd26acb..1d228826d 100644 --- a/lib/compile/jtd/serialize.ts +++ b/lib/compile/jtd/serialize.ts @@ -117,7 +117,7 @@ function serializeValues(cxt: SerializeCxt): void { gen.add(N.json, str`}`) } -function serializeKeyValue(cxt: SerializeCxt, key: Name, schema: SchemaObject, first: Name): void { +function serializeKeyValue(cxt: SerializeCxt, key: Name, schema: SchemaObject, first?: Name): void { const {gen, data} = cxt addComma(cxt, first) serializeString({...cxt, data: key}) @@ -156,20 +156,24 @@ function serializeSchemaProperties(cxt: SerializeCxt, discriminator?: string): v const optProps = keys(optionalProperties) const allProps = allProperties(props.concat(optProps)) let first = !discriminator + let firstProp: Name | undefined + for (const key of props) { + if (first) first = false + else gen.add(N.json, str`,`) serializeProperty(key, properties[key], keyValue(key)) } + if (first) firstProp = gen.let("first", true) for (const key of optProps) { const value = keyValue(key) - gen.if(and(_`${value} !== undefined`, isOwnProperty(gen, data, key)), () => + gen.if(and(_`${value} !== undefined`, isOwnProperty(gen, data, key)), () => { + addComma(cxt, firstProp) serializeProperty(key, optionalProperties[key], value) - ) + }) } if (schema.additionalProperties) { gen.forIn("key", data, (key) => - gen.if(isAdditional(key, allProps), () => - serializeKeyValue(cxt, key, {}, gen.let("first", first)) - ) + gen.if(isAdditional(key, allProps), () => serializeKeyValue(cxt, key, {}, firstProp)) ) } @@ -190,8 +194,6 @@ function serializeSchemaProperties(cxt: SerializeCxt, discriminator?: string): v } function serializeProperty(key: string, propSchema: SchemaObject, value: Name): void { - if (first) first = false - else gen.add(N.json, str`,`) gen.add(N.json, str`${JSON.stringify(key)}:`) serializeCode({...cxt, schema: propSchema, data: value}) } @@ -251,10 +253,14 @@ function serializeEmpty({gen, data}: SerializeCxt): void { gen.add(N.json, _`JSON.stringify(${data})`) } -function addComma({gen}: SerializeCxt, first: Name): void { - gen.if( - first, - () => gen.assign(first, false), - () => gen.add(N.json, str`,`) - ) +function addComma({gen}: SerializeCxt, first?: Name): void { + if (first) { + gen.if( + first, + () => gen.assign(first, false), + () => gen.add(N.json, str`,`) + ) + } else { + gen.add(N.json, str`,`) + } } diff --git a/spec/issues/2001_jtd_only_optional_properties.spec.ts b/spec/issues/2001_jtd_only_optional_properties.spec.ts new file mode 100644 index 000000000..55efcc562 --- /dev/null +++ b/spec/issues/2001_jtd_only_optional_properties.spec.ts @@ -0,0 +1,24 @@ +import _Ajv from "../ajv_jtd" +import * as assert from "assert" + +describe("schema with optional/additional properties only", () => { + const ajv = new _Ajv() + + it("should correctly serialize optional properties", () => { + const schema = { + optionalProperties: { + prop0: {type: "uint16"}, + prop1: {type: "uint16"}, + prop2: {type: "uint16"}, + }, + additionalProperties: true, + } + const serialize = ajv.compileSerializer(schema) + const test = (data, json) => assert.strictEqual(serialize(data), json) + test({prop0: 0, prop1: 1, prop2: 2}, '{"prop0":0,"prop1":1,"prop2":2}') + test({prop1: 1, prop2: 2}, '{"prop1":1,"prop2":2}') + test({prop0: 0, prop1: 1, prop2: 2, foo: "bar"}, '{"prop0":0,"prop1":1,"prop2":2,"foo":"bar"}') + test({prop1: 1, prop2: 2, foo: "bar"}, '{"prop1":1,"prop2":2,"foo":"bar"}') + test({foo: "bar"}, '{"foo":"bar"}') + }) +})