Skip to content

Commit a0c185f

Browse files
eiixyCopilot
andauthored
Removed root $ref from GenerateSchemaForType (sashabaranov#1033)
* support $ref and $defs in JSON Schema * update * removed root $ref from JSON Schema * Update json.go * Update json_test.go * Update jsonschema/json.go Co-authored-by: Copilot <[email protected]> * Update jsonschema/json.go Co-authored-by: Copilot <[email protected]> --------- Co-authored-by: Copilot <[email protected]>
1 parent 1bf77f6 commit a0c185f

File tree

2 files changed

+218
-0
lines changed

2 files changed

+218
-0
lines changed

jsonschema/json.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,27 @@ func GenerateSchemaForType(v any) (*Definition, error) {
7777
if err != nil {
7878
return nil, err
7979
}
80+
// If the schema has a root $ref, resolve it by:
81+
// 1. Extracting the key from the $ref.
82+
// 2. Detaching the referenced definition from $defs.
83+
// 3. Checking for self-references in the detached definition.
84+
// - If a self-reference is found, restore the original $defs structure.
85+
// 4. Flattening the referenced definition into the root schema.
86+
// 5. Clearing the $ref field in the root schema.
87+
if def.Ref != "" {
88+
origRef := def.Ref
89+
key := strings.TrimPrefix(origRef, "#/$defs/")
90+
if root, ok := defs[key]; ok {
91+
delete(defs, key)
92+
root.Defs = defs
93+
if containsRef(root, origRef) {
94+
root.Defs = nil
95+
defs[key] = root
96+
}
97+
*def = root
98+
}
99+
def.Ref = ""
100+
}
80101
def.Defs = defs
81102
return def, nil
82103
}
@@ -189,3 +210,26 @@ func reflectSchemaObject(t reflect.Type, defs map[string]Definition) (*Definitio
189210
d.Properties = properties
190211
return &d, nil
191212
}
213+
214+
func containsRef(def Definition, targetRef string) bool {
215+
if def.Ref == targetRef {
216+
return true
217+
}
218+
219+
for _, d := range def.Defs {
220+
if containsRef(d, targetRef) {
221+
return true
222+
}
223+
}
224+
225+
for _, prop := range def.Properties {
226+
if containsRef(prop, targetRef) {
227+
return true
228+
}
229+
}
230+
231+
if def.Items != nil && containsRef(*def.Items, targetRef) {
232+
return true
233+
}
234+
return false
235+
}

jsonschema/json_test.go

Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,18 @@ func TestDefinition_MarshalJSON(t *testing.T) {
182182
}
183183
}
184184

185+
type User struct {
186+
ID int `json:"id,omitempty"`
187+
Name string `json:"name,omitempty"`
188+
Orders []*Order `json:"orders,omitempty"`
189+
}
190+
191+
type Order struct {
192+
ID int `json:"id,omitempty"`
193+
Amount float64 `json:"amount,omitempty"`
194+
Buyer *User `json:"buyer,omitempty"`
195+
}
196+
185197
func TestStructToSchema(t *testing.T) {
186198
type Tweet struct {
187199
Text string `json:"text"`
@@ -194,6 +206,13 @@ func TestStructToSchema(t *testing.T) {
194206
Tweets []Tweet `json:"tweets,omitempty"`
195207
}
196208

209+
type MyStructuredResponse struct {
210+
PascalCase string `json:"pascal_case" required:"true" description:"PascalCase"`
211+
CamelCase string `json:"camel_case" required:"true" description:"CamelCase"`
212+
KebabCase string `json:"kebab_case" required:"true" description:"KebabCase"`
213+
SnakeCase string `json:"snake_case" required:"true" description:"SnakeCase"`
214+
}
215+
197216
tests := []struct {
198217
name string
199218
in any
@@ -444,6 +463,161 @@ func TestStructToSchema(t *testing.T) {
444463
"additionalProperties" : false
445464
}
446465
}
466+
}`,
467+
},
468+
{
469+
name: "Test Person",
470+
in: Person{},
471+
want: `{
472+
"type": "object",
473+
"properties": {
474+
"age": {
475+
"type": "integer"
476+
},
477+
"friends": {
478+
"type": "array",
479+
"items": {
480+
"$ref": "#/$defs/Person"
481+
}
482+
},
483+
"name": {
484+
"type": "string"
485+
},
486+
"tweets": {
487+
"type": "array",
488+
"items": {
489+
"$ref": "#/$defs/Tweet"
490+
}
491+
}
492+
},
493+
"additionalProperties": false,
494+
"$defs": {
495+
"Person": {
496+
"type": "object",
497+
"properties": {
498+
"age": {
499+
"type": "integer"
500+
},
501+
"friends": {
502+
"type": "array",
503+
"items": {
504+
"$ref": "#/$defs/Person"
505+
}
506+
},
507+
"name": {
508+
"type": "string"
509+
},
510+
"tweets": {
511+
"type": "array",
512+
"items": {
513+
"$ref": "#/$defs/Tweet"
514+
}
515+
}
516+
},
517+
"additionalProperties": false
518+
},
519+
"Tweet": {
520+
"type": "object",
521+
"properties": {
522+
"text": {
523+
"type": "string"
524+
}
525+
},
526+
"required": [
527+
"text"
528+
],
529+
"additionalProperties": false
530+
}
531+
}
532+
}`,
533+
},
534+
{
535+
name: "Test MyStructuredResponse",
536+
in: MyStructuredResponse{},
537+
want: `{
538+
"type": "object",
539+
"properties": {
540+
"camel_case": {
541+
"type": "string",
542+
"description": "CamelCase"
543+
},
544+
"kebab_case": {
545+
"type": "string",
546+
"description": "KebabCase"
547+
},
548+
"pascal_case": {
549+
"type": "string",
550+
"description": "PascalCase"
551+
},
552+
"snake_case": {
553+
"type": "string",
554+
"description": "SnakeCase"
555+
}
556+
},
557+
"required": [
558+
"pascal_case",
559+
"camel_case",
560+
"kebab_case",
561+
"snake_case"
562+
],
563+
"additionalProperties": false
564+
}`,
565+
},
566+
{
567+
name: "Test User",
568+
in: User{},
569+
want: `{
570+
"type": "object",
571+
"properties": {
572+
"id": {
573+
"type": "integer"
574+
},
575+
"name": {
576+
"type": "string"
577+
},
578+
"orders": {
579+
"type": "array",
580+
"items": {
581+
"$ref": "#/$defs/Order"
582+
}
583+
}
584+
},
585+
"additionalProperties": false,
586+
"$defs": {
587+
"Order": {
588+
"type": "object",
589+
"properties": {
590+
"amount": {
591+
"type": "number"
592+
},
593+
"buyer": {
594+
"$ref": "#/$defs/User"
595+
},
596+
"id": {
597+
"type": "integer"
598+
}
599+
},
600+
"additionalProperties": false
601+
},
602+
"User": {
603+
"type": "object",
604+
"properties": {
605+
"id": {
606+
"type": "integer"
607+
},
608+
"name": {
609+
"type": "string"
610+
},
611+
"orders": {
612+
"type": "array",
613+
"items": {
614+
"$ref": "#/$defs/Order"
615+
}
616+
}
617+
},
618+
"additionalProperties": false
619+
}
620+
}
447621
}`,
448622
},
449623
}

0 commit comments

Comments
 (0)