Skip to content

Commit

Permalink
Merge pull request #4 from claudioluciano/feature/support_json_tags
Browse files Browse the repository at this point in the history
feat: add support for parsing struct json tags
  • Loading branch information
rsfreitas authored Jun 12, 2023
2 parents 1a8aa46 + 3a41319 commit b8d11be
Show file tree
Hide file tree
Showing 5 changed files with 70 additions and 27 deletions.
16 changes: 8 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ v := struct {
Name: "John",
}

parsed := Parse(v)
parsed, err := Parse(v)
```

The `parsed` variable will now contain a map with the same fields as the input struct:
Expand All @@ -35,7 +35,7 @@ map[string]interface{}{

The output format of the `Parse` function depends on the input type:

- Struct: the output is a map with the same fields as the input struct. The keys of the map are the field names, and the values are the field types represented as strings.
- Struct: the output is a map with the same fields as the input struct. The keys of the map are the field names (if `json` tags is being used, the name attribute will be the key), and the values are the field types represented as strings.

- Map: the output is a map with the same keys as the input map. The values of the map are the corresponding types of the input map values, represented as strings.

Expand All @@ -50,16 +50,16 @@ Here are some examples of input and output for the `Parse` function:
```go
// Struct
v := struct {
Age int
Age int `json:"age"`
Name string
}{
Age: 30,
Name: "John",
}

parsed := Parse(v)
parsed, err := Parse(v)
// parsed = map[string]interface{}{
// "Age": "int",
// "age": "int",
// "Name": "string",
// }

Expand All @@ -69,7 +69,7 @@ v := map[string]int{
"bar": 69,
}

parsed := Parse(v)
parsed, err := Parse(v)
// parsed = map[string]interface{}{
// "foo": "int",
// "bar": "int",
Expand All @@ -81,7 +81,7 @@ v := []interface{}{
42,
}

parsed := Parse(v)
parsed, err := Parse(v)
// parsed = []interface{}{
// "string",
// "int",
Expand All @@ -91,6 +91,6 @@ parsed := Parse(v)
// Primitive types
v := true

parsed := Parse(v)
parsed, err := Parse(v)
// parsed = "bool"
```
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
module github.com/claudioluciano/simple_json_schema_like

go 1.18

require github.com/fatih/structtag v1.2.0
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
github.com/fatih/structtag v1.2.0 h1:/OdNE99OxoI/PqaW/SuSK9uxxT3f/tcSZgon/ssNSx4=
github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94=
75 changes: 57 additions & 18 deletions parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package simplejsonschemalike
import (
"fmt"
"reflect"

"github.com/fatih/structtag"
)

const timeStringKind = "time.Time"
Expand All @@ -23,17 +25,16 @@ const timeStringKind = "time.Time"
//
// The function returns an interface{} value that can be asserted to the appropriate type
// after the function call.
func Parse(in interface{}) interface{} {
func Parse(in interface{}) (interface{}, error) {
v := reflect.ValueOf(in)

return parse(v)
}

func parse(v reflect.Value) interface{} {
func parse(v reflect.Value) (interface{}, error) {
switch v.Kind() {
case reflect.Struct:
if ok := isTime(v); ok {
return "date-time"
return "date-time", nil
}

return parseStruct(v)
Expand All @@ -46,7 +47,7 @@ func parse(v reflect.Value) interface{} {
case reflect.Slice, reflect.Array:
return parseSlice(v)
default:
return v.Type().String()
return v.Type().String(), nil
}
}

Expand All @@ -58,7 +59,7 @@ func parseNil(v reflect.Value) interface{} {
return v.Type().String()
}

func parseStruct(v reflect.Value) map[string]interface{} {
func parseStruct(v reflect.Value) (map[string]interface{}, error) {
m := make(map[string]interface{})

for i := 0; i < v.NumField(); i++ {
Expand All @@ -68,38 +69,72 @@ func parseStruct(v reflect.Value) map[string]interface{} {
continue
}

m[field.Name] = parse(v.Field(i))
name := field.Name
tagName, err := getNameFromTag(field)
if err != nil {
return nil, err
}
if tagName != "" {
name = tagName
}

v, err := parse(v.Field(i))
if err != nil {
return nil, err
}

m[name] = v
}

return m
return m, nil
}

func parseInterface(v reflect.Value) interface{} {
func getNameFromTag(field reflect.StructField) (string, error) {
tags, err := structtag.Parse(string(field.Tag))
if err != nil {
return "", err
}

jsonTag, err := tags.Get("json")
if err != nil {
// json tag does not exist
return "", nil
}

return jsonTag.Name, nil
}

func parseInterface(v reflect.Value) (interface{}, error) {
field := v.Elem()
return parse(field)
}

func parsePrt(v reflect.Value) interface{} {
func parsePrt(v reflect.Value) (interface{}, error) {
if v.IsNil() {
return parseNil(v)
return parseNil(v), nil
}

field := v.Elem()
return parse(field)
}

func parseMap(v reflect.Value) interface{} {
func parseMap(v reflect.Value) (interface{}, error) {
m := make(map[string]interface{})

for _, key := range v.MapKeys() {
value := v.MapIndex(key)
m[key.String()] = parse(value)
v, err := parse(value)
if err != nil {
return nil, err
}

m[key.String()] = v
}

return m
return m, nil
}

func parseSlice(v reflect.Value) interface{} {
func parseSlice(v reflect.Value) (interface{}, error) {
if v.Len() == 0 {
el := v.Type().Elem()
elType := el.String()
Expand All @@ -108,17 +143,21 @@ func parseSlice(v reflect.Value) interface{} {
elType = "any"
}

return fmt.Sprintf("[%v]", elType)
return fmt.Sprintf("[%v]", elType), nil
}

var m []interface{}
for i := 0; i < v.Len(); i++ {
field := v.Index(i)
t := parse(field)
t, err := parse(field)
if err != nil {
return nil, err
}

m = append(m, t)
}

return m
return m, nil
}

func isTime(v reflect.Value) bool {
Expand Down
2 changes: 1 addition & 1 deletion parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ func TestParse(t *testing.T) {

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
actual := Parse(tt.input)
actual, _ := Parse(tt.input)

expectedMap := make(map[string]interface{})
expectedBytes, _ := json.Marshal(actual)
Expand Down

0 comments on commit b8d11be

Please sign in to comment.