-
-
Notifications
You must be signed in to change notification settings - Fork 1.5k
/
Copy pathjson.go
154 lines (143 loc) · 4.49 KB
/
json.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
// Package jsonschema provides very simple functionality for representing a JSON schema as a
// (nested) struct. This struct can be used with the chat completion "function call" feature.
// For more complicated schemas, it is recommended to use a dedicated JSON schema library
// and/or pass in the schema in []byte format.
package jsonschema
import (
"encoding/json"
"fmt"
"reflect"
"strconv"
"strings"
)
type DataType string
const (
Object DataType = "object"
Number DataType = "number"
Integer DataType = "integer"
String DataType = "string"
Array DataType = "array"
Null DataType = "null"
Boolean DataType = "boolean"
)
// Definition is a struct for describing a JSON Schema.
// It is fairly limited, and you may have better luck using a third-party library.
type Definition struct {
// Type specifies the data type of the schema.
Type DataType `json:"type,omitempty"`
// Description is the description of the schema.
Description string `json:"description,omitempty"`
// Enum is used to restrict a value to a fixed set of values. It must be an array with at least
// one element, where each element is unique. You will probably only use this with strings.
Enum []string `json:"enum,omitempty"`
// Properties describes the properties of an object, if the schema type is Object.
Properties map[string]Definition `json:"properties,omitempty"`
// Required specifies which properties are required, if the schema type is Object.
Required []string `json:"required,omitempty"`
// Items specifies which data type an array contains, if the schema type is Array.
Items *Definition `json:"items,omitempty"`
// AdditionalProperties is used to control the handling of properties in an object
// that are not explicitly defined in the properties section of the schema. example:
// additionalProperties: true
// additionalProperties: false
// additionalProperties: jsonschema.Definition{Type: jsonschema.String}
AdditionalProperties any `json:"additionalProperties,omitempty"`
}
func (d *Definition) MarshalJSON() ([]byte, error) {
if d.Properties == nil {
d.Properties = make(map[string]Definition)
}
type Alias Definition
return json.Marshal(struct {
Alias
}{
Alias: (Alias)(*d),
})
}
func (d *Definition) Unmarshal(content string, v any) error {
return VerifySchemaAndUnmarshal(*d, []byte(content), v)
}
func GenerateSchemaForType(v any) (*Definition, error) {
return reflectSchema(reflect.TypeOf(v))
}
func reflectSchema(t reflect.Type) (*Definition, error) {
var d Definition
switch t.Kind() {
case reflect.String:
d.Type = String
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
d.Type = Integer
case reflect.Float32, reflect.Float64:
d.Type = Number
case reflect.Bool:
d.Type = Boolean
case reflect.Slice, reflect.Array:
d.Type = Array
items, err := reflectSchema(t.Elem())
if err != nil {
return nil, err
}
d.Items = items
case reflect.Struct:
d.Type = Object
d.AdditionalProperties = false
object, err := reflectSchemaObject(t)
if err != nil {
return nil, err
}
d = *object
case reflect.Ptr:
definition, err := reflectSchema(t.Elem())
if err != nil {
return nil, err
}
d = *definition
case reflect.Invalid, reflect.Uintptr, reflect.Complex64, reflect.Complex128,
reflect.Chan, reflect.Func, reflect.Interface, reflect.Map,
reflect.UnsafePointer:
return nil, fmt.Errorf("unsupported type: %s", t.Kind().String())
default:
}
return &d, nil
}
func reflectSchemaObject(t reflect.Type) (*Definition, error) {
var d = Definition{
Type: Object,
AdditionalProperties: false,
}
properties := make(map[string]Definition)
var requiredFields []string
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
if !field.IsExported() {
continue
}
jsonTag := field.Tag.Get("json")
var required = true
if jsonTag == "" {
jsonTag = field.Name
} else if strings.HasSuffix(jsonTag, ",omitempty") {
jsonTag = strings.TrimSuffix(jsonTag, ",omitempty")
required = false
}
item, err := reflectSchema(field.Type)
if err != nil {
return nil, err
}
description := field.Tag.Get("description")
if description != "" {
item.Description = description
}
properties[jsonTag] = *item
if s := field.Tag.Get("required"); s != "" {
required, _ = strconv.ParseBool(s)
}
if required {
requiredFields = append(requiredFields, jsonTag)
}
}
d.Required = requiredFields
d.Properties = properties
return &d, nil
}