diff --git a/jsonschema.go b/jsonschema.go index 94a4791..e5ca170 100644 --- a/jsonschema.go +++ b/jsonschema.go @@ -6,6 +6,7 @@ package jsonschema import ( "encoding/json" + "fmt" "reflect" "strings" ) @@ -22,6 +23,7 @@ func (d *Document) Read(variable interface{}) { d.setDefaultSchema() value := reflect.ValueOf(variable) + d.read(value.Type(), tagOptions("")) } @@ -42,6 +44,18 @@ func (d *Document) String() string { return string(json) } +func PkgName(t reflect.Type) string { + var pkgName string + if t.Kind() == reflect.Struct { + pkgName = fmt.Sprintf("%s.%s", t.PkgPath(), t.Name()) + } else if t.Kind() == reflect.Ptr && t.Elem().Kind() == reflect.Struct { + pkgName = fmt.Sprintf("%s.%s", t.Elem().PkgPath(), t.Elem().Name()) + } else if t.Kind() == reflect.Slice { + pkgName = PkgName(t.Elem()) + } + return pkgName +} + type property struct { Type string `json:"type,omitempty"` Format string `json:"format,omitempty"` @@ -62,7 +76,7 @@ func (p *property) read(t reflect.Type, opts tagOptions) { switch kind { case reflect.Slice: - p.readFromSlice(t) + p.readFromSlice(t, opts) case reflect.Map: p.readFromMap(t) case reflect.Struct: @@ -72,13 +86,17 @@ func (p *property) read(t reflect.Type, opts tagOptions) { } } -func (p *property) readFromSlice(t reflect.Type) { +func (p *property) readFromSlice(t reflect.Type, opts tagOptions) { jsType, _, kind := getTypeFromMapping(t.Elem()) + if kind == reflect.Uint8 { p.Type = "string" } else if jsType != "" { p.Items = &property{} p.Items.read(t.Elem(), tagOptions("")) + } else if kind == reflect.Ptr { + p.Items = &property{} + p.Items.read(t.Elem(), opts) } } @@ -98,6 +116,8 @@ func (p *property) readFromStruct(t reflect.Type) { p.Properties = make(map[string]*property, 0) p.AdditionalProperties = false + pkgName := PkgName(t) + count := t.NumField() for i := 0; i < count; i++ { field := t.Field(i) @@ -124,11 +144,17 @@ func (p *property) readFromStruct(t reflect.Type) { } p.Properties[name] = &property{} - p.Properties[name].read(field.Type, opts) - if !opts.Contains("omitempty") { p.Required = append(p.Required, name) } + + // 不支持树状结构的递归 + if PkgName(field.Type) == pkgName { + p.Properties[name].Type = "object" + continue + } + p.Properties[name].read(field.Type, opts) + } } diff --git a/jsonschema_test.go b/jsonschema_test.go index 6c70a37..ed78aec 100644 --- a/jsonschema_test.go +++ b/jsonschema_test.go @@ -94,6 +94,7 @@ type ExampleJSONBasicSlices struct { Slice []string `json:",foo,omitempty"` SliceOfInterface []interface{} `json:",foo"` SliceOfStruct []SliceStruct + SliceOfStructPtr []*SliceStruct } func (self *propertySuite) TestLoadSliceAndContains(c *C) { @@ -124,9 +125,63 @@ func (self *propertySuite) TestLoadSliceAndContains(c *C) { }, }, }, + "SliceOfStructPtr": &property{ + Type: "array", + Items: &property{ + Type: "object", + Required: []string{"Value"}, + Properties: map[string]*property{ + "Value": &property{ + Type: "string", + }, + }, + }, + }, }, - Required: []string{"SliceOfInterface", "SliceOfStruct"}, + Required: []string{"SliceOfInterface", "SliceOfStruct", "SliceOfStructPtr"}, + }, + }) +} + +type Node struct { + Next *Node +} + +type Tree struct { + Children []*Tree +} + +type ExampleJSONTree struct { + Node Node + Tree Tree +} + +func (self *propertySuite) TestLoadTree(c *C) { + j := &Document{} + j.Read(&ExampleJSONTree{}) + + c.Assert(*j, DeepEquals, Document{ + Schema: "http://json-schema.org/schema#", + property: property{ + Type: "object", + Properties: map[string]*property{ + "Node": &property{ + Type: "object", + Properties: map[string]*property{ + "Next": &property{Type: "object"}, + }, + Required: []string{"Next"}, + }, + "Tree": &property{ + Type: "object", + Properties: map[string]*property{ + "Children": &property{Type: "object"}, + }, + Required: []string{"Children"}, + }, + }, + Required: []string{"Node", "Tree"}, }, }) }