Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions go/internal/base/json.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ func InferJSONSchema(x any) (s *jsonschema.Schema) {
if baseType.Kind() == reflect.Struct {
if seen[baseType] {
return &jsonschema.Schema{
Type: "object",
AdditionalProperties: jsonschema.TrueSchema,
}
Comment on lines 105 to 108
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

This change correctly adds Type: "object" for seen structs, making the schema more specific. However, this introduces an issue for struct types that implement json.Marshaler to produce a non-object JSON representation (e.g., a string or number).

For such types, this logic will incorrectly claim the type is object. For example:

type MyStringer struct { Value string }
func (s MyStringer) MarshalJSON() ([]byte, error) { return json.Marshal(s.Value) }

type Container struct {
    A MyStringer
    B MyStringer
}

When inferring the schema for Container, the schema for the second occurrence of MyStringer (field B) will be incorrectly marked as type: "object".

To fix this, we should check if the struct implements json.Marshaler and, if so, fall back to a more generic schema that doesn't specify the type, which is consistent with how jsonschema handles marshalers.

          var marshalerType = reflect.TypeOf((*json.Marshaler)(nil)).Elem()
          if baseType.Implements(marshalerType) || reflect.PtrTo(baseType).Implements(marshalerType) {
            return &jsonschema.Schema{
              AdditionalProperties: jsonschema.TrueSchema,
            }
          }
          return &jsonschema.Schema{
            Type:                 "object",
            AdditionalProperties: jsonschema.TrueSchema,
          }

}
Expand Down
32 changes: 32 additions & 0 deletions go/internal/base/json_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,39 @@ func TestSchemaAsMapRecursive(t *testing.T) {
t.Fatal("expected children to have items")
}
// The recursive Node reference should have become an "any" schema
// including "type" property to prevent recursion errors for schemas for the same type
if items["type"] != "object" {
t.Errorf("expected children.items.type to be 'object', got %v", items["type"])
}
if items["additionalProperties"] != true {
t.Errorf("expected children.items to be 'any' schema (additionalProperties: true), got %v", items)
}
}

func TestInferJSONSchema_SharedType(t *testing.T) {
type Shared struct {
Amount float64 `json:"amount"`
}
type Prizes struct {
First Shared `json:"first"`
Second Shared `json:"second"`
}

schema := SchemaAsMap(InferJSONSchema(Prizes{}))
properties, ok := schema["properties"].(map[string]any)
if !ok {
t.Fatal("expected properties in schema")
}

second, ok := properties["second"].(map[string]any)
if !ok {
t.Fatal("expected 'second' property in schema")
}

if second["type"] != "object" {
t.Errorf("expected type: object for shared type occurrence, got %v", second["type"])
}
if second["additionalProperties"] != true {
t.Errorf("expected additionalProperties: true for shared type occurrence, got %v", second["additionalProperties"])
}
}
Loading