internal/util: move FieldJSONInfo into jsonschema/util (#251)

This CL effectively removes any dependencies on mcp from jsonschema.
This will enable us to move the repo.
This CL also deletes unused marshalStructWithMap and
unmarshalStructWithMap which had a dependency on FieldJSONInfo.

Fixes: #250
This commit is contained in:
Sam Thanawalla 2025-08-06 12:07:57 -04:00 • committed by GitHub
commit b392875dc5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 82 additions and 264 deletions

View file

@ -14,8 +14,6 @@ import (
"reflect"
"regexp"
"time"
"github.com/modelcontextprotocol/go-sdk/internal/util"
)
// ForOptions are options for the [For] function.
@ -192,8 +190,8 @@ func forType(t reflect.Type, seen map[reflect.Type]bool, ignore bool, schemas ma
for i := range t.NumField() {
field := t.Field(i)
info := util.FieldJSONInfo(field)
if info.Omit {
info := fieldJSONInfo(field)
if info.omit {
continue
}
if s.Properties == nil {
@ -216,9 +214,9 @@ func forType(t reflect.Type, seen map[reflect.Type]bool, ignore bool, schemas ma
}
fs.Description = tag
}
s.Properties[info.Name] = fs
if !info.Settings["omitempty"] && !info.Settings["omitzero"] {
s.Required = append(s.Required, info.Name)
s.Properties[info.name] = fs
if !info.settings["omitempty"] && !info.settings["omitzero"] {
s.Required = append(s.Required, info.name)
}
}

View file

@ -15,8 +15,6 @@ import (
"math"
"reflect"
"slices"
"github.com/modelcontextprotocol/go-sdk/internal/util"
)
// A Schema is a JSON schema object.
@ -424,9 +422,9 @@ var (
func init() {
for _, sf := range reflect.VisibleFields(reflect.TypeFor[Schema]()) {
info := util.FieldJSONInfo(sf)
if !info.Omit {
schemaFieldInfos = append(schemaFieldInfos, structFieldInfo{sf, info.Name})
info := fieldJSONInfo(sf)
if !info.omit {
schemaFieldInfos = append(schemaFieldInfos, structFieldInfo{sf, info.name})
}
}
slices.SortFunc(schemaFieldInfos, func(i1, i2 structFieldInfo) int {

View file

@ -15,9 +15,8 @@ import (
"math/big"
"reflect"
"slices"
"strings"
"sync"
"github.com/modelcontextprotocol/go-sdk/internal/util"
)
// Equal reports whether two Go values representing JSON values are equal according
@ -410,11 +409,48 @@ func jsonNames(t reflect.Type) map[string]bool {
}
continue
}
info := util.FieldJSONInfo(field)
if !info.Omit {
m[info.Name] = true
info := fieldJSONInfo(field)
if !info.omit {
m[info.name] = true
}
}
jsonNamesMap.Store(t, m)
return m
}
type jsonInfo struct {
omit bool // unexported or first tag element is "-"
name string // Go field name or first tag element. Empty if omit is true.
settings map[string]bool // "omitempty", "omitzero", etc.
}
// fieldJSONInfo reports information about how encoding/json
// handles the given struct field.
// If the field is unexported, jsonInfo.omit is true and no other jsonInfo field
// is populated.
// If the field is exported and has no tag, then name is the field's name and all
// other fields are false.
// Otherwise, the information is obtained from the tag.
func fieldJSONInfo(f reflect.StructField) jsonInfo {
if !f.IsExported() {
return jsonInfo{omit: true}
}
info := jsonInfo{name: f.Name}
if tag, ok := f.Tag.Lookup("json"); ok {
name, rest, found := strings.Cut(tag, ",")
// "-" means omit, but "-," means the name is "-"
if name == "-" && !found {
return jsonInfo{omit: true}
}
if name != "" {
info.name = name
}
if len(rest) > 0 {
info.settings = map[string]bool{}
for _, s := range strings.Split(rest, ",") {
info.settings[s] = true
}
}
}
return info
}

View file

@ -184,3 +184,31 @@ func TestMarshalStructWithMap(t *testing.T) {
}
})
}
func TestJSONInfo(t *testing.T) {
type S struct {
A int
B int `json:","`
C int `json:"-"`
D int `json:"-,"`
E int `json:"echo"`
F int `json:"foxtrot,omitempty"`
g int `json:"golf"`
}
want := []jsonInfo{
{name: "A"},
{name: "B"},
{omit: true},
{name: "-"},
{name: "echo"},
{name: "foxtrot", settings: map[string]bool{"omitempty": true}},
{omit: true},
}
tt := reflect.TypeFor[S]()
for i := range tt.NumField() {
got := fieldJSONInfo(tt.Field(i))
if !reflect.DeepEqual(got, want[i]) {
t.Errorf("got %+v, want %+v", got, want[i])
}
}
}

View file

@ -697,8 +697,8 @@ func properties(v reflect.Value) iter.Seq2[string, reflect.Value] {
for name, sf := range structPropertiesOf(v.Type()) {
val := v.FieldByIndex(sf.Index)
if val.IsZero() {
info := util.FieldJSONInfo(sf)
if info.Settings["omitempty"] || info.Settings["omitzero"] {
info := fieldJSONInfo(sf)
if info.settings["omitempty"] || info.settings["omitzero"] {
continue
}
}
@ -750,9 +750,9 @@ func structPropertiesOf(t reflect.Type) propertyMap {
}
props := map[string]reflect.StructField{}
for _, sf := range reflect.VisibleFields(t) {
info := util.FieldJSONInfo(sf)
if !info.Omit {
props[info.Name] = sf
info := fieldJSONInfo(sf)
if !info.omit {
props[info.name] = sf
}
}
structProperties.Store(t, props)