Skip to content

Commit

Permalink
support map parse (#40)
Browse files Browse the repository at this point in the history
* support map parse

---------

Co-authored-by: gouhuan <[email protected]>
  • Loading branch information
HugoWw and gouhuan authored Apr 2, 2024
1 parent ca43ef8 commit 35fc1f3
Show file tree
Hide file tree
Showing 7 changed files with 382 additions and 38 deletions.
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
173 changes: 168 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,145 @@ 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 {
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
}

// isByteArrayType
// Check if the data is of slice or array type
// while simultaneously verifying if the element type is of byte type(in go defined "type byte=uint8").
func isByteArrayType(t reflect.Type) bool {
return (t.Kind() == reflect.Slice || t.Kind() == reflect.Array) &&
t.Elem().Kind() == reflect.Uint8
}

// 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

0 comments on commit 35fc1f3

Please sign in to comment.