From 55033dab8ace58a0269b3dc1383db93515803a53 Mon Sep 17 00:00:00 2001 From: Mark Probst Date: Mon, 20 Jul 2026 17:49:29 -0400 Subject: [PATCH 1/3] fix(cpp): initialize class members from JSON Schema default (#1535) Co-Authored-By: gpt-5.6-sol via pi --- .../src/attributes/DefaultValue.ts | 87 +++++++++++++++++++ .../src/input/JSONSchemaInput.ts | 16 +++- .../language/CPlusPlus/CPlusPlusRenderer.ts | 40 ++++++++- test/inputs/schema/default-value.1.json | 3 + test/inputs/schema/default-value.schema | 13 +++ 5 files changed, 156 insertions(+), 3 deletions(-) create mode 100644 packages/quicktype-core/src/attributes/DefaultValue.ts create mode 100644 test/inputs/schema/default-value.1.json create mode 100644 test/inputs/schema/default-value.schema diff --git a/packages/quicktype-core/src/attributes/DefaultValue.ts b/packages/quicktype-core/src/attributes/DefaultValue.ts new file mode 100644 index 0000000000..784e86e0ae --- /dev/null +++ b/packages/quicktype-core/src/attributes/DefaultValue.ts @@ -0,0 +1,87 @@ +import type { + JSONSchemaAttributes, + JSONSchemaType, + Ref, +} from "../input/JSONSchemaInput.js"; +import type { JSONSchema } from "../input/JSONSchemaStore.js"; +import type { Type } from "../Type/Type.js"; + +import { TypeAttributeKind } from "./TypeAttributes.js"; + +export type DefaultValue = boolean | number | string | null; + +class DefaultValueTypeAttributeKind extends TypeAttributeKind { + public constructor() { + super("defaultValue"); + } + + public get inIdentity(): boolean { + return true; + } + + public combine(values: DefaultValue[]): DefaultValue | undefined { + const first = values[0]; + return values.every((value) => value === first) ? first : undefined; + } + + public makeInferred(_: DefaultValue): undefined { + return undefined; + } + + public addToSchema( + schema: { [name: string]: unknown }, + _t: Type, + defaultValue: DefaultValue, + ): void { + schema.default = defaultValue; + } + + public stringify(defaultValue: DefaultValue): string { + return JSON.stringify(defaultValue); + } +} + +export const defaultValueTypeAttributeKind: TypeAttributeKind = + new DefaultValueTypeAttributeKind(); + +export function defaultValueAttributeProducer( + schema: JSONSchema, + _ref: Ref, + types: Set, +): JSONSchemaAttributes | undefined { + if (typeof schema !== "object") return undefined; + + const defaultValue = schema.default; + if ( + defaultValue !== null && + typeof defaultValue !== "boolean" && + typeof defaultValue !== "number" && + typeof defaultValue !== "string" + ) { + return undefined; + } + + const attributes = + defaultValueTypeAttributeKind.makeAttributes(defaultValue); + if (typeof defaultValue === "number") { + if (!types.has("number") && !types.has("integer")) return undefined; + return { forNumber: attributes }; + } + + if (typeof defaultValue === "string") { + if (!types.has("string")) return undefined; + return { forString: attributes }; + } + + if (typeof defaultValue === "boolean") { + if (!types.has("boolean")) return undefined; + return { forBoolean: attributes }; + } + + if (!types.has("null")) return undefined; + return { forNull: attributes }; +} + +export function defaultValueForType(t: Type): DefaultValue | undefined { + return defaultValueTypeAttributeKind.tryGetInAttributes(t.getAttributes()); +} diff --git a/packages/quicktype-core/src/input/JSONSchemaInput.ts b/packages/quicktype-core/src/input/JSONSchemaInput.ts index d3a0e4feef..0b618ee786 100644 --- a/packages/quicktype-core/src/input/JSONSchemaInput.ts +++ b/packages/quicktype-core/src/input/JSONSchemaInput.ts @@ -28,6 +28,7 @@ import { minMaxLengthAttributeProducer, patternAttributeProducer, } from "../attributes/Constraints.js"; +import { defaultValueAttributeProducer } from "../attributes/DefaultValue.js"; import { descriptionAttributeProducer } from "../attributes/Description.js"; import { enumValuesAttributeProducer } from "../attributes/EnumValues.js"; import { StringTypes } from "../attributes/StringTypes.js"; @@ -649,7 +650,9 @@ const schemaTypes = Object.getOwnPropertyNames( ) as JSONSchemaType[]; export interface JSONSchemaAttributes { + forBoolean?: TypeAttributes; forCases?: TypeAttributes[]; + forNull?: TypeAttributes; forNumber?: TypeAttributes; forObject?: TypeAttributes; forString?: TypeAttributes; @@ -1273,6 +1276,12 @@ async function addTypesInSchema( const numberAttributes = combineProducedAttributes( ({ forNumber }) => forNumber, ); + const booleanAttributes = combineProducedAttributes( + ({ forBoolean }) => forBoolean, + ); + const nullAttributes = combineProducedAttributes( + ({ forNull }) => forNull, + ); for (const [name, kind] of [ ["null", "null"], @@ -1284,7 +1293,11 @@ async function addTypesInSchema( const attributes = isNumberTypeKind(kind) ? numberAttributes - : undefined; + : kind === "bool" + ? booleanAttributes + : kind === "null" + ? nullAttributes + : undefined; unionTypes.push(typeBuilder.getPrimitiveType(kind, attributes)); } @@ -1545,6 +1558,7 @@ export class JSONSchemaInput implements Input { this._attributeProducers = [ descriptionAttributeProducer, accessorNamesAttributeProducer, + defaultValueAttributeProducer, enumValuesAttributeProducer, uriSchemaAttributesProducer, minMaxAttributeProducer, diff --git a/packages/quicktype-core/src/language/CPlusPlus/CPlusPlusRenderer.ts b/packages/quicktype-core/src/language/CPlusPlus/CPlusPlusRenderer.ts index ec99e8029b..f65ff933ec 100644 --- a/packages/quicktype-core/src/language/CPlusPlus/CPlusPlusRenderer.ts +++ b/packages/quicktype-core/src/language/CPlusPlus/CPlusPlusRenderer.ts @@ -13,6 +13,7 @@ import { nullTypeIssueAnnotation, } from "../../Annotation.js"; import { getAccessorName } from "../../attributes/AccessorNames.js"; +import { defaultValueForType } from "../../attributes/DefaultValue.js"; import { enumCaseValues } from "../../attributes/EnumValues.js"; import { ConvenienceRenderer, @@ -925,8 +926,41 @@ export class CPlusPlusRenderer extends ConvenienceRenderer { return this._memberNameStyle(`${jsonName}Constraint`); } - protected emitMember(cppType: Sourcelike, name: Sourcelike): void { - this.emitLine(cppType, " ", name, ";"); + protected emitMember( + cppType: Sourcelike, + name: Sourcelike, + defaultValue?: Sourcelike, + ): void { + this.emitLine( + cppType, + " ", + name, + defaultValue === undefined ? [] : [" = ", defaultValue], + ";", + ); + } + + protected cppDefaultValue(t: Type): Sourcelike | undefined { + const defaultValue = defaultValueForType(t); + if (defaultValue === undefined) return undefined; + + if (defaultValue === null) return "nullptr"; + if (typeof defaultValue === "boolean") { + return defaultValue ? "true" : "false"; + } + + if (typeof defaultValue === "number") return String(defaultValue); + if (t instanceof EnumType && t.cases.has(defaultValue)) { + return [ + this.nameForNamedType(t), + "::", + this.nameForEnumCase(t, defaultValue), + ]; + } + + return this._stringType.createStringLiteral([ + stringEscape(defaultValue), + ]); } protected emitClassMembers( @@ -950,6 +984,7 @@ export class CPlusPlusRenderer extends ConvenienceRenderer { property.isOptional, ), name, + this.cppDefaultValue(property.type), ); if (constraints?.has(jsonName)) { /** FIXME!!! NameStyle will/can collide with other Names */ @@ -980,6 +1015,7 @@ export class CPlusPlusRenderer extends ConvenienceRenderer { property.isOptional, ), name, + this.cppDefaultValue(property.type), ); } else { const [getterName, mutableGetterName, setterName] = defined( diff --git a/test/inputs/schema/default-value.1.json b/test/inputs/schema/default-value.1.json new file mode 100644 index 0000000000..8bf2491e04 --- /dev/null +++ b/test/inputs/schema/default-value.1.json @@ -0,0 +1,3 @@ +{ + "id": 0 +} diff --git a/test/inputs/schema/default-value.schema b/test/inputs/schema/default-value.schema new file mode 100644 index 0000000000..02aecfa888 --- /dev/null +++ b/test/inputs/schema/default-value.schema @@ -0,0 +1,13 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "description": "An object with a scalar default", + "type": "object", + "properties": { + "id": { + "type": "integer", + "default": 0 + } + }, + "required": ["id"], + "additionalProperties": false +} From 6b5313025c614154cd01970d7d4d11d2f743c3f4 Mon Sep 17 00:00:00 2001 From: Mark Probst Date: Mon, 20 Jul 2026 19:37:05 -0400 Subject: [PATCH 2/3] test: add missing fixture cases for default-value.schema (#3021) Co-Authored-By: Claude --- test/inputs/schema/default-value.1.fail.no-defaults.json | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 test/inputs/schema/default-value.1.fail.no-defaults.json diff --git a/test/inputs/schema/default-value.1.fail.no-defaults.json b/test/inputs/schema/default-value.1.fail.no-defaults.json new file mode 100644 index 0000000000..2c63c08510 --- /dev/null +++ b/test/inputs/schema/default-value.1.fail.no-defaults.json @@ -0,0 +1,2 @@ +{ +} From ebf59e87dc105045cd92488c5db748b6f6521b76 Mon Sep 17 00:00:00 2001 From: Mark Probst Date: Tue, 21 Jul 2026 12:07:25 -0400 Subject: [PATCH 3/3] test(schema): skip default-value fixture where required is not enforced The default-value.1.fail.no-defaults.json fail sample is an empty object that must be rejected because the schema marks `id` as required. Languages that do not enforce required properties (and therefore already skip required.schema) cannot make that sample fail, so the schema-cjson fixture reported an unexpected pass. Add default-value.schema to skipSchema for exactly those languages (cjson, swift, haskell, typescript-zod, typescript-effect-schema, elixir), mirroring the existing required.schema skips. The positive and negative cases still run on every language that enforces required (cplusplus, csharp, java, python, rust, kotlin, dart, go, ...). Co-Authored-By: Claude --- test/languages.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/test/languages.ts b/test/languages.ts index 4e2cf42083..642fe44f0d 100644 --- a/test/languages.ts +++ b/test/languages.ts @@ -648,6 +648,9 @@ export const CJSONLanguage: Language = { /* Required properties absent are not checked (for the current implementation, can be added later, should abord parsing and return NULL) */ "intersection.schema", "required.schema", + // The default-value fail sample also relies on required-property + // enforcement, which cJSON does not do. + "default-value.schema", /* Pure Any type not supported (for the current implementation, can be added later, should manage a callback to provide the final application a way to handle it at parsing and creation of cJSON) */ "any.schema", "direct-union.schema", @@ -969,6 +972,8 @@ export const SwiftLanguage: Language = { // This works on macOS, but on Linux one of the failure test cases doesn't fail ...skipsUntypedUnions, "required.schema", + // The default-value fail sample also relies on required-property enforcement. + "default-value.schema", "multi-type-enum.schema", "intersection.schema", ...skipsMapValueValidation, @@ -1897,6 +1902,8 @@ export const HaskellLanguage: Language = { "keyword-unions.schema", "optional-any.schema", "required.schema", + // The default-value fail sample also relies on required-property enforcement. + "default-value.schema", "required-non-properties.schema", ], rendererOptions: {}, @@ -2067,6 +2074,8 @@ export const TypeScriptZodLanguage: Language = { "optional-any.schema", "recursive-union-flattening.schema", "required.schema", + // The default-value fail sample also relies on required-property enforcement. + "default-value.schema", "required-non-properties.schema", ], rendererOptions: {}, @@ -2183,6 +2192,8 @@ export const TypeScriptEffectSchemaLanguage: Language = { "keyword-unions.schema", "optional-any.schema", "required.schema", + // The default-value fail sample also relies on required-property enforcement. + "default-value.schema", "required-non-properties.schema", ], rendererOptions: {}, @@ -2241,6 +2252,8 @@ export const ElixirLanguage: Language = { // Struct keys cannot be enforced at runtime in Elixir and their values will just be set to null. "strict-optional.schema", "required.schema", + // The default-value fail sample also relies on required-property enforcement. + "default-value.schema", "boolean-subschema.schema", "intersection.schema", "optional-any.schema",