Skip to content

Commit

Permalink
added unit tests for issues (#21)
Browse files Browse the repository at this point in the history
* added unit tests for issues

* resolve issues

* add tests for unmarshal_from_json_map

* rename argument for clarity
  • Loading branch information
avivpxi authored Nov 10, 2022
1 parent 489edf4 commit cc4ddda
Show file tree
Hide file tree
Showing 4 changed files with 267 additions and 34 deletions.
38 changes: 21 additions & 17 deletions unmarshal.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ func Unmarshal(data []byte, v interface{}, options ...UnmarshalOption) (map[stri
} else if !d.lexer.IsDelim('{') {
return nil, ErrInvalidInput
} else {
d.populateStruct(v, result)
d.populateStruct(false, v, result)
}
d.lexer.Consumed()
if useMultipleErrors {
Expand All @@ -59,8 +59,8 @@ type decoder struct {
lexer *jlexer.Lexer
}

func (d *decoder) populateStruct(structInstance interface{}, result map[string]interface{}) (interface{}, bool) {
doPopulate := !d.options.skipPopulateStruct || result == nil
func (d *decoder) populateStruct(forcePopulate bool, structInstance interface{}, result map[string]interface{}) (interface{}, bool) {
doPopulate := !d.options.skipPopulateStruct || forcePopulate
var structValue reflect.Value
if doPopulate {
structValue = reflectStructValue(structInstance)
Expand All @@ -76,25 +76,26 @@ func (d *decoder) populateStruct(structInstance interface{}, result map[string]i
d.lexer.WantColon()
refInfo, exists := fields[key]
if exists {
value, isValidType := d.valueByReflectType(refInfo.t, false)
value, isValidType := d.valueByReflectType(refInfo.t)
if isValidType {
if value != nil && doPopulate {
field := refInfo.field(structValue)
assignValue(field, value)
}
if result != nil {
if !d.options.excludeKnownFieldsFromMap {
if !d.options.excludeKnownFieldsFromMap {
if result != nil {
result[key] = value
}
} else if clone != nil {
clone[key] = value
if clone != nil {
clone[key] = value
}
}
} else {
switch d.options.mode {
case ModeFailOnFirstError:
return nil, false
case ModeFailOverToOriginalValue:
if result != nil {
if !forcePopulate {
result[key] = value
} else {
clone[key] = value
Expand All @@ -109,14 +110,17 @@ func (d *decoder) populateStruct(structInstance interface{}, result map[string]i
if result != nil {
result[key] = value
}
if clone != nil {
clone[key] = value
}
}
d.lexer.WantComma()
}
d.lexer.Delim('}')
return structInstance, true
}

func (d *decoder) valueByReflectType(t reflect.Type, isPtr bool) (interface{}, bool) {
func (d *decoder) valueByReflectType(t reflect.Type) (interface{}, bool) {
if t.Implements(unmarshalerType) {
result := reflect.New(t.Elem()).Interface()
d.valueFromCustomUnmarshaler(result.(json.Unmarshaler))
Expand Down Expand Up @@ -160,7 +164,7 @@ func (d *decoder) valueByReflectType(t reflect.Type, isPtr bool) (interface{}, b
if t.Elem().Kind() == reflect.Struct {
return d.buildStruct(t.Elem())
}
value, valid := d.valueByReflectType(t.Elem(), true)
value, valid := d.valueByReflectType(t.Elem())
if value == nil {
return nil, valid
}
Expand Down Expand Up @@ -193,7 +197,7 @@ func (d *decoder) buildSlice(sliceType reflect.Type) (interface{}, bool) {
sliceValue = reflect.MakeSlice(sliceType, 0, 0)
}
for !d.lexer.IsDelim(']') {
current, valid := d.valueByReflectType(elemType, false)
current, valid := d.valueByReflectType(elemType)
if !valid {
if d.options.mode != ModeFailOverToOriginalValue {
d.drainLexerArray(nil)
Expand Down Expand Up @@ -223,7 +227,7 @@ func (d *decoder) buildArray(arrayType reflect.Type) (interface{}, bool) {
arrayValue := reflect.New(arrayType).Elem()
d.lexer.Delim('[')
for i := 0; !d.lexer.IsDelim(']'); i++ {
current, valid := d.valueByReflectType(elemType, false)
current, valid := d.valueByReflectType(elemType)
if !valid {
if d.options.mode != ModeFailOverToOriginalValue {
d.drainLexerArray(nil)
Expand Down Expand Up @@ -256,7 +260,7 @@ func (d *decoder) buildMap(mapType reflect.Type) (interface{}, bool) {
valueType := mapType.Elem()
mapValue := reflect.MakeMap(mapType)
for !d.lexer.IsDelim('}') {
key, valid := d.valueByReflectType(keyType, false)
key, valid := d.valueByReflectType(keyType)
if !valid {
if d.options.mode != ModeFailOverToOriginalValue {
d.lexer.WantColon()
Expand All @@ -275,7 +279,7 @@ func (d *decoder) buildMap(mapType reflect.Type) (interface{}, bool) {
return result, true
}
d.lexer.WantColon()
value, valid := d.valueByReflectType(valueType, false)
value, valid := d.valueByReflectType(valueType)
if !valid {
if d.options.mode != ModeFailOverToOriginalValue {
d.lexer.WantComma()
Expand Down Expand Up @@ -308,10 +312,10 @@ func (d *decoder) buildStruct(structType reflect.Type) (interface{}, bool) {
value := reflect.New(structType).Interface()
handler, ok := value.(JSONDataHandler)
if !ok {
return d.populateStruct(value, nil)
return d.populateStruct(true, value, nil)
}
data := make(map[string]interface{})
result, valid := d.populateStruct(value, data)
result, valid := d.populateStruct(true, value, data)
if valid {
handler.HandleJSONData(data)
}
Expand Down
30 changes: 15 additions & 15 deletions unmarshal_from_json_map.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ func UnmarshalFromJSONMap(data map[string]interface{}, v interface{}, options ..
d := &mapDecoder{options: opts}
result := make(map[string]interface{})
if data != nil {
d.populateStruct(nil, data, v, result)
d.populateStruct(false, nil, data, v, result)
}
if opts.mode == ModeAllowMultipleErrors || opts.mode == ModeFailOverToOriginalValue {
if len(d.errs) == 0 {
Expand All @@ -64,8 +64,8 @@ type mapDecoder struct {
errs []error
}

func (m *mapDecoder) populateStruct(path []string, data map[string]interface{}, structInstance interface{}, result map[string]interface{}) (interface{}, bool) {
doPopulate := !m.options.skipPopulateStruct || result == nil
func (m *mapDecoder) populateStruct(forcePopulate bool, path []string, data map[string]interface{}, structInstance interface{}, result map[string]interface{}) (interface{}, bool) {
doPopulate := !m.options.skipPopulateStruct || forcePopulate
var structValue reflect.Value
if doPopulate {
structValue = reflectStructValue(structInstance)
Expand All @@ -74,14 +74,14 @@ func (m *mapDecoder) populateStruct(path []string, data map[string]interface{},
for key, inputValue := range data {
refInfo, exists := fields[key]
if exists {
value, isValidType := m.valueByReflectType(append(path, key), inputValue, refInfo.t, false)
value, isValidType := m.valueByReflectType(append(path, key), inputValue, refInfo.t)
if isValidType {
if value != nil && doPopulate {
field := refInfo.field(structValue)
assignValue(field, value)
}
if result != nil {
if !m.options.excludeKnownFieldsFromMap {
if !m.options.excludeKnownFieldsFromMap {
if result != nil {
result[key] = value
}
}
Expand All @@ -90,7 +90,7 @@ func (m *mapDecoder) populateStruct(path []string, data map[string]interface{},
case ModeFailOnFirstError:
return nil, false
case ModeFailOverToOriginalValue:
if result != nil {
if !forcePopulate {
result[key] = value
} else {
return data, false
Expand All @@ -106,7 +106,7 @@ func (m *mapDecoder) populateStruct(path []string, data map[string]interface{},
return structInstance, true
}

func (m *mapDecoder) valueByReflectType(path []string, v interface{}, t reflect.Type, isPtr bool) (interface{}, bool) {
func (m *mapDecoder) valueByReflectType(path []string, v interface{}, t reflect.Type) (interface{}, bool) {
if t.Implements(unmarshalerFromJSONMapType) {
result := reflect.New(t.Elem()).Interface()
m.valueFromCustomUnmarshaler(v, result.(UnmarshalerFromJSONMap))
Expand Down Expand Up @@ -149,7 +149,7 @@ func (m *mapDecoder) valueByReflectType(path []string, v interface{}, t reflect.
if t.Elem().Kind() == reflect.Struct {
return m.buildStruct(path, v, t.Elem())
}
value, valid := m.valueByReflectType(path, v, t.Elem(), true)
value, valid := m.valueByReflectType(path, v, t.Elem())
if value == nil {
return nil, valid
}
Expand Down Expand Up @@ -181,7 +181,7 @@ func (m *mapDecoder) buildSlice(path []string, v interface{}, sliceType reflect.
sliceValue = reflect.MakeSlice(sliceType, 0, 0)
}
for _, element := range arr {
current, valid := m.valueByReflectType(path, element, elemType, false)
current, valid := m.valueByReflectType(path, element, elemType)
if !valid {
if m.options.mode != ModeFailOverToOriginalValue {
return nil, true
Expand All @@ -205,7 +205,7 @@ func (m *mapDecoder) buildArray(path []string, v interface{}, arrayType reflect.
elemType := arrayType.Elem()
arrayValue := reflect.New(arrayType).Elem()
for i, element := range arr {
current, valid := m.valueByReflectType(path, element, elemType, false)
current, valid := m.valueByReflectType(path, element, elemType)
if !valid {
if m.options.mode != ModeFailOverToOriginalValue {
return nil, true
Expand Down Expand Up @@ -233,14 +233,14 @@ func (m *mapDecoder) buildMap(path []string, v interface{}, mapType reflect.Type
mapValue := reflect.MakeMap(mapType)
for inputKey, inputValue := range mp {
keyPath := append(path, inputKey)
key, valid := m.valueByReflectType(keyPath, inputKey, keyType, false)
key, valid := m.valueByReflectType(keyPath, inputKey, keyType)
if !valid {
if m.options.mode != ModeFailOverToOriginalValue {
return nil, true
}
return v, true
}
value, valid := m.valueByReflectType(keyPath, inputValue, valueType, false)
value, valid := m.valueByReflectType(keyPath, inputValue, valueType)
if !valid {
if m.options.mode != ModeFailOverToOriginalValue {
return nil, true
Expand All @@ -264,10 +264,10 @@ func (m *mapDecoder) buildStruct(path []string, v interface{}, structType reflec
value := reflect.New(structType).Interface()
handler, ok := value.(JSONDataHandler)
if !ok {
return m.populateStruct(path, mp, value, nil)
return m.populateStruct(true, path, mp, value, nil)
}
data := make(map[string]interface{})
result, valid := m.populateStruct(path, mp, value, data)
result, valid := m.populateStruct(true, path, mp, value, data)
if valid {
handler.HandleJSONData(data)
}
Expand Down
117 changes: 115 additions & 2 deletions unmarshal_from_json_map_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2305,7 +2305,14 @@ func TestUnmarshalFromJSONMapJSONDataHandler(t *testing.T) {
func TestUnmarshalFromJSONMapExcludeKnownFieldsFromMap(t *testing.T) {
t.Run("test_exclude_known_fields_from_map_with_empty_map", func(t *testing.T) {
p := Person{}
result, err := UnmarshalFromJSONMap(map[string]interface{}{"firstName": "string_firstName", "lastName": "string_LastName"}, &p, WithExcludeKnownFieldsFromMap(true))
result, err := UnmarshalFromJSONMap(
map[string]interface{}{
"firstName": "string_firstName",
"lastName": "string_LastName",
},
&p,
WithExcludeKnownFieldsFromMap(true),
)
if err != nil {
t.Errorf("unexpected error %v", err)
}
Expand All @@ -2316,7 +2323,15 @@ func TestUnmarshalFromJSONMapExcludeKnownFieldsFromMap(t *testing.T) {

t.Run("test_exclude_known_fields_from_map", func(t *testing.T) {
p := Person{}
result, err := UnmarshalFromJSONMap(map[string]interface{}{"firstName": "string_firstName", "lastName": "string_LastName", "unknown": "string_unknown"}, &p, WithExcludeKnownFieldsFromMap(true))
result, err := UnmarshalFromJSONMap(
map[string]interface{}{
"firstName": "string_firstName",
"lastName": "string_LastName",
"unknown": "string_unknown",
},
&p,
WithExcludeKnownFieldsFromMap(true),
)
if err != nil {
t.Errorf("unexpected error %v", err)
}
Expand All @@ -2330,3 +2345,101 @@ func TestUnmarshalFromJSONMapExcludeKnownFieldsFromMap(t *testing.T) {
}
})
}

func TestUnmarshalFromJSONMapNestedSkipPopulate(t *testing.T) {
t.Run("TestUnmarshalFromJSONMapNestedSkipPopulate", func(t *testing.T) {
p := &nestedSkipPopulateParent{}
result, err := UnmarshalFromJSONMap(
map[string]interface{}{"child": map[string]interface{}{"foo": "value"}},
p,
WithSkipPopulateStruct(true),
)
if err != nil {
t.Errorf("unexpected error %v", err)
}
value, exists := result["child"]
if !exists {
t.Error("missing child element in result map")
}
child, ok := value.(nestedSkipPopulateChild)
if !ok {
t.Errorf("invalid child type %T in result map", child)
}
if child.Foo != "value" {
t.Errorf("invalid value '%s' in child", child.Foo)
}
})
t.Run("TestUnmarshalFromJSONMapNestedSkipPopulate_with_ModeFailOverToOriginalValue", func(t *testing.T) {
p := &nestedSkipPopulateParent{}
result, err := UnmarshalFromJSONMap(
map[string]interface{}{"child": map[string]interface{}{"foo": float64(12)}},
p,
WithMode(ModeFailOverToOriginalValue),
WithSkipPopulateStruct(true),
)
if err == nil {
t.Error("expected error")
}
value, exists := result["child"]
if !exists {
t.Error("missing child element in result map")
}
child, ok := value.(map[string]interface{})
if !ok {
t.Errorf("invalid child type %T in result map", child)
}
if child["foo"] != float64(12) {
t.Errorf("invalid value '%v' in child", child["foo"])
}
})
t.Run("TestUnmarshalFromJSONMapNestedSkipPopulate_all_fields_exist_in_root_struct", func(t *testing.T) {
s := &failOverStruct{}
result, err := UnmarshalFromJSONMap(
map[string]interface{}{"a": "a_val", "b": float64(12), "c": "c_val"},
s,
WithMode(ModeFailOverToOriginalValue),
WithSkipPopulateStruct(true),
)
if err == nil {
t.Error("expected error")
}
if result["a"] != "a_val" {
t.Errorf("invalid value '%v' in a", result["a"])
}
if result["b"] != float64(12) {
t.Errorf("invalid value '%v' in a", result["b"])
}
if result["c"] != "c_val" {
t.Errorf("invalid value '%v' in a", result["c"])
}
})
t.Run("TestUnmarshalFromJSONMapNestedSkipPopulate_all_fields_exist_in_nested_struct", func(t *testing.T) {
s := &failOverParent{}
result, err := UnmarshalFromJSONMap(
map[string]interface{}{"child": map[string]interface{}{"a": "a_val", "b": float64(12), "c": "c_val"}},
s,
WithMode(ModeFailOverToOriginalValue),
WithSkipPopulateStruct(true),
)
if err == nil {
t.Error("expected error")
}
val, ok := result["child"]
if !ok {
t.Error("missing child in result value")
}
child, ok := val.(map[string]interface{})
if !ok {
t.Error("invalid child type in result value")
}
if child["a"] != "a_val" {
t.Errorf("invalid value '%v' in a", child["a"])
}
if child["b"] != float64(12) {
t.Errorf("invalid value '%v' in a", child["b"])
}
if child["c"] != "c_val" {
t.Errorf("invalid value '%v' in a", child["c"])
}
})
}
Loading

0 comments on commit cc4ddda

Please sign in to comment.