diff --git a/etc.go b/etc.go index 92be79c..50fdc46 100644 --- a/etc.go +++ b/etc.go @@ -5,7 +5,7 @@ import ( "strings" ) -func validTag(filed reflect.StructField) bool { +func validTag(filed reflect.StructField, tag string) bool { return !(filed.Tag.Get(tag) == "" || filed.Tag.Get(tag) == "-") } diff --git a/unmarshal.go b/unmarshal.go index e66090d..7ccc1fa 100644 --- a/unmarshal.go +++ b/unmarshal.go @@ -3,18 +3,22 @@ package njson import ( "encoding/json" "errors" + "fmt" "reflect" + "strings" "github.com/tidwall/gjson" ) -const tag string = "njson" +const ( + njsonTag = "njson" + jsonTag = "json" +) var jsonNumberType = reflect.TypeOf(json.Number("")) // Unmarshal used to unmarshal nested json using "njson" tag func Unmarshal(data []byte, v interface{}) (err error) { - // catch code panic and return error message defer func() { if r := recover(); r != nil { @@ -24,7 +28,7 @@ func Unmarshal(data []byte, v interface{}) (err error) { case error: err = x default: - err = errors.New("Unknown panic") + err = fmt.Errorf("unknown panic: %v", r) } } }() @@ -34,12 +38,25 @@ func Unmarshal(data []byte, v interface{}) (err error) { for i := 0; i < elem.NumField(); i++ { field := elem.Field(i) - if !(validTag(typeOfT.Field(i)) && field.CanSet()) { + // Check that the tag is either "json" or "njson", and can be set + if (!validTag(typeOfT.Field(i), njsonTag) && !validTag(typeOfT.Field(i), jsonTag)) || !field.CanSet() { continue } + // Assume "njson" by default, but change to "json" if tag matches + fieldName := typeOfT.Field(i).Tag.Get(njsonTag) + if validTag(typeOfT.Field(i), jsonTag) { + fieldName = typeOfT.Field(i).Tag.Get(jsonTag) + + // Only support true "json" tags: + // if a tag is nested, it must use the "njson" tag + if len(strings.Split(fieldName, ".")) > 1 { + return fmt.Errorf("invalid json tag: %s", fieldName) + } + } + // get field value by tag - result := gjson.GetBytes(data, typeOfT.Field(i).Tag.Get(tag)) + result := gjson.GetBytes(data, fieldName) // if field type json.Number if v != nil && field.Kind() == reflect.String && field.Type() == jsonNumberType { @@ -60,7 +77,7 @@ func Unmarshal(data []byte, v interface{}) (err error) { } } - return nil + return } func unmarshalSlice(results []gjson.Result, field reflect.Type) interface{} { diff --git a/unmarshal_test.go b/unmarshal_test.go index ea7a3d7..2f09a20 100644 --- a/unmarshal_test.go +++ b/unmarshal_test.go @@ -260,7 +260,7 @@ func TestUnmarshalSlices(t *testing.T) { [ [ 601, 602, 603 - ], + ] ] ], @@ -429,7 +429,7 @@ func TestUnmarshalComplex(t *testing.T) { ], "time_1": "2021-01-11T23:56:51.141Z", "time_2": "2021-01-11T23:56:51.141+01:00", - "time_3": "2021-01-11T23:56:51.141-01:00", + "time_3": "2021-01-11T23:56:51.141-01:00" } ` @@ -550,3 +550,93 @@ func TestUnmarshalMoreComplex(t *testing.T) { } } + +func TestUnmarshalJson(t *testing.T) { + t.Run("success", func(t *testing.T) { + json := ` + { + "name": {"first": "Mohamed", "last": "Shapan"}, + "age": 26, + "friends": [ + {"first": "Asma", "age": 26}, + {"first": "Ahmed", "age": 25}, + {"first": "Mahmoud", "age": 30} + ] + }` + + type Name struct { + First string `njson:"first"` + Last string `njson:"last"` + } + + type User struct { + Name Name `njson:"name"` + Age int `json:"age"` + Friends []Name `json:"friends"` + } + + actual := User{} + + err := Unmarshal([]byte(json), &actual) + if err != nil { + t.Error(err) + } + + var friends []Name + friends = append(friends, Name{ + First: "Asma", + }) + + friends = append(friends, Name{ + First: "Ahmed", + }) + + friends = append(friends, Name{ + First: "Mahmoud", + }) + + expected := User{ + Name: Name{ + First: "Mohamed", + Last: "Shapan", + }, + Age: 26, + Friends: friends, + } + + diff := cmp.Diff(expected, actual) + if diff != "" { + t.Error(diff) + } + }) + + t.Run("fail", func(t *testing.T) { + json := ` + { + "name": {"first": "Mohamed", "last": "Shapan"}, + "age": 26, + "friends": [ + {"first": "Asma", "age": 26}, + {"first": "Ahmed", "age": 25}, + {"first": "Mahmoud", "age": 30} + ] + }` + + type Name struct { + First string `njson:"first"` + Last string `njson:"last"` + } + + type User struct { + Name string `json:"name.first"` + Age int `json:"age"` + Friends []Name `json:"friends"` + } + + actual := User{} + + if err := Unmarshal([]byte(json), &actual); err == nil { + t.Error("error should not be nil") + } + }) +}