From f76b9762622bd8c550ee6385efb20e5a3ddf7368 Mon Sep 17 00:00:00 2001 From: Kevin Wan Date: Tue, 13 Dec 2022 00:16:31 +0800 Subject: [PATCH] fix: #2684 (#2693) --- core/mapping/unmarshaler.go | 862 ++++++++++++++++--------------- core/mapping/unmarshaler_test.go | 48 ++ core/mapping/utils.go | 4 +- 3 files changed, 483 insertions(+), 431 deletions(-) diff --git a/core/mapping/unmarshaler.go b/core/mapping/unmarshaler.go index e1a1c570..59977b44 100644 --- a/core/mapping/unmarshaler.go +++ b/core/mapping/unmarshaler.go @@ -80,438 +80,22 @@ func (u *Unmarshaler) UnmarshalValuer(m Valuer, v interface{}) error { return u.unmarshalWithFullName(simpleValuer{current: m}, v, "") } -func (u *Unmarshaler) unmarshalWithFullName(m valuerWithParent, v interface{}, fullName string) error { - rv := reflect.ValueOf(v) - if err := ValidatePtr(&rv); err != nil { - return err - } - - rte := reflect.TypeOf(v).Elem() - if rte.Kind() != reflect.Struct { - return errValueNotStruct - } - - rve := rv.Elem() - numFields := rte.NumField() - for i := 0; i < numFields; i++ { - if err := u.processField(rte.Field(i), rve.Field(i), m, fullName); err != nil { - return err - } - } - - return nil -} - -func (u *Unmarshaler) processAnonymousField(field reflect.StructField, value reflect.Value, - m valuerWithParent, fullName string) error { - key, options, err := u.parseOptionsWithContext(field, m, fullName) - if err != nil { - return err - } - - if _, hasValue := getValue(m, key); hasValue { - return fmt.Errorf("fields of %s can't be wrapped inside, because it's anonymous", key) - } - - if options.optional() { - return u.processAnonymousFieldOptional(field, value, key, m, fullName) - } - - return u.processAnonymousFieldRequired(field, value, m, fullName) -} - -func (u *Unmarshaler) processAnonymousFieldOptional(field reflect.StructField, value reflect.Value, - key string, m valuerWithParent, fullName string) error { - var filled bool - var required int - var requiredFilled int - var indirectValue reflect.Value - fieldType := Deref(field.Type) - - for i := 0; i < fieldType.NumField(); i++ { - subField := fieldType.Field(i) - fieldKey, fieldOpts, err := u.parseOptionsWithContext(subField, m, fullName) - if err != nil { - return err - } - - _, hasValue := getValue(m, fieldKey) - if hasValue { - if !filled { - filled = true - maybeNewValue(field, value) - indirectValue = reflect.Indirect(value) - } - if err = u.processField(subField, indirectValue.Field(i), m, fullName); err != nil { - return err - } - } - if !fieldOpts.optional() { - required++ - if hasValue { - requiredFilled++ - } - } - } - - if filled && required != requiredFilled { - return fmt.Errorf("%s is not fully set", key) - } - - return nil -} - -func (u *Unmarshaler) processAnonymousFieldRequired(field reflect.StructField, value reflect.Value, - m valuerWithParent, fullName string) error { - maybeNewValue(field, value) - fieldType := Deref(field.Type) - indirectValue := reflect.Indirect(value) - - for i := 0; i < fieldType.NumField(); i++ { - if err := u.processField(fieldType.Field(i), indirectValue.Field(i), m, fullName); err != nil { - return err - } - } - - return nil -} - -func (u *Unmarshaler) processField(field reflect.StructField, value reflect.Value, - m valuerWithParent, fullName string) error { - if usingDifferentKeys(u.key, field) { - return nil - } - - if field.Anonymous { - return u.processAnonymousField(field, value, m, fullName) - } - - return u.processNamedField(field, value, m, fullName) -} - -func (u *Unmarshaler) processFieldNotFromString(field reflect.StructField, value reflect.Value, - vp valueWithParent, opts *fieldOptionsWithContext, fullName string) error { - fieldType := field.Type - derefedFieldType := Deref(fieldType) - typeKind := derefedFieldType.Kind() - valueKind := reflect.TypeOf(vp.value).Kind() - mapValue := vp.value - - switch { - case valueKind == reflect.Map && typeKind == reflect.Struct: - if mv, ok := mapValue.(map[string]interface{}); ok { - return u.processFieldStruct(field, value, &simpleValuer{ - current: mapValuer(mv), - parent: vp.parent, - }, fullName) - } else { - return errTypeMismatch - } - case valueKind == reflect.Map && typeKind == reflect.Map: - return u.fillMap(field, value, mapValue) - case valueKind == reflect.String && typeKind == reflect.Map: - return u.fillMapFromString(value, mapValue) - case valueKind == reflect.String && typeKind == reflect.Slice: - return u.fillSliceFromString(fieldType, value, mapValue) - case valueKind == reflect.String && derefedFieldType == durationType: - return fillDurationValue(fieldType.Kind(), value, mapValue.(string)) - default: - return u.processFieldPrimitive(field, value, mapValue, opts, fullName) - } -} - -func (u *Unmarshaler) processFieldPrimitive(field reflect.StructField, value reflect.Value, - mapValue interface{}, opts *fieldOptionsWithContext, fullName string) error { - fieldType := field.Type - typeKind := Deref(fieldType).Kind() - valueKind := reflect.TypeOf(mapValue).Kind() - - switch { - case typeKind == reflect.Slice && valueKind == reflect.Slice: - return u.fillSlice(fieldType, value, mapValue) - case typeKind == reflect.Map && valueKind == reflect.Map: - return u.fillMap(field, value, mapValue) - default: - switch v := mapValue.(type) { - case json.Number: - return u.processFieldPrimitiveWithJSONNumber(field, value, v, opts, fullName) - default: - if typeKind == valueKind { - if err := validateValueInOptions(mapValue, opts.options()); err != nil { - return err - } - - return fillWithSameType(field, value, mapValue, opts) - } - } - } - - return newTypeMismatchError(fullName) -} - -func (u *Unmarshaler) processFieldPrimitiveWithJSONNumber(field reflect.StructField, value reflect.Value, - v json.Number, opts *fieldOptionsWithContext, fullName string) error { - fieldType := field.Type - fieldKind := fieldType.Kind() - typeKind := Deref(fieldType).Kind() - - if err := validateJsonNumberRange(v, opts); err != nil { - return err - } - - if err := validateValueInOptions(v, opts.options()); err != nil { - return err - } - - if fieldKind == reflect.Ptr { - value = value.Elem() - } - - switch typeKind { - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - iValue, err := v.Int64() - if err != nil { - return err - } - - value.SetInt(iValue) - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: - iValue, err := v.Int64() - if err != nil { - return err - } - - if iValue < 0 { - return fmt.Errorf("unmarshal %q with bad value %q", fullName, v.String()) - } - - value.SetUint(uint64(iValue)) - case reflect.Float32, reflect.Float64: - fValue, err := v.Float64() - if err != nil { - return err - } - - value.SetFloat(fValue) - default: - return newTypeMismatchError(fullName) - } - - return nil -} - -func (u *Unmarshaler) processFieldStruct(field reflect.StructField, value reflect.Value, - m valuerWithParent, fullName string) error { - if field.Type.Kind() == reflect.Ptr { - baseType := Deref(field.Type) - target := reflect.New(baseType).Elem() - if err := u.unmarshalWithFullName(m, target.Addr().Interface(), fullName); err != nil { - return err - } - - value.Set(target.Addr()) - } else if err := u.unmarshalWithFullName(m, value.Addr().Interface(), fullName); err != nil { - return err - } - - return nil -} - -func (u *Unmarshaler) processFieldTextUnmarshaler(field reflect.StructField, value reflect.Value, - mapValue interface{}) (bool, error) { - var tval encoding.TextUnmarshaler - var ok bool - - if field.Type.Kind() == reflect.Ptr { - tval, ok = value.Interface().(encoding.TextUnmarshaler) - } else { - tval, ok = value.Addr().Interface().(encoding.TextUnmarshaler) - } - if ok { - switch mv := mapValue.(type) { - case string: - return true, tval.UnmarshalText([]byte(mv)) - case []byte: - return true, tval.UnmarshalText(mv) - } - } - - return false, nil -} - -func (u *Unmarshaler) processFieldWithEnvValue(field reflect.StructField, value reflect.Value, - envVal string, opts *fieldOptionsWithContext, fullName string) error { - if err := validateValueInOptions(envVal, opts.options()); err != nil { - return err - } - - fieldKind := field.Type.Kind() - switch fieldKind { - case reflect.Bool: - val, err := strconv.ParseBool(envVal) - if err != nil { - return fmt.Errorf("unmarshal field %q with environment variable, %w", fullName, err) - } - - value.SetBool(val) - return nil - case durationType.Kind(): - if err := fillDurationValue(fieldKind, value, envVal); err != nil { - return fmt.Errorf("unmarshal field %q with environment variable, %w", fullName, err) - } - - return nil - case reflect.String: - value.SetString(envVal) - return nil - default: - return u.processFieldPrimitiveWithJSONNumber(field, value, json.Number(envVal), opts, fullName) - } -} - -func (u *Unmarshaler) processNamedField(field reflect.StructField, value reflect.Value, - m valuerWithParent, fullName string) error { - key, opts, err := u.parseOptionsWithContext(field, m, fullName) - if err != nil { - return err - } - - fullName = join(fullName, key) - if opts != nil && len(opts.EnvVar) > 0 { - envVal := proc.Env(opts.EnvVar) - if len(envVal) > 0 { - return u.processFieldWithEnvValue(field, value, envVal, opts, fullName) - } - } - - canonicalKey := key - if u.opts.canonicalKey != nil { - canonicalKey = u.opts.canonicalKey(key) - } - - valuer := createValuer(m, opts) - mapValue, hasValue := getValue(valuer, canonicalKey) - if !hasValue { - return u.processNamedFieldWithoutValue(field, value, opts, fullName) - } - - return u.processNamedFieldWithValue(field, value, valueWithParent{ - value: mapValue, - parent: valuer, - }, key, opts, fullName) -} - -func (u *Unmarshaler) processNamedFieldWithValue(field reflect.StructField, value reflect.Value, - vp valueWithParent, key string, opts *fieldOptionsWithContext, fullName string) error { - mapValue := vp.value - if mapValue == nil { - if opts.optional() { - return nil - } - - return fmt.Errorf("field %s mustn't be nil", key) - } - - if !value.CanSet() { - return fmt.Errorf("field %s is not settable", key) - } - - maybeNewValue(field, value) - - if yes, err := u.processFieldTextUnmarshaler(field, value, mapValue); yes { - return err - } - - fieldKind := Deref(field.Type).Kind() - switch fieldKind { - case reflect.Array, reflect.Map, reflect.Slice, reflect.Struct: - return u.processFieldNotFromString(field, value, vp, opts, fullName) - default: - if u.opts.fromString || opts.fromString() { - valueKind := reflect.TypeOf(mapValue).Kind() - if valueKind != reflect.String { - return fmt.Errorf("error: the value in map is not string, but %s", valueKind) - } - - options := opts.options() - if len(options) > 0 { - if !stringx.Contains(options, mapValue.(string)) { - return fmt.Errorf(`error: value "%s" for field "%s" is not defined in options "%v"`, - mapValue, key, options) - } - } - - return fillPrimitive(field.Type, value, mapValue, opts, fullName) - } - - return u.processFieldNotFromString(field, value, vp, opts, fullName) - } -} - -func (u *Unmarshaler) processNamedFieldWithoutValue(field reflect.StructField, value reflect.Value, - opts *fieldOptionsWithContext, fullName string) error { - derefedType := Deref(field.Type) - fieldKind := derefedType.Kind() - if defaultValue, ok := opts.getDefault(); ok { - if field.Type.Kind() == reflect.Ptr { - maybeNewValue(field, value) - value = value.Elem() - } - if derefedType == durationType { - return fillDurationValue(fieldKind, value, defaultValue) - } - - switch fieldKind { - case reflect.Array, reflect.Slice: - return u.fillSliceWithDefault(derefedType, value, defaultValue) - default: - return setValue(fieldKind, value, defaultValue) - } - } - - switch fieldKind { - case reflect.Array, reflect.Map, reflect.Slice: - if !opts.optional() { - return u.processFieldNotFromString(field, value, valueWithParent{ - value: emptyMap, - }, opts, fullName) - } - case reflect.Struct: - if !opts.optional() { - required, err := structValueRequired(u.key, derefedType) - if err != nil { - return err - } - - if required { - return fmt.Errorf("%q is not set", fullName) - } - - return u.processFieldNotFromString(field, value, valueWithParent{ - value: emptyMap, - }, opts, fullName) - } - default: - if !opts.optional() { - return newInitError(fullName) - } - } - - return nil -} - -func (u *Unmarshaler) fillMap(field reflect.StructField, value reflect.Value, mapValue interface{}) error { +func (u *Unmarshaler) fillMap(fieldType reflect.Type, value reflect.Value, mapValue interface{}) error { if !value.CanSet() { return errValueNotSettable } - fieldKeyType := field.Type.Key() - fieldElemType := field.Type.Elem() + fieldKeyType := fieldType.Key() + fieldElemType := fieldType.Elem() targetValue, err := u.generateMap(fieldKeyType, fieldElemType, mapValue) if err != nil { return err } + if !targetValue.Type().AssignableTo(value.Type()) { + return errTypeMismatch + } + value.Set(targetValue) return nil } @@ -633,12 +217,14 @@ func (u *Unmarshaler) fillSliceValue(slice reflect.Value, index int, return setValue(baseKind, ithVal, v.String()) case string: return setValue(baseKind, ithVal, v) + case map[string]interface{}: + return u.fillMap(ithVal.Type(), ithVal, value) default: // don't need to consider the difference between int, int8, int16, int32, int64, // uint, uint8, uint16, uint32, uint64, because they're handled as json.Number. if ithVal.Kind() == reflect.Ptr { baseType := Deref(ithVal.Type()) - if baseType.Kind() != reflect.TypeOf(value).Kind() { + if !reflect.TypeOf(value).AssignableTo(baseType) { return errTypeMismatch } @@ -648,7 +234,7 @@ func (u *Unmarshaler) fillSliceValue(slice reflect.Value, index int, return nil } - if ithVal.Kind() != reflect.TypeOf(value).Kind() { + if !reflect.TypeOf(value).AssignableTo(ithVal.Type()) { return errTypeMismatch } @@ -783,6 +369,424 @@ func (u *Unmarshaler) parseOptionsWithContext(field reflect.StructField, m Value return key, optsWithContext, nil } +func (u *Unmarshaler) processAnonymousField(field reflect.StructField, value reflect.Value, + m valuerWithParent, fullName string) error { + key, options, err := u.parseOptionsWithContext(field, m, fullName) + if err != nil { + return err + } + + if _, hasValue := getValue(m, key); hasValue { + return fmt.Errorf("fields of %s can't be wrapped inside, because it's anonymous", key) + } + + if options.optional() { + return u.processAnonymousFieldOptional(field.Type, value, key, m, fullName) + } + + return u.processAnonymousFieldRequired(field.Type, value, m, fullName) +} + +func (u *Unmarshaler) processAnonymousFieldOptional(fieldType reflect.Type, value reflect.Value, + key string, m valuerWithParent, fullName string) error { + var filled bool + var required int + var requiredFilled int + var indirectValue reflect.Value + derefedFieldType := Deref(fieldType) + + for i := 0; i < derefedFieldType.NumField(); i++ { + subField := derefedFieldType.Field(i) + fieldKey, fieldOpts, err := u.parseOptionsWithContext(subField, m, fullName) + if err != nil { + return err + } + + _, hasValue := getValue(m, fieldKey) + if hasValue { + if !filled { + filled = true + maybeNewValue(fieldType, value) + indirectValue = reflect.Indirect(value) + } + if err = u.processField(subField, indirectValue.Field(i), m, fullName); err != nil { + return err + } + } + if !fieldOpts.optional() { + required++ + if hasValue { + requiredFilled++ + } + } + } + + if filled && required != requiredFilled { + return fmt.Errorf("%s is not fully set", key) + } + + return nil +} + +func (u *Unmarshaler) processAnonymousFieldRequired(fieldType reflect.Type, value reflect.Value, + m valuerWithParent, fullName string) error { + maybeNewValue(fieldType, value) + derefedFieldType := Deref(fieldType) + indirectValue := reflect.Indirect(value) + + for i := 0; i < derefedFieldType.NumField(); i++ { + if err := u.processField(derefedFieldType.Field(i), indirectValue.Field(i), m, fullName); err != nil { + return err + } + } + + return nil +} + +func (u *Unmarshaler) processField(field reflect.StructField, value reflect.Value, + m valuerWithParent, fullName string) error { + if usingDifferentKeys(u.key, field) { + return nil + } + + if field.Anonymous { + return u.processAnonymousField(field, value, m, fullName) + } + + return u.processNamedField(field, value, m, fullName) +} + +func (u *Unmarshaler) processFieldNotFromString(fieldType reflect.Type, value reflect.Value, + vp valueWithParent, opts *fieldOptionsWithContext, fullName string) error { + derefedFieldType := Deref(fieldType) + typeKind := derefedFieldType.Kind() + valueKind := reflect.TypeOf(vp.value).Kind() + mapValue := vp.value + + switch { + case valueKind == reflect.Map && typeKind == reflect.Struct: + mv, ok := mapValue.(map[string]interface{}) + if !ok { + return errTypeMismatch + } + + return u.processFieldStruct(fieldType, value, &simpleValuer{ + current: mapValuer(mv), + parent: vp.parent, + }, fullName) + case valueKind == reflect.Map && typeKind == reflect.Map: + return u.fillMap(fieldType, value, mapValue) + case valueKind == reflect.String && typeKind == reflect.Map: + return u.fillMapFromString(value, mapValue) + case valueKind == reflect.String && typeKind == reflect.Slice: + return u.fillSliceFromString(fieldType, value, mapValue) + case valueKind == reflect.String && derefedFieldType == durationType: + return fillDurationValue(fieldType.Kind(), value, mapValue.(string)) + default: + return u.processFieldPrimitive(fieldType, value, mapValue, opts, fullName) + } +} + +func (u *Unmarshaler) processFieldPrimitive(fieldType reflect.Type, value reflect.Value, + mapValue interface{}, opts *fieldOptionsWithContext, fullName string) error { + typeKind := Deref(fieldType).Kind() + valueKind := reflect.TypeOf(mapValue).Kind() + + switch { + case typeKind == reflect.Slice && valueKind == reflect.Slice: + return u.fillSlice(fieldType, value, mapValue) + case typeKind == reflect.Map && valueKind == reflect.Map: + return u.fillMap(fieldType, value, mapValue) + default: + switch v := mapValue.(type) { + case json.Number: + return u.processFieldPrimitiveWithJSONNumber(fieldType, value, v, opts, fullName) + default: + if typeKind == valueKind { + if err := validateValueInOptions(mapValue, opts.options()); err != nil { + return err + } + + return fillWithSameType(fieldType, value, mapValue, opts) + } + } + } + + return newTypeMismatchError(fullName) +} + +func (u *Unmarshaler) processFieldPrimitiveWithJSONNumber(fieldType reflect.Type, value reflect.Value, + v json.Number, opts *fieldOptionsWithContext, fullName string) error { + fieldKind := fieldType.Kind() + typeKind := Deref(fieldType).Kind() + + if err := validateJsonNumberRange(v, opts); err != nil { + return err + } + + if err := validateValueInOptions(v, opts.options()); err != nil { + return err + } + + if fieldKind == reflect.Ptr { + value = value.Elem() + } + + switch typeKind { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + iValue, err := v.Int64() + if err != nil { + return err + } + + value.SetInt(iValue) + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + iValue, err := v.Int64() + if err != nil { + return err + } + + if iValue < 0 { + return fmt.Errorf("unmarshal %q with bad value %q", fullName, v.String()) + } + + value.SetUint(uint64(iValue)) + case reflect.Float32, reflect.Float64: + fValue, err := v.Float64() + if err != nil { + return err + } + + value.SetFloat(fValue) + default: + return newTypeMismatchError(fullName) + } + + return nil +} + +func (u *Unmarshaler) processFieldStruct(fieldType reflect.Type, value reflect.Value, + m valuerWithParent, fullName string) error { + if fieldType.Kind() == reflect.Ptr { + baseType := Deref(fieldType) + target := reflect.New(baseType).Elem() + if err := u.unmarshalWithFullName(m, target.Addr().Interface(), fullName); err != nil { + return err + } + + value.Set(target.Addr()) + } else if err := u.unmarshalWithFullName(m, value.Addr().Interface(), fullName); err != nil { + return err + } + + return nil +} + +func (u *Unmarshaler) processFieldTextUnmarshaler(fieldType reflect.Type, value reflect.Value, + mapValue interface{}) (bool, error) { + var tval encoding.TextUnmarshaler + var ok bool + + if fieldType.Kind() == reflect.Ptr { + tval, ok = value.Interface().(encoding.TextUnmarshaler) + } else { + tval, ok = value.Addr().Interface().(encoding.TextUnmarshaler) + } + if ok { + switch mv := mapValue.(type) { + case string: + return true, tval.UnmarshalText([]byte(mv)) + case []byte: + return true, tval.UnmarshalText(mv) + } + } + + return false, nil +} + +func (u *Unmarshaler) processFieldWithEnvValue(fieldType reflect.Type, value reflect.Value, + envVal string, opts *fieldOptionsWithContext, fullName string) error { + if err := validateValueInOptions(envVal, opts.options()); err != nil { + return err + } + + fieldKind := fieldType.Kind() + switch fieldKind { + case reflect.Bool: + val, err := strconv.ParseBool(envVal) + if err != nil { + return fmt.Errorf("unmarshal field %q with environment variable, %w", fullName, err) + } + + value.SetBool(val) + return nil + case durationType.Kind(): + if err := fillDurationValue(fieldKind, value, envVal); err != nil { + return fmt.Errorf("unmarshal field %q with environment variable, %w", fullName, err) + } + + return nil + case reflect.String: + value.SetString(envVal) + return nil + default: + return u.processFieldPrimitiveWithJSONNumber(fieldType, value, json.Number(envVal), opts, fullName) + } +} + +func (u *Unmarshaler) processNamedField(field reflect.StructField, value reflect.Value, + m valuerWithParent, fullName string) error { + key, opts, err := u.parseOptionsWithContext(field, m, fullName) + if err != nil { + return err + } + + fullName = join(fullName, key) + if opts != nil && len(opts.EnvVar) > 0 { + envVal := proc.Env(opts.EnvVar) + if len(envVal) > 0 { + return u.processFieldWithEnvValue(field.Type, value, envVal, opts, fullName) + } + } + + canonicalKey := key + if u.opts.canonicalKey != nil { + canonicalKey = u.opts.canonicalKey(key) + } + + valuer := createValuer(m, opts) + mapValue, hasValue := getValue(valuer, canonicalKey) + if !hasValue { + return u.processNamedFieldWithoutValue(field.Type, value, opts, fullName) + } + + return u.processNamedFieldWithValue(field.Type, value, valueWithParent{ + value: mapValue, + parent: valuer, + }, key, opts, fullName) +} + +func (u *Unmarshaler) processNamedFieldWithValue(fieldType reflect.Type, value reflect.Value, + vp valueWithParent, key string, opts *fieldOptionsWithContext, fullName string) error { + mapValue := vp.value + if mapValue == nil { + if opts.optional() { + return nil + } + + return fmt.Errorf("field %s mustn't be nil", key) + } + + if !value.CanSet() { + return fmt.Errorf("field %s is not settable", key) + } + + maybeNewValue(fieldType, value) + + if yes, err := u.processFieldTextUnmarshaler(fieldType, value, mapValue); yes { + return err + } + + fieldKind := Deref(fieldType).Kind() + switch fieldKind { + case reflect.Array, reflect.Map, reflect.Slice, reflect.Struct: + return u.processFieldNotFromString(fieldType, value, vp, opts, fullName) + default: + if u.opts.fromString || opts.fromString() { + valueKind := reflect.TypeOf(mapValue).Kind() + if valueKind != reflect.String { + return fmt.Errorf("error: the value in map is not string, but %s", valueKind) + } + + options := opts.options() + if len(options) > 0 { + if !stringx.Contains(options, mapValue.(string)) { + return fmt.Errorf(`error: value "%s" for field "%s" is not defined in options "%v"`, + mapValue, key, options) + } + } + + return fillPrimitive(fieldType, value, mapValue, opts, fullName) + } + + return u.processFieldNotFromString(fieldType, value, vp, opts, fullName) + } +} + +func (u *Unmarshaler) processNamedFieldWithoutValue(fieldType reflect.Type, value reflect.Value, + opts *fieldOptionsWithContext, fullName string) error { + derefedType := Deref(fieldType) + fieldKind := derefedType.Kind() + if defaultValue, ok := opts.getDefault(); ok { + if fieldType.Kind() == reflect.Ptr { + maybeNewValue(fieldType, value) + value = value.Elem() + } + if derefedType == durationType { + return fillDurationValue(fieldKind, value, defaultValue) + } + + switch fieldKind { + case reflect.Array, reflect.Slice: + return u.fillSliceWithDefault(derefedType, value, defaultValue) + default: + return setValue(fieldKind, value, defaultValue) + } + } + + switch fieldKind { + case reflect.Array, reflect.Map, reflect.Slice: + if !opts.optional() { + return u.processFieldNotFromString(fieldType, value, valueWithParent{ + value: emptyMap, + }, opts, fullName) + } + case reflect.Struct: + if !opts.optional() { + required, err := structValueRequired(u.key, derefedType) + if err != nil { + return err + } + + if required { + return fmt.Errorf("%q is not set", fullName) + } + + return u.processFieldNotFromString(fieldType, value, valueWithParent{ + value: emptyMap, + }, opts, fullName) + } + default: + if !opts.optional() { + return newInitError(fullName) + } + } + + return nil +} + +func (u *Unmarshaler) unmarshalWithFullName(m valuerWithParent, v interface{}, fullName string) error { + rv := reflect.ValueOf(v) + if err := ValidatePtr(&rv); err != nil { + return err + } + + rte := reflect.TypeOf(v).Elem() + if rte.Kind() != reflect.Struct { + return errValueNotStruct + } + + rve := rv.Elem() + numFields := rte.NumField() + for i := 0; i < numFields; i++ { + if err := u.processField(rte.Field(i), rve.Field(i), m, fullName); err != nil { + return err + } + } + + return nil +} + // WithStringValues customizes an Unmarshaler with number values from strings. func WithStringValues() UnmarshalOption { return func(opt *unmarshalOptions) { @@ -855,7 +859,7 @@ func fillPrimitive(fieldType reflect.Type, value reflect.Value, mapValue interfa } } -func fillWithSameType(field reflect.StructField, value reflect.Value, mapValue interface{}, +func fillWithSameType(fieldType reflect.Type, value reflect.Value, mapValue interface{}, opts *fieldOptionsWithContext) error { if !value.CanSet() { return errValueNotSettable @@ -865,13 +869,13 @@ func fillWithSameType(field reflect.StructField, value reflect.Value, mapValue i return err } - if field.Type.Kind() == reflect.Ptr { - baseType := Deref(field.Type) + if fieldType.Kind() == reflect.Ptr { + baseType := Deref(fieldType) target := reflect.New(baseType).Elem() setSameKindValue(baseType, target, mapValue) value.Set(target.Addr()) } else { - setSameKindValue(field.Type, value, mapValue) + setSameKindValue(fieldType, value, mapValue) } return nil diff --git a/core/mapping/unmarshaler_test.go b/core/mapping/unmarshaler_test.go index 96f841fb..356cbb3a 100644 --- a/core/mapping/unmarshaler_test.go +++ b/core/mapping/unmarshaler_test.go @@ -3628,6 +3628,54 @@ func TestUnmarshalJsonReaderWithMismatchTypeBoolMap(t *testing.T) { }, &req)) } +func TestUnmarshalJsonBytesSliceOfMaps(t *testing.T) { + input := []byte(`{ + "order_id": "1234567", + "refund_reason": { + "reason_code": [ + 123, + 234 + ], + "desc": "not wanted", + "show_reason": [ + { + "123": "not enough", + "234": "closed" + } + ] + }, + "product_detail": { + "product_id": "123", + "sku_id": "123", + "name": "cake", + "actual_amount": 100 + } +}`) + + type ( + RefundReasonData struct { + ReasonCode []int `json:"reason_code"` + Desc string `json:"desc"` + ShowReason []map[string]string `json:"show_reason"` + } + + ProductDetailData struct { + ProductId string `json:"product_id"` + SkuId string `json:"sku_id"` + Name string `json:"name"` + ActualAmount int `json:"actual_amount"` + } + OrderApplyRefundReq struct { + OrderId string `json:"order_id"` + RefundReason RefundReasonData `json:"refund_reason,optional"` + ProductDetail ProductDetailData `json:"product_detail,optional"` + } + ) + + var req OrderApplyRefundReq + assert.NoError(t, UnmarshalJsonBytes(input, &req)) +} + func BenchmarkDefaultValue(b *testing.B) { for i := 0; i < b.N; i++ { var a struct { diff --git a/core/mapping/utils.go b/core/mapping/utils.go index d7c9beca..54259614 100644 --- a/core/mapping/utils.go +++ b/core/mapping/utils.go @@ -210,8 +210,8 @@ func isRightInclude(b byte) (bool, error) { } } -func maybeNewValue(field reflect.StructField, value reflect.Value) { - if field.Type.Kind() == reflect.Ptr && value.IsNil() { +func maybeNewValue(fieldType reflect.Type, value reflect.Value) { + if fieldType.Kind() == reflect.Ptr && value.IsNil() { value.Set(reflect.New(value.Type().Elem())) } }