Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat support map #40

Merged
merged 11 commits into from
Apr 2, 2024
23 changes: 23 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,29 @@ Golang 1.16+
go get -u github.com/zc2638/[email protected]
```

## Use note:
The time.time type uses jsonTag to parse it into a string type
```go
type XXX struct{
Now time.Time `json:",string"`
}
```

The compatibility is resolved as follows:
```go
type xxx struct {
MapSlicePtr map[string][]*string
MapSlice map[string][]string
MapSliceStructPtr map[string][]*Person
MapSliceStruct map[string][]Person
SliceStructPtr *[]*Person
SliceStruct *[]Person
SliceStringPtr *[]*string
SliceString *[]string
}

```

**Tip:** As of `v1.2.0`, lower versions are no longer compatible. In order to be compatible with most web frameworks,
the overall architecture has been greatly changed.

Expand Down
23 changes: 23 additions & 0 deletions README_zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,29 @@ Golang 1.16+
go get -u github.com/zc2638/[email protected]
```

## 使用注意:
time.time类型使用jsonTag来将其解析为string类型
```go
type XXX struct{
Now time.Time `json:",string"`
}
```

兼容的解析如下:
```go
type xxx struct {
MapSlicePtr map[string][]*string
MapSlice map[string][]string
MapSliceStructPtr map[string][]*Person
MapSliceStruct map[string][]Person
SliceStructPtr *[]*Person
SliceStruct *[]Person
SliceStringPtr *[]*string
SliceString *[]string
}

```

**Tip:** 从 `v1.2.0` 开始,低版本不再兼容。为了兼容大部分的web框架,整体架构做了很大的改动。

## 默认 Swagger UI 服务器
Expand Down
12 changes: 12 additions & 0 deletions api.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,18 @@ type Object struct {

// Property represents the property entity from the swagger definition
type Property struct {
GoType reflect.Type `json:"-"`
Type string `json:"type,omitempty"`
Description string `json:"description,omitempty"`
Enum []string `json:"enum,omitempty"`
Format string `json:"format,omitempty"`
Ref string `json:"$ref,omitempty"`
Example string `json:"example,omitempty"`
Items *Items `json:"items,omitempty"`
AddPropertie *AdditionalProperties `json:"additionalProperties,omitempty"`
}

type AdditionalProperties struct {
GoType reflect.Type `json:"-"`
Type string `json:"type,omitempty"`
Description string `json:"description,omitempty"`
Expand Down
170 changes: 165 additions & 5 deletions reflect.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,9 @@
package swag

import (
"github.com/zc2638/swag/types"
"reflect"
"strings"

"github.com/zc2638/swag/types"
)

func inspect(t reflect.Type, jsonTag string) Property {
Expand Down Expand Up @@ -67,16 +66,41 @@ func inspect(t reflect.Type, jsonTag string) Property {
name := makeName(p.GoType)
p.Ref = makeRef(name)

case reflect.Map:
p.Type = "object"
p.AddPropertie = buildMapType(p.GoType)
// get the go reflect type of the map value
p.GoType = p.AddPropertie.GoType

case reflect.Slice:
p.Type = types.Array.String()
p.Items = &Items{}

p.GoType = t.Elem() // dereference the slice
// dereference the slice,get the element object of the slice
// Example:
// t ==> *[]*Struct|Struct|*string|string
// p.GoType.Kind() ==> []*Struct|Struct|*string|string

//p.GoType ==> *Struct|Struct|*string|string
p.GoType = p.GoType.Elem()

switch p.GoType.Kind() {
case reflect.Ptr:
//p.GoType ==> Struct|string
p.GoType = p.GoType.Elem()
name := makeName(p.GoType)
p.Items.Ref = makeRef(name)

// determine the type of element object in the slice
isPrimitive := isPrimitiveType(p.GoType.Name(), p.GoType)

if isPrimitive {
// golang built-in primitive type
kind_type := jsonSchemaType(p.GoType.String(), p.GoType)
p.Items.Type = kind_type
} else {
// Struct types
name := makeName(p.GoType)
p.Items.Ref = makeRef(name)
}

case reflect.Struct:
name := makeName(p.GoType)
Expand Down Expand Up @@ -106,6 +130,142 @@ func inspect(t reflect.Type, jsonTag string) Property {
return p
}

// buildMapType build map type swag info
func buildMapType(mapType reflect.Type) *AdditionalProperties {

prop := AdditionalProperties{}

// get the element object of the map
// Example:
// mapType ==> map[string][]*Struct|Struct|*string|string
// or mapTYpe ==> map[string]*Struct|Struct|*string|string
// mapType.Elem().Kind() ==> []*Struct|Struct|*string|string
// or mapType.Elem().Kind() ==> *Struct|Struct|*string|string
if mapType.Elem().Kind().String() != "interface" {
isSlice := isSliceOrArryType(mapType.Elem().Kind())
if isSlice || isByteArrayType(mapType.Elem()) {
// if map value is slice
// Example:
// mapType.Elem()==> []*Struct|Struct|*string|string
mapType = mapType.Elem()
}

// if map value is struct or built-in primitive type
// Example:
// mapType.Elem()==> *Struct|Struct|*string|string
isPrimitive := isPrimitiveType(mapType.Elem().Name(), mapType.Elem())

if isByteArrayType(mapType.Elem()) {
prop.Type = "string"
} else {
zc2638 marked this conversation as resolved.
Show resolved Hide resolved
if isSlice {
prop.Type = types.Array.String()
prop.Items = &Items{}
if isPrimitive {
prop.Items.Type = jsonSchemaType(mapType.Elem().String(), mapType.Elem())
prop.GoType = getGoType(mapType.Elem())
} else {
prop.Items.Ref = makeMapRef(mapType.Elem().String())
prop.GoType = getGoType(mapType.Elem())
}
} else if isPrimitive {
prop.Type = jsonSchemaType(mapType.Elem().String(), mapType.Elem())
prop.GoType = getGoType(mapType.Elem())
} else {
prop.Ref = makeMapRef(mapType.Elem().String())
prop.GoType = getGoType(mapType.Elem())
}
}
}

return &prop
}

func getGoType(t reflect.Type) reflect.Type {

var goType reflect.Type

if t.Kind() == reflect.Ptr {
goType = t.Elem()
} else {
goType = t
}

return goType
}

func makeMapRef(typeName string) string {
type_name := strings.Trim(typeName, "*")
return makeRef(type_name)
}

func isSliceOrArryType(t reflect.Kind) bool {
return t == reflect.Slice || t == reflect.Array
}

func isByteArrayType(t reflect.Type) bool {
return (t.Kind() == reflect.Slice || t.Kind() == reflect.Array) &&
t.Elem().Kind() == reflect.Uint8
}
zc2638 marked this conversation as resolved.
Show resolved Hide resolved

// isPrimitiveType Whether it is a built-in primitive type
func isPrimitiveType(modelName string, modelType reflect.Type) bool {
var modelKind reflect.Kind

if modelType.Kind() == reflect.Ptr {
modelKind = modelType.Elem().Kind()
} else {
modelKind = modelType.Kind()
}

switch modelKind {
case reflect.Bool:
return true
case reflect.Float32, reflect.Float64,
reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
return true
case reflect.String:
return true
}

if len(modelName) == 0 {
return false
}

_, ok := types.SchemaMap[modelName]
return ok
}

func jsonSchemaType(modelName string, modelType reflect.Type) string {
var modelKind reflect.Kind

if modelType.Kind() == reflect.Ptr {
modelKind = modelType.Elem().Kind()
} else {
modelKind = modelType.Kind()
}

if mapped, ok := types.SchemaMap[modelName]; ok {
return mapped
}

// check if original type is primitive
switch modelKind {
case reflect.Bool:
return types.Boolean.String()
case reflect.Float32, reflect.Float64:
return types.Number.String()
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
return types.Integer.String()
case reflect.String:
return types.String.String()
}

return modelName // use as is (custom or struct)
}

func buildProperty(t reflect.Type) (map[string]Property, []string) {
properties := make(map[string]Property)
required := make([]string, 0)
Expand Down
20 changes: 19 additions & 1 deletion reflect_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,24 @@ type Pet struct {
Bool bool
Enum string `json:"enum" enum:"a,b,c" example:"b"`
Anonymous
MapSlicePtr map[string][]*string
MapSlice map[string][]string
MapSliceStructPtr map[string][]*Person
MapSliceStruct map[string][]Person
SliceStructPtr *[]*Person
SliceStruct *[]Person
SliceStringPtr *[]*string
SliceString *[]string
MapNestOptions *MapObj `json:"map_nest_options,omitempty"`
}

type MapObj struct {
RuleOptions map[string]*MapOption `json:"rule_options"`
}

type MapOption struct {
Name string `json:"name"`
SubOptions map[string]*MapOption `json:"sub_options,omitempty"`
}

type Empty struct {
Expand All @@ -61,7 +79,7 @@ func TestDefine(t *testing.T) {
obj, ok := v["github.com_zc2638_swag.Pet"]
assert.True(t, ok)
assert.False(t, obj.IsArray)
assert.Equal(t, 17, len(obj.Properties))
assert.Equal(t, 26, len(obj.Properties))

content := make(map[string]Object)
data, err := os.ReadFile("testdata/pet.json")
Expand Down
Loading