@@ -8,15 +8,40 @@ package jsonschema
88
99import (
1010 "fmt"
11+ "log/slog"
12+ "math/big"
1113 "reflect"
1214 "regexp"
15+ "time"
1316
1417 "github.com/modelcontextprotocol/go-sdk/internal/util"
1518)
1619
20+ // ForOptions are options for the [For] function.
21+ type ForOptions struct {
22+ // If IgnoreInvalidTypes is true, fields that can't be represented as a JSON Schema
23+ // are ignored instead of causing an error.
24+ // This allows callers to adjust the resulting schema using custom knowledge.
25+ // For example, an interface type where all the possible implementations are
26+ // known can be described with "oneof".
27+ IgnoreInvalidTypes bool
28+
29+ // TypeSchemas maps types to their schemas.
30+ // If [For] encounters a type equal to a type of a key in this map, the
31+ // corresponding value is used as the resulting schema (after cloning to
32+ // ensure uniqueness).
33+ // Types in this map override the default translations, as described
34+ // in [For]'s documentation.
35+ TypeSchemas map [any ]* Schema
36+ }
37+
1738// For constructs a JSON schema object for the given type argument.
39+ // If non-nil, the provided options configure certain aspects of this contruction,
40+ // described below.
41+
42+ // It translates Go types into compatible JSON schema types, as follows.
43+ // These defaults can be overridden by [ForOptions.TypeSchemas].
1844//
19- // It translates Go types into compatible JSON schema types, as follows:
2045// - Strings have schema type "string".
2146// - Bools have schema type "boolean".
2247// - Signed and unsigned integer types have schema type "integer".
@@ -29,48 +54,51 @@ import (
2954// Their properties are derived from exported struct fields, using the
3055// struct field JSON name. Fields that are marked "omitempty" are
3156// considered optional; all other fields become required properties.
57+ // - Some types in the standard library that implement json.Marshaler
58+ // translate to schemas that match the values to which they marshal.
59+ // For example, [time.Time] translates to the schema for strings.
60+ //
61+ // For will return an error if there is a cycle in the types.
3262//
33- // For returns an error if t contains (possibly recursively) any of the following Go
34- // types, as they are incompatible with the JSON schema spec.
63+ // By default, For returns an error if t contains (possibly recursively) any of the
64+ // following Go types, as they are incompatible with the JSON schema spec.
65+ // If [ForOptions.IgnoreInvalidTypes] is true, then these types are ignored instead.
3566// - maps with key other than 'string'
3667// - function types
3768// - channel types
3869// - complex numbers
3970// - unsafe pointers
4071//
41- // It will return an error if there is a cycle in the types.
42- //
4372// This function recognizes struct field tags named "jsonschema".
4473// A jsonschema tag on a field is used as the description for the corresponding property.
4574// For future compatibility, descriptions must not start with "WORD=", where WORD is a
4675// sequence of non-whitespace characters.
47- func For [T any ]() (* Schema , error ) {
48- // TODO: consider skipping incompatible fields, instead of failing.
49- seen := make (map [reflect.Type ]bool )
50- s , err := forType (reflect .TypeFor [T ](), seen , false )
51- if err != nil {
52- var z T
53- return nil , fmt .Errorf ("For[%T](): %w" , z , err )
76+ func For [T any ](opts * ForOptions ) (* Schema , error ) {
77+ if opts == nil {
78+ opts = & ForOptions {}
5479 }
55- return s , nil
56- }
57-
58- // ForLax behaves like [For], except that it ignores struct fields with invalid types instead of
59- // returning an error. That allows callers to adjust the resulting schema using custom knowledge.
60- // For example, an interface type where all the possible implementations are known
61- // can be described with "oneof".
62- func ForLax [T any ]() (* Schema , error ) {
63- // TODO: consider skipping incompatible fields, instead of failing.
64- seen := make (map [reflect.Type ]bool )
65- s , err := forType (reflect .TypeFor [T ](), seen , true )
80+ schemas := make (map [reflect.Type ]* Schema )
81+ // Add types from the standard library that have MarshalJSON methods.
82+ ss := & Schema {Type : "string" }
83+ schemas [reflect .TypeFor [time.Time ]()] = ss
84+ schemas [reflect .TypeFor [slog.Level ]()] = ss
85+ schemas [reflect .TypeFor [big.Int ]()] = & Schema {Types : []string {"null" , "string" }}
86+ schemas [reflect .TypeFor [big.Rat ]()] = ss
87+ schemas [reflect .TypeFor [big.Float ]()] = ss
88+
89+ // Add types from the options. They override the default ones.
90+ for v , s := range opts .TypeSchemas {
91+ schemas [reflect .TypeOf (v )] = s
92+ }
93+ s , err := forType (reflect .TypeFor [T ](), map [reflect.Type ]bool {}, opts .IgnoreInvalidTypes , schemas )
6694 if err != nil {
6795 var z T
68- return nil , fmt .Errorf ("ForLax [%T](): %w" , z , err )
96+ return nil , fmt .Errorf ("For [%T](): %w" , z , err )
6997 }
7098 return s , nil
7199}
72100
73- func forType (t reflect.Type , seen map [reflect.Type ]bool , lax bool ) (* Schema , error ) {
101+ func forType (t reflect.Type , seen map [reflect.Type ]bool , ignore bool , schemas map [reflect. Type ] * Schema ) (* Schema , error ) {
74102 // Follow pointers: the schema for *T is almost the same as for T, except that
75103 // an explicit JSON "null" is allowed for the pointer.
76104 allowNull := false
@@ -89,6 +117,10 @@ func forType(t reflect.Type, seen map[reflect.Type]bool, lax bool) (*Schema, err
89117 defer delete (seen , t )
90118 }
91119
120+ if s := schemas [t ]; s != nil {
121+ return s .CloneSchemas (), nil
122+ }
123+
92124 var (
93125 s = new (Schema )
94126 err error
@@ -111,30 +143,30 @@ func forType(t reflect.Type, seen map[reflect.Type]bool, lax bool) (*Schema, err
111143
112144 case reflect .Map :
113145 if t .Key ().Kind () != reflect .String {
114- if lax {
146+ if ignore {
115147 return nil , nil // ignore
116148 }
117149 return nil , fmt .Errorf ("unsupported map key type %v" , t .Key ().Kind ())
118150 }
119151 if t .Key ().Kind () != reflect .String {
120152 }
121153 s .Type = "object"
122- s .AdditionalProperties , err = forType (t .Elem (), seen , lax )
154+ s .AdditionalProperties , err = forType (t .Elem (), seen , ignore , schemas )
123155 if err != nil {
124156 return nil , fmt .Errorf ("computing map value schema: %v" , err )
125157 }
126- if lax && s .AdditionalProperties == nil {
158+ if ignore && s .AdditionalProperties == nil {
127159 // Ignore if the element type is invalid.
128160 return nil , nil
129161 }
130162
131163 case reflect .Slice , reflect .Array :
132164 s .Type = "array"
133- s .Items , err = forType (t .Elem (), seen , lax )
165+ s .Items , err = forType (t .Elem (), seen , ignore , schemas )
134166 if err != nil {
135167 return nil , fmt .Errorf ("computing element schema: %v" , err )
136168 }
137- if lax && s .Items == nil {
169+ if ignore && s .Items == nil {
138170 // Ignore if the element type is invalid.
139171 return nil , nil
140172 }
@@ -160,11 +192,11 @@ func forType(t reflect.Type, seen map[reflect.Type]bool, lax bool) (*Schema, err
160192 if s .Properties == nil {
161193 s .Properties = make (map [string ]* Schema )
162194 }
163- fs , err := forType (field .Type , seen , lax )
195+ fs , err := forType (field .Type , seen , ignore , schemas )
164196 if err != nil {
165197 return nil , err
166198 }
167- if lax && fs == nil {
199+ if ignore && fs == nil {
168200 // Skip fields of invalid type.
169201 continue
170202 }
@@ -184,7 +216,7 @@ func forType(t reflect.Type, seen map[reflect.Type]bool, lax bool) (*Schema, err
184216 }
185217
186218 default :
187- if lax {
219+ if ignore {
188220 // Ignore.
189221 return nil , nil
190222 }
@@ -194,6 +226,7 @@ func forType(t reflect.Type, seen map[reflect.Type]bool, lax bool) (*Schema, err
194226 s .Types = []string {"null" , s .Type }
195227 s .Type = ""
196228 }
229+ schemas [t ] = s
197230 return s , nil
198231}
199232
0 commit comments