@@ -86,6 +86,7 @@ type schemaModel struct {
8686 Package string
8787 DescriptionLines []string
8888 Fields []schemaField
89+ AdditionalProps * additionalPropertiesModel
8990 Imports []string
9091 HasRequired bool
9192 IsEnum bool
@@ -101,6 +102,15 @@ type schemaField struct {
101102 ReadOnly bool
102103}
103104
105+ // additionalPropertiesModel describes synthetic map storage used when object
106+ // schemas define both fixed properties and additionalProperties.
107+ type additionalPropertiesModel struct {
108+ FieldName string
109+ Type string
110+ ValueType string
111+ DescriptionLines []string
112+ }
113+
104114// enumValueModel captures a single enum constant and its wire value.
105115type enumValueModel struct {
106116 Name string
@@ -533,7 +543,10 @@ func buildSchemas(doc *v3.Document, params Params, resolver *typeResolver) []sch
533543 })
534544 continue
535545 }
536- fields , imports , hasRequired := buildSchemaFields (name , ref , resolver )
546+ fields , additionalProps , imports , hasRequired := buildSchemaFields (name , ref , resolver )
547+ if additionalProps != nil {
548+ imports = withAdditionalPropertiesImports (imports )
549+ }
537550 if hasRequired {
538551 imports = uniqueStrings (append (imports , "java.util.Objects" ))
539552 }
@@ -543,6 +556,7 @@ func buildSchemas(doc *v3.Document, params Params, resolver *typeResolver) []sch
543556 Package : params .modelPackage (),
544557 DescriptionLines : splitComment (description ),
545558 Fields : fields ,
559+ AdditionalProps : additionalProps ,
546560 Imports : imports ,
547561 HasRequired : hasRequired ,
548562 }
@@ -553,23 +567,24 @@ func buildSchemas(doc *v3.Document, params Params, resolver *typeResolver) []sch
553567
554568// buildSchemaFields inspects a schema proxy and returns the fields, required
555569// imports, and a flag indicating whether any required properties exist.
556- func buildSchemaFields (name string , ref * base.SchemaProxy , resolver * typeResolver ) ([]schemaField , []string , bool ) {
570+ func buildSchemaFields (name string , ref * base.SchemaProxy , resolver * typeResolver ) ([]schemaField , * additionalPropertiesModel , []string , bool ) {
557571 imports := map [string ]struct {}{}
558572 var fields []schemaField
573+ var additionalProps * additionalPropertiesModel
559574 hasRequired := false
560575
561576 if ref == nil {
562- return []schemaField {{Name : "value" , Type : "Object" }}, nil , false
577+ return []schemaField {{Name : "value" , Type : "Object" }}, nil , nil , false
563578 }
564579
565580 schema := ref .Schema ()
566581 if schema == nil {
567- return []schemaField {{Name : "value" , Type : "Object" }}, nil , false
582+ return []schemaField {{Name : "value" , Type : "Object" }}, nil , nil , false
568583 }
569584 if schemaHasType (schema , "object" ) {
570585 props := collectProperties (schema )
571586 if len (props ) == 0 {
572- return []schemaField {{Name : "value" , Type : "java.util.Map<String, Object>" }}, []string {"java.util.Map" }, false
587+ return []schemaField {{Name : "value" , Type : "java.util.Map<String, Object>" }}, nil , []string {"java.util.Map" }, false
573588 }
574589 required := collectRequired (schema )
575590 fields = make ([]schemaField , 0 , len (props ))
@@ -600,6 +615,23 @@ func buildSchemaFields(name string, ref *base.SchemaProxy, resolver *typeResolve
600615 hasRequired = true
601616 }
602617 }
618+ additionalProps = resolveAdditionalProperties (schema , resolver , name )
619+ if additionalProps != nil {
620+ imports ["java.util.Map" ] = struct {}{}
621+ valueTypeRef := additionalPropertiesTypedSchema (schema )
622+ if valueTypeRef != nil {
623+ valueJavaType := resolver .javaType (valueTypeRef , name , "AdditionalProperty" )
624+ for _ , imp := range valueJavaType .Imports {
625+ imports [imp ] = struct {}{}
626+ }
627+ }
628+ fields = append (fields , schemaField {
629+ Name : additionalProps .FieldName ,
630+ Type : additionalProps .Type ,
631+ DescriptionLines : additionalProps .DescriptionLines ,
632+ Required : false ,
633+ })
634+ }
603635 } else {
604636 javaType := resolver .javaType (ref , name )
605637 for _ , imp := range javaType .Imports {
@@ -611,7 +643,68 @@ func buildSchemaFields(name string, ref *base.SchemaProxy, resolver *typeResolve
611643 }}
612644 }
613645
614- return fields , sortedImports (imports ), hasRequired
646+ return fields , additionalProps , sortedImports (imports ), hasRequired
647+ }
648+
649+ // resolveAdditionalProperties returns metadata for schemas that allow
650+ // additional fields alongside declared properties.
651+ func resolveAdditionalProperties (schema * base.Schema , resolver * typeResolver , context ... string ) * additionalPropertiesModel {
652+ valueType , ok := additionalPropertiesValueType (schema , resolver , context ... )
653+ if ! ok {
654+ return nil
655+ }
656+ return & additionalPropertiesModel {
657+ FieldName : "additionalProperties" ,
658+ Type : fmt .Sprintf ("java.util.Map<String, %s>" , valueType ),
659+ ValueType : valueType ,
660+ DescriptionLines : splitComment ("Additional fields not described by the fixed schema properties." ),
661+ }
662+ }
663+
664+ // additionalPropertiesValueType reports whether additionalProperties is enabled
665+ // and, when enabled, the expected Java value type.
666+ func additionalPropertiesValueType (schema * base.Schema , resolver * typeResolver , context ... string ) (string , bool ) {
667+ if schema == nil {
668+ return "" , false
669+ }
670+ if schema .AdditionalProperties != nil {
671+ if schema .AdditionalProperties .IsA () && schema .AdditionalProperties .A != nil {
672+ valueJavaType := resolver .javaType (schema .AdditionalProperties .A , append (context , "AdditionalProperty" )... )
673+ return valueJavaType .Name , true
674+ }
675+ if schema .AdditionalProperties .IsB () && schema .AdditionalProperties .B {
676+ return "Object" , true
677+ }
678+ }
679+ for _ , item := range schema .AllOf {
680+ if item == nil {
681+ continue
682+ }
683+ if valueType , ok := additionalPropertiesValueType (item .Schema (), resolver , context ... ); ok {
684+ return valueType , true
685+ }
686+ }
687+ return "" , false
688+ }
689+
690+ // additionalPropertiesTypedSchema returns a schema reference when
691+ // additionalProperties defines a concrete value schema.
692+ func additionalPropertiesTypedSchema (schema * base.Schema ) * base.SchemaProxy {
693+ if schema == nil {
694+ return nil
695+ }
696+ if schema .AdditionalProperties != nil && schema .AdditionalProperties .IsA () && schema .AdditionalProperties .A != nil {
697+ return schema .AdditionalProperties .A
698+ }
699+ for _ , item := range schema .AllOf {
700+ if item == nil {
701+ continue
702+ }
703+ if nested := additionalPropertiesTypedSchema (item .Schema ()); nested != nil {
704+ return nested
705+ }
706+ }
707+ return nil
615708}
616709
617710// enumValuesForSchema extracts enum values for string schemas.
@@ -759,3 +852,15 @@ func sortedImports(set map[string]struct{}) []string {
759852 sort .Strings (values )
760853 return values
761854}
855+
856+ // withAdditionalPropertiesImports ensures generated models that capture extra
857+ // JSON fields include the Jackson and collection types used by the template.
858+ func withAdditionalPropertiesImports (imports []string ) []string {
859+ return uniqueStrings (append (imports ,
860+ "com.fasterxml.jackson.annotation.JsonAnyGetter" ,
861+ "com.fasterxml.jackson.annotation.JsonAnySetter" ,
862+ "com.fasterxml.jackson.databind.annotation.JsonDeserialize" ,
863+ "com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder" ,
864+ "java.util.LinkedHashMap" ,
865+ ))
866+ }
0 commit comments