From 8be53b921fe2dade1967506a9039e446c8e9a409 Mon Sep 17 00:00:00 2001 From: Dordzhiev Danzan Date: Mon, 29 Sep 2025 18:08:52 +0300 Subject: [PATCH 1/3] feat: Add basic default value support for Dart renderer This commit implements foundational default value functionality for the Dart renderer, supporting primitive types and enums but not complex objects. --- packages/quicktype-core/src/GraphRewriting.ts | 8 +- packages/quicktype-core/src/Type/Type.ts | 17 +- .../quicktype-core/src/Type/TypeBuilder.ts | 5 +- .../quicktype-core/src/Type/TypeGraphUtils.ts | 2 +- packages/quicktype-core/src/UnifyClasses.ts | 2 +- .../quicktype-core/src/input/Inference.ts | 4 +- .../src/input/JSONSchemaInput.ts | 25 +-- .../src/language/Dart/DartRenderer.ts | 147 ++++++++++++++---- .../src/rewrites/ReplaceObjectType.ts | 11 +- .../src/rewrites/ResolveIntersections.ts | 18 +-- test/inputs/schema/default-values.1.json | 8 + test/inputs/schema/default-values.schema | 32 ++++ 12 files changed, 208 insertions(+), 71 deletions(-) create mode 100644 test/inputs/schema/default-values.1.json create mode 100644 test/inputs/schema/default-values.schema diff --git a/packages/quicktype-core/src/GraphRewriting.ts b/packages/quicktype-core/src/GraphRewriting.ts index 71cb44a8a..ae566bf8f 100644 --- a/packages/quicktype-core/src/GraphRewriting.ts +++ b/packages/quicktype-core/src/GraphRewriting.ts @@ -48,7 +48,7 @@ export class TypeReconstituter { private readonly _typeAttributes: TypeAttributes, private readonly _forwardingRef: TypeRef | undefined, private readonly _register: (tref: TypeRef) => void, - ) {} + ) { } private builderForNewType(): TBuilder { assert(!this._wasUsed, "TypeReconstituter used more than once"); @@ -211,8 +211,9 @@ export class TypeReconstituter { public makeClassProperty( tref: TypeRef, isOptional: boolean, + defaultValue: unknown = undefined, ): ClassProperty { - return this._typeBuilder.makeClassProperty(tref, isOptional); + return this._typeBuilder.makeClassProperty(tref, isOptional, defaultValue); } public getObjectType( @@ -333,8 +334,7 @@ export class TypeReconstituter { export abstract class BaseGraphRewriteBuilder extends TypeBuilder - implements TypeLookerUp -{ + implements TypeLookerUp { protected readonly reconstitutedTypes: Map = new Map(); private _lostTypeAttributes = false; diff --git a/packages/quicktype-core/src/Type/Type.ts b/packages/quicktype-core/src/Type/Type.ts index f7bb83721..e5bee77a8 100644 --- a/packages/quicktype-core/src/Type/Type.ts +++ b/packages/quicktype-core/src/Type/Type.ts @@ -93,7 +93,7 @@ export abstract class Type { public constructor( public readonly typeRef: TypeRef, protected readonly graph: TypeGraph, - ) {} + ) { } public get index(): number { return typeRefIndex(this.typeRef); @@ -235,7 +235,7 @@ export abstract class Type { const workList: Type[] = [this]; const processed = new Set(); const ancestors = new Set(); - for (;;) { + for (; ;) { const t = workList.pop(); if (t === undefined) break; @@ -400,7 +400,8 @@ export class GenericClassProperty { public constructor( public readonly typeData: T, public readonly isOptional: boolean, - ) {} + public readonly defaultValue: unknown = undefined, + ) { } public equals(other: GenericClassProperty): boolean { if (!(other instanceof GenericClassProperty)) { @@ -409,12 +410,13 @@ export class GenericClassProperty { return ( areEqual(this.typeData, other.typeData) && - this.isOptional === other.isOptional + this.isOptional === other.isOptional && + areEqual(this.defaultValue, other.defaultValue) ); } public hashCode(): number { - return hashCodeOf(this.typeData) + (this.isOptional ? 17 : 23); + return hashCodeOf(this.typeData) + (this.isOptional ? 17 : 23) + (this.defaultValue !== undefined ? 31 : 0); } } @@ -423,8 +425,9 @@ export class ClassProperty extends GenericClassProperty { typeRef: TypeRef, public readonly graph: TypeGraph, isOptional: boolean, + defaultValue: unknown = undefined, ) { - super(typeRef, isOptional); + super(typeRef, isOptional, defaultValue); } public get typeRef(): TypeRef { @@ -591,6 +594,7 @@ export class ObjectType extends Type { builder.makeClassProperty( defined(maybePropertyTypes.get(n)), cp.isOptional, + cp.defaultValue, ), ); @@ -639,6 +643,7 @@ export class ObjectType extends Type { builder.makeClassProperty( defined(reconstitutedTypes.get(n)), cp.isOptional, + cp.defaultValue, ), ); const additionalProperties = definedMap( diff --git a/packages/quicktype-core/src/Type/TypeBuilder.ts b/packages/quicktype-core/src/Type/TypeBuilder.ts index 9f04812d9..cda3ed3d6 100644 --- a/packages/quicktype-core/src/Type/TypeBuilder.ts +++ b/packages/quicktype-core/src/Type/TypeBuilder.ts @@ -405,8 +405,9 @@ export class TypeBuilder { public makeClassProperty( tref: TypeRef, isOptional: boolean, + defaultValue: unknown = undefined, ): ClassProperty { - return new ClassProperty(tref, this.typeGraph, isOptional); + return new ClassProperty(tref, this.typeGraph, isOptional, defaultValue); } public getUniqueObjectType( @@ -523,7 +524,7 @@ export class TypeBuilder { if (this._allPropertiesOptional) { properties = mapMap(properties, (cp) => - this.makeClassProperty(cp.typeRef, true), + this.makeClassProperty(cp.typeRef, true, cp.defaultValue), ); } diff --git a/packages/quicktype-core/src/Type/TypeGraphUtils.ts b/packages/quicktype-core/src/Type/TypeGraphUtils.ts index cecc04fb9..d07955ca3 100644 --- a/packages/quicktype-core/src/Type/TypeGraphUtils.ts +++ b/packages/quicktype-core/src/Type/TypeGraphUtils.ts @@ -82,7 +82,7 @@ export function optionalToNullable( ref = builder.getUnionType(attributes, members); } - return builder.makeClassProperty(ref, p.isOptional); + return builder.makeClassProperty(ref, p.isOptional, p.defaultValue); }); if (c.isFixed) { return builder.getUniqueClassType( diff --git a/packages/quicktype-core/src/UnifyClasses.ts b/packages/quicktype-core/src/UnifyClasses.ts index 5c1e0094e..0e4798264 100644 --- a/packages/quicktype-core/src/UnifyClasses.ts +++ b/packages/quicktype-core/src/UnifyClasses.ts @@ -80,7 +80,7 @@ function getCliqueProperties( ([name, types, isOptional]) => { return [ name, - builder.makeClassProperty(makePropertyType(types), isOptional), + builder.makeClassProperty(makePropertyType(types), isOptional, undefined), ] as [string, ClassProperty]; }, ); diff --git a/packages/quicktype-core/src/input/Inference.ts b/packages/quicktype-core/src/input/Inference.ts index 4c3154623..b014f5c1f 100644 --- a/packages/quicktype-core/src/input/Inference.ts +++ b/packages/quicktype-core/src/input/Inference.ts @@ -120,7 +120,7 @@ export class TypeInference { private readonly _typeBuilder: TypeBuilder, private readonly _inferMaps: boolean, private readonly _inferEnums: boolean, - ) {} + ) { } private addValuesToAccumulator( valueArray: NestedValueArray, @@ -421,7 +421,7 @@ export class TypeInference { const isOptional = values.length < objects.length; properties.set( key, - this._typeBuilder.makeClassProperty(t, isOptional), + this._typeBuilder.makeClassProperty(t, isOptional, undefined), ); } diff --git a/packages/quicktype-core/src/input/JSONSchemaInput.ts b/packages/quicktype-core/src/input/JSONSchemaInput.ts index 6907c0e48..79ac2d0fe 100644 --- a/packages/quicktype-core/src/input/JSONSchemaInput.ts +++ b/packages/quicktype-core/src/input/JSONSchemaInput.ts @@ -102,8 +102,8 @@ function withRef( typeof refOrLoc === "function" ? refOrLoc() : refOrLoc instanceof Ref - ? refOrLoc - : refOrLoc.canonicalRef; + ? refOrLoc + : refOrLoc.canonicalRef; return Object.assign({ ref }, props ?? {}); } @@ -262,7 +262,7 @@ export class Ref { public get name(): string { const path = Array.from(this.path); - for (;;) { + for (; ;) { const e = path.pop(); if (e === undefined || e.kind === PathElementKind.Root) { let name = @@ -499,7 +499,7 @@ class Canonizer { private readonly _schemaAddressesAdded = new Set(); - public constructor(private readonly _ctx: RunContext) {} + public constructor(private readonly _ctx: RunContext) { } private addIDs(schema: unknown, loc: Location): void { if (schema === null) return; @@ -690,7 +690,7 @@ class Resolver { private readonly _ctx: RunContext, private readonly _store: JSONSchemaStore, private readonly _canonizer: Canonizer, - ) {} + ) { } private async tryResolveVirtualRef( fetchBase: Location, @@ -702,7 +702,7 @@ class Resolver { // we don't know its $id mapping yet, which means we don't know where we // will end up. What we do if we encounter a new schema is add all its // IDs first, and then try to canonize again. - for (;;) { + for (; ;) { const loc = this._canonizer.canonize(fetchBase, virtualRef); const canonical = loc.canonicalRef; assert( @@ -715,9 +715,9 @@ class Resolver { canonical.addressURI === undefined ? undefined : await this._store.get( - address, - this._ctx.debugPrintSchemaResolving, - ); + address, + this._ctx.debugPrintSchemaResolving, + ); if (schema === undefined) { return [undefined, loc]; } @@ -837,7 +837,8 @@ async function addTypesInSchema( makeNamesTypeAttributes(propName, true), ); const isOptional = !required.has(propName); - return typeBuilder.makeClassProperty(t, isOptional); + const defaultValue = propSchema.default; + return typeBuilder.makeClassProperty(t, isOptional, defaultValue); }, ); let additionalPropertiesType: TypeRef | undefined; @@ -872,7 +873,7 @@ async function addTypesInSchema( const additionalProps = mapFromIterable( additionalRequired, - (_name) => typeBuilder.makeClassProperty(t, false), + (_name) => typeBuilder.makeClassProperty(t, false, undefined), ); mapMergeInto(props, additionalProps); } @@ -985,7 +986,7 @@ async function addTypesInSchema( ({ forType, forUnion, forCases }) => { assert( forUnion === undefined && - forCases === undefined, + forCases === undefined, "We can't have attributes for unions and cases if we don't have a union", ); return forType; diff --git a/packages/quicktype-core/src/language/Dart/DartRenderer.ts b/packages/quicktype-core/src/language/Dart/DartRenderer.ts index 2f67f0551..777ecd900 100644 --- a/packages/quicktype-core/src/language/Dart/DartRenderer.ts +++ b/packages/quicktype-core/src/language/Dart/DartRenderer.ts @@ -6,19 +6,19 @@ import { ConvenienceRenderer, type ForbiddenWordsInfo, } from "../../ConvenienceRenderer"; -import {DependencyName, type Name, type Namer} from "../../Naming"; -import type {RenderContext} from "../../Renderer"; -import type {OptionValues} from "../../RendererOptions"; -import {type Sourcelike, maybeAnnotated, modifySource} from "../../Source"; -import {decapitalize, snakeCase} from "../../support/Strings"; -import {defined} from "../../support/Support"; -import type {TargetLanguage} from "../../TargetLanguage"; +import { DependencyName, type Name, type Namer } from "../../Naming"; +import type { RenderContext } from "../../Renderer"; +import type { OptionValues } from "../../RendererOptions"; +import { type Sourcelike, maybeAnnotated, modifySource } from "../../Source"; +import { decapitalize, snakeCase } from "../../support/Strings"; +import { defined } from "../../support/Support"; +import type { TargetLanguage } from "../../TargetLanguage"; import { type ClassProperty, type ClassType, EnumType, type Type, - type UnionType, + UnionType, } from "../../Type"; import { directlyReachableSingleNamedType, @@ -26,8 +26,8 @@ import { nullableFromUnion, } from "../../Type/TypeUtils"; -import {keywords} from "./constants"; -import type {dartOptions} from "./language"; +import { keywords } from "./constants"; +import type { dartOptions } from "./language"; import { enumCaseNamingFunction, propertyNamingFunction, @@ -72,7 +72,7 @@ export class DartRenderer extends ConvenienceRenderer { _c: ClassType, _className: Name, ): ForbiddenWordsInfo { - return {names: [], includeGlobalForbidden: true}; + return { names: [], includeGlobalForbidden: true }; } protected makeNamedTypeNamer(): Namer { @@ -124,7 +124,7 @@ export class DartRenderer extends ConvenienceRenderer { name.order, (lookup) => `${lookup(name)}_${this.fromJson}`, ); - this._topLevelDependents.set(name, {encoder, decoder}); + this._topLevelDependents.set(name, { encoder, decoder }); return [encoder, decoder]; } @@ -191,7 +191,7 @@ export class DartRenderer extends ConvenienceRenderer { this.emitLine("// To parse this JSON data, do"); this.emitLine("//"); this.forEachTopLevel("none", (_t, name) => { - const {decoder} = defined(this._topLevelDependents.get(name)); + const { decoder } = defined(this._topLevelDependents.get(name)); this.emitLine( "// final ", modifySource(decapitalize, name), @@ -261,7 +261,7 @@ export class DartRenderer extends ConvenienceRenderer { } protected emitDescriptionBlock(lines: Sourcelike[]): void { - this.emitCommentLines(lines, {lineStart: "///", beforeComment: ""}); + this.emitCommentLines(lines, { lineStart: "///", beforeComment: "" }); } protected emitBlock(line: Sourcelike, f: () => void): void { @@ -274,12 +274,14 @@ export class DartRenderer extends ConvenienceRenderer { t: Type, withIssues = false, forceNullable = false, + hasDefault = false, ): Sourcelike { const nullable = forceNullable || (this._options.nullSafety && t.isNullable && - !this._options.requiredProperties); + !this._options.requiredProperties && + !hasDefault); const withNullable = (s: Sourcelike): Sourcelike => nullable ? [s, "?"] : s; return matchType( @@ -649,15 +651,90 @@ export class DartRenderer extends ConvenienceRenderer { this.emitLine(className, "();"); } + private _formatDefaultValue(value: unknown, type: Type): Sourcelike { + if (value === null) { + return "null"; + } + + // Handle enum types - convert string value to enum case + if (typeof value === "string") { + let enumType: EnumType | undefined; + + // Check if the type is directly an enum + if (type instanceof EnumType) { + enumType = type; + } + // Check if the type is a union that contains an enum (for optional fields) + else if (type instanceof UnionType) { + // Find the enum type in the union + for (const member of type.members) { + if (member instanceof EnumType) { + enumType = member; + break; + } + } + } + + // If we found an enum type, check if the value matches + if (enumType && enumType.cases.has(value)) { + // Convert the string value to the appropriate enum case name + // Use the same naming function that's used for enum cases + const enumCaseName = enumCaseNamingFunction.nameStyle(value); + return [this.nameForNamedType(enumType), ".", enumCaseName]; + } + } + + if (typeof value === "string") { + return ['"', stringEscape(value), '"']; + } + + if (typeof value === "number") { + return value.toString(); + } + + if (typeof value === "boolean") { + return value.toString(); + } + + if (Array.isArray(value)) { + return "[]"; + } + + if (typeof value === "object") { + return "{}"; + } + + // Fallback for unknown types + return "null"; + } + private _emitConstructor(c: ClassType, className: Name): void { this.emitLine(className, "({"); this.indent(() => { this.forEachClassProperty(c, "none", (name, _, prop) => { + const hasDefault = prop.defaultValue !== undefined; + // Fields with default values should not be optional + const isOptional = !hasDefault && prop.isOptional; const required = this._options.requiredProperties || (this._options.nullSafety && - (!prop.type.isNullable || !prop.isOptional)); - this.emitLine(required ? "required " : "", "this.", name, ","); + (!prop.type.isNullable || !isOptional)); + const defaultValue = hasDefault ? this._formatDefaultValue(prop.defaultValue, prop.type) : undefined; + + const parts: Sourcelike[] = [ + required && !hasDefault ? "required " : "", + this.dartType(prop.type, true, false, hasDefault), + " ", + name, + ]; + + if (hasDefault && defaultValue !== undefined) { + parts.push(" = ", defaultValue); + } + + parts.push(","); + + this.emitLine(...parts); }); }); this.emitLine("});"); @@ -681,9 +758,10 @@ export class DartRenderer extends ConvenienceRenderer { this.emitLine(`@JsonKey(name: "${jsonName}")`); } + const hasDefault = p.defaultValue !== undefined; this.emitLine( this._options.finalProperties ? "final " : "", - this.dartType(p.type, true), + this.dartType(p.type, true, false, hasDefault), " ", name, ";", @@ -875,23 +953,34 @@ export class DartRenderer extends ConvenienceRenderer { this.emitDescription(description); } + const hasDefault = prop.defaultValue !== undefined; + // Fields with default values should not be optional + const isOptional = !hasDefault && prop.isOptional; const required = this._options.requiredProperties || (this._options.nullSafety && - (!prop.type.isNullable || - !prop.isOptional)); + (!prop.type.isNullable || !isOptional)); + const defaultValue = hasDefault ? this._formatDefaultValue(prop.defaultValue, prop.type) : undefined; + + // For Freezed classes, use @Default() annotation instead of constructor defaults + if (hasDefault && defaultValue !== undefined) { + this.emitLine("@Default(", defaultValue, ")"); + } + if (this._options.useJsonAnnotation) { this.classPropertyCounter++; this.emitLine(`@JsonKey(name: "${jsonName}")`); } - this.emitLine( - required ? "required " : "", - this.dartType(prop.type, true), + const parts: Sourcelike[] = [ + required && !hasDefault ? "required " : "", + this.dartType(prop.type, true, false, hasDefault), " ", name, ",", - ); + ]; + + this.emitLine(...parts); }, ); }); @@ -916,15 +1005,15 @@ export class DartRenderer extends ConvenienceRenderer { protected emitEnumDefinition(e: EnumType, enumName: Name): void { this.emitDescription(this.descriptionForType(e)); //if (this._options.useJsonAnnotation) { - this.emitLine("@JsonEnum(alwaysCreate: true)"); + this.emitLine("@JsonEnum(alwaysCreate: true)"); //} this.emitLine("enum ", enumName, " {"); this.indent(() => { this.forEachEnumCase(e, "none", (name, jsonName, pos) => { const comma = pos === "first" || pos === "middle" ? "," : ";"; - // if (this._options.useJsonAnnotation) { - this.emitLine('@JsonValue("', stringEscape(jsonName), '")'); - // } + // if (this._options.useJsonAnnotation) { + this.emitLine('@JsonValue("', stringEscape(jsonName), '")'); + // } this.emitLine(name, comma); }); @@ -975,7 +1064,7 @@ export class DartRenderer extends ConvenienceRenderer { private _emitTopLvlEncoderDecoder(): void { this.forEachTopLevel("leading-and-interposing", (t, name) => { - const {encoder, decoder} = defined( + const { encoder, decoder } = defined( this._topLevelDependents.get(name), ); diff --git a/packages/quicktype-core/src/rewrites/ReplaceObjectType.ts b/packages/quicktype-core/src/rewrites/ReplaceObjectType.ts index d1aadc06d..baa32baf9 100644 --- a/packages/quicktype-core/src/rewrites/ReplaceObjectType.ts +++ b/packages/quicktype-core/src/rewrites/ReplaceObjectType.ts @@ -30,6 +30,7 @@ export function replaceObjectType( builder.makeClassProperty( builder.reconstituteTypeRef(cp.typeRef), cp.isOptional, + cp.defaultValue, ), ); } @@ -103,11 +104,11 @@ export function replaceObjectType( ) as Set; const objectTypesToReplace = leaveFullObjects ? setFilter( - allObjectTypes, - (o) => - o.getProperties().size === 0 || - o.getAdditionalProperties() === undefined, - ) + allObjectTypes, + (o) => + o.getProperties().size === 0 || + o.getAdditionalProperties() === undefined, + ) : allObjectTypes; const groups = Array.from(objectTypesToReplace).map((t) => [t]); return graph.rewrite( diff --git a/packages/quicktype-core/src/rewrites/ResolveIntersections.ts b/packages/quicktype-core/src/rewrites/ResolveIntersections.ts index cf59817ec..bd75251a4 100644 --- a/packages/quicktype-core/src/rewrites/ResolveIntersections.ts +++ b/packages/quicktype-core/src/rewrites/ResolveIntersections.ts @@ -70,11 +70,10 @@ type PropertyMap = Map>>; class IntersectionAccumulator implements - UnionTypeProvider< - ReadonlySet, - [PropertyMap, ReadonlySet | undefined] | undefined - > -{ + UnionTypeProvider< + ReadonlySet, + [PropertyMap, ReadonlySet | undefined] | undefined + > { private _primitiveTypes: Set | undefined; private readonly _primitiveAttributes: TypeAttributeMap = @@ -123,7 +122,7 @@ class IntersectionAccumulator const haveNumber = iterableFind(this._primitiveTypes, isNumberTypeKind) !== - undefined && + undefined && iterableFind(kinds, isNumberTypeKind) !== undefined; this._primitiveTypes = setIntersect(this._primitiveTypes, kinds); if ( @@ -410,15 +409,16 @@ class IntersectionUnionBuilder extends UnionBuilder< this.typeBuilder.makeClassProperty( this.makeIntersection(cp.typeData, emptyTypeAttributes), cp.isOptional, + cp.defaultValue, ), ); const additionalProperties = maybeAdditionalProperties === undefined ? undefined : this.makeIntersection( - maybeAdditionalProperties, - emptyTypeAttributes, - ); + maybeAdditionalProperties, + emptyTypeAttributes, + ); return this.typeBuilder.getUniqueObjectType( typeAttributes, properties, diff --git a/test/inputs/schema/default-values.1.json b/test/inputs/schema/default-values.1.json new file mode 100644 index 000000000..107932668 --- /dev/null +++ b/test/inputs/schema/default-values.1.json @@ -0,0 +1,8 @@ +{ + "name": "Jane Smith", + "age": 30, + "isActive": false, + "tags": ["custom", "tag"], + "metadata": {"key": "value"}, + "optionalField": "present" +} diff --git a/test/inputs/schema/default-values.schema b/test/inputs/schema/default-values.schema new file mode 100644 index 000000000..8063f8d56 --- /dev/null +++ b/test/inputs/schema/default-values.schema @@ -0,0 +1,32 @@ +{ + "type": "object", + "properties": { + "name": { + "type": "string", + "default": "John Doe" + }, + "age": { + "type": "integer", + "default": 25 + }, + "isActive": { + "type": "boolean", + "default": true + }, + "tags": { + "type": "array", + "items": { + "type": "string" + }, + "default": ["default", "tag"] + }, + "metadata": { + "type": "object", + "default": {} + }, + "optionalField": { + "type": "string" + } + }, + "required": ["name", "age"] +} From 891201b7b79a08fc8b01bf10f192d74a295aefa8 Mon Sep 17 00:00:00 2001 From: Dordzhiev Danzan Date: Mon, 29 Sep 2025 19:52:15 +0300 Subject: [PATCH 2/3] feat: Add comprehensive object default value support to Dart renderer This commit extends the existing default value functionality to support complex object types with nested structures, completing the implementation of default values for all Dart data types. --- .../src/language/Dart/DartRenderer.ts | 58 ++++++++++++++++++- 1 file changed, 56 insertions(+), 2 deletions(-) diff --git a/packages/quicktype-core/src/language/Dart/DartRenderer.ts b/packages/quicktype-core/src/language/Dart/DartRenderer.ts index 777ecd900..62c15a8aa 100644 --- a/packages/quicktype-core/src/language/Dart/DartRenderer.ts +++ b/packages/quicktype-core/src/language/Dart/DartRenderer.ts @@ -15,7 +15,7 @@ import { defined } from "../../support/Support"; import type { TargetLanguage } from "../../TargetLanguage"; import { type ClassProperty, - type ClassType, + ClassType, EnumType, type Type, UnionType, @@ -700,7 +700,61 @@ export class DartRenderer extends ConvenienceRenderer { return "[]"; } - if (typeof value === "object") { + if (typeof value === "object" && value !== null) { + // Handle object default values + let objectType: ClassType | undefined; + + // Check if the type is directly a class + if (type instanceof ClassType) { + objectType = type; + } + // Check if the type is a union that contains a class (for optional fields) + else if (type instanceof UnionType) { + // Find the class type in the union + for (const member of type.members) { + if (member instanceof ClassType) { + objectType = member; + break; + } + } + } + + // If we found a class type, generate proper object construction + if (objectType) { + const className = this.nameForNamedType(objectType); + const properties = objectType.getProperties(); + + if (properties.size === 0) { + return [className, "()"]; + } + + // Generate constructor with default values + const constructorArgs: Sourcelike[] = []; + properties.forEach((prop, propName) => { + const propValue = (value as Record)[propName]; + if (propValue !== undefined) { + const formattedValue = this._formatDefaultValue(propValue, prop.type); + constructorArgs.push([propName, ": ", formattedValue]); + } + }); + + if (constructorArgs.length === 0) { + return [className, "()"]; + } + + // Join constructor arguments with commas + const joinedArgs: Sourcelike[] = []; + constructorArgs.forEach((arg, index) => { + if (index > 0) { + joinedArgs.push(", "); + } + joinedArgs.push(arg); + }); + + return [className, "(", ...joinedArgs, ")"]; + } + + // Fallback for unknown object types return "{}"; } From 943fff6dfc83626c634ef15b12d91b9f3215f2ce Mon Sep 17 00:00:00 2001 From: Dordzhiev Danzan Date: Tue, 30 Sep 2025 14:39:58 +0300 Subject: [PATCH 3/3] docs: Document default values support for Dart - Add default values documentation to README and FAQ - Clarify Dart-only support for default values --- FAQ.md | 45 +++++++++++++++++++++++++++++++++++++++++++++ README.md | 26 ++++++++++++++++++++++++++ 2 files changed, 71 insertions(+) diff --git a/FAQ.md b/FAQ.md index 34beed013..91f72c69b 100644 --- a/FAQ.md +++ b/FAQ.md @@ -14,6 +14,7 @@ - [Where can I learn more about JSON Schema?](#where-can-i-learn-more-about-json-schema) - [I'd like to customize the output for my particular application.](#id-like-to-customize-the-output-for-my-particular-application) - [How can I control the property order in JSON Schema?](#how-can-i-control-the-property-order-in-json-schema) +- [Does quicktype support default values from JSON Schema?](#does-quicktype-support-default-values-from-json-schema) - [quicktype is awesome, I'd like to support it!](#quicktype-is-awesome-id-like-to-support-it) - [How is this different from other JSON converters?](#how-is-quicktype-different-from-other-json-converters) @@ -140,3 +141,47 @@ There are many ways you can support quicktype: - Tell all your friends about it! Show it around, tweet about it, write a blog post, present it at a lightning talk. - quicktype is open source - please contribute! We need documentation at least as much as code, so you don't need strong coding skills to make an impact. If you do, we have [lots of open issues](https://github.com/quicktype/quicktype/issues) that need resolving, almost all of our target languages can be improved, and there are many, many programming languages that quicktype doesn't support yet. Talk to us [on Slack](http://slack.quicktype.io) if you're interested - we're always happy to help. + +## Does quicktype support default values from JSON Schema? + +Yes! quicktype supports default values from JSON Schema for Dart language and generates appropriate default values in constructors. + +For example, with this JSON Schema: + +```json +{ + "type": "object", + "properties": { + "name": { + "type": "string", + "default": "John Doe" + }, + "age": { + "type": "integer", + "default": 25 + }, + "isActive": { + "type": "boolean", + "default": true + } + } +} +``` + +quicktype will generate Dart code with default values in constructors. This generates: + +```dart +class Example { + final String name; + final int age; + final bool isActive; + + Example({ + String name = "John Doe", + int age = 25, + bool isActive = true, + }); +} +``` + +Default values work with all supported types including strings, numbers, booleans, arrays, and objects in Dart. This makes your generated Dart models more convenient to use and reduces boilerplate code. Support for other languages may be added in future versions. diff --git a/README.md b/README.md index 9d9fcbbf9..72c74330e 100644 --- a/README.md +++ b/README.md @@ -79,6 +79,32 @@ quicktype https://api.somewhere.com/data -o Data.java The recommended way to use `quicktype` is to generate a JSON schema from sample data, review and edit the schema, commit the schema to your project repo, then generate code from the schema as part of your build process: +#### Default Values Support + +`quicktype` supports default values from JSON Schema for Dart, automatically generating appropriate default values in constructors: + +```json +{ + "type": "object", + "properties": { + "name": { + "type": "string", + "default": "John Doe" + }, + "age": { + "type": "integer", + "default": 25 + }, + "isActive": { + "type": "boolean", + "default": true + } + } +} +``` + +This generates Dart code with default values in constructors, making your generated models more convenient to use. Default values support is currently available for Dart language. + ```bash # First, infer a JSON schema from a sample. quicktype pokedex.json -l schema -o schema.json