Repository: zeromicro/go-zero
Pull request #4145 (2024-05-11 23:46:26)

chore: refactor mapping unmarshaler

codecov[bot]: ## [Codecov](https://app.codecov.io/gh/zeromicro/go-zero/pull/4145?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=zeromicro) Report
Attention: Patch coverage is `75.00000%` with `6 lines` in your changes are missing coverage. Please review.
> Project coverage is 95.08%. Comparing base [(`8690859`)](https://app.codecov.io/gh/zeromicro/go-zero/commit/8690859c7d1c9461bdc422c3c65608bde30904db?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=zeromicro) to head [(`9dd3da3`)](https://app.codecov.io/gh/zeromicro/go-zero/pull/4145?dropdown=coverage&src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=zeromicro).
> Report is 48 commits behind head on master.

<details><summary>Additional details and impacted files</summary>



| [Files](https://app.codecov.io/gh/zeromicro/go-zero/pull/4145?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=zeromicro) | Coverage Δ | |
|---|---|---|
| [core/mapping/unmarshaler.go](https://app.codecov.io/gh/zeromicro/go-zero/pull/4145?src=pr&el=tree&filepath=core%2Fmapping%2Funmarshaler.go&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=zeromicro#diff-Y29yZS9tYXBwaW5nL3VubWFyc2hhbGVyLmdv) | `96.33% <75.00%> (-0.15%)` | :arrow_down: |

... and [254 files with indirect coverage changes](https://app.codecov.io/gh/zeromicro/go-zero/pull/4145/indirect-changes?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=zeromicro)

</details>

Patch:
diff --git a/core/mapping/unmarshaler.go b/core/mapping/unmarshaler.go
index b43df3ca..fc843891 100644
--- a/core/mapping/unmarshaler.go
+++ b/core/mapping/unmarshaler.go
@@ -113,27 +113,6 @@ func (u *Unmarshaler) unmarshalValuer(m Valuer, v any, fullName string) error {
 	return u.unmarshalWithFullName(simpleValuer{current: m}, v, fullName)
 }
 
-func (u *Unmarshaler) fillJsonUnmarshalerStruct(fieldType reflect.Type,
-	value reflect.Value, targetValue string) error {
-	if !value.CanSet() {
-		return errValueNotSettable
-	}
-
-	baseType := Deref(fieldType)
-	target := reflect.New(baseType)
-	unmarshaler, ok := target.Interface().(json.Unmarshaler)
-	if !ok {
-		return errUnsupportedType
-	}
-
-	if err := unmarshaler.UnmarshalJSON([]byte(targetValue)); err != nil {
-		return err
-	}
-
-	value.Set(target)
-	return nil
-}
-
 func (u *Unmarshaler) fillMap(fieldType reflect.Type, value reflect.Value,
 	mapValue any, fullName string) error {
 	if !value.CanSet() {
@@ -330,6 +309,32 @@ func (u *Unmarshaler) fillSliceWithDefault(derefedType reflect.Type, value refle
 	return u.fillSlice(derefedType, value, slice, fullName)
 }
 
+func (u *Unmarshaler) fillUnmarshalerStruct(fieldType reflect.Type,
+	value reflect.Value, targetValue string) error {
+	if !value.CanSet() {
+		return errValueNotSettable
+	}
+
+	baseType := Deref(fieldType)
+	target := reflect.New(baseType)
+	switch u.key {
+	case jsonTagKey:
+		unmarshaler, ok := target.Interface().(json.Unmarshaler)
+		if !ok {
+			return errUnsupportedType
+		}
+
+		if err := unmarshaler.UnmarshalJSON([]byte(targetValue)); err != nil {
+			return err
+		}
+	default:
+		return errUnsupportedType
+	}
+
+	value.Set(target)
+	return nil
+}
+
 func (u *Unmarshaler) generateMap(keyType, elemType reflect.Type, mapValue any,
 	fullName string) (reflect.Value, error) {
 	mapType := reflect.MapOf(keyType, elemType)
@@ -423,6 +428,15 @@ func (u *Unmarshaler) generateMap(keyType, elemType reflect.Type, mapValue any,
 	return targetValue, nil
 }
 
+func (u *Unmarshaler) implementsUnmarshaler(t reflect.Type) bool {
+	switch u.key {
+	case jsonTagKey:
+		return t.Implements(reflect.TypeOf((*json.Unmarshaler)(nil)).Elem())
+	default:
+		return false
+	}
+}
+
 func (u *Unmarshaler) parseOptionsWithContext(field reflect.StructField, m Valuer, fullName string) (
 	string, *fieldOptionsWithContext, error) {
 	key, options, err := parseKeyAndOptions(u.key, field)
@@ -600,8 +614,8 @@ func (u *Unmarshaler) processFieldNotFromString(fieldType reflect.Type, value re
 		return u.fillSliceFromString(fieldType, value, mapValue, fullName)
 	case valueKind == reflect.String && derefedFieldType == durationType:
 		return fillDurationValue(fieldType, value, mapValue.(string))
-	case valueKind == reflect.String && typeKind == reflect.Struct && implementsJsonUnmarshaler(fieldType):
-		return u.fillJsonUnmarshalerStruct(fieldType, value, mapValue.(string))
+	case valueKind == reflect.String && typeKind == reflect.Struct && u.implementsUnmarshaler(fieldType):
+		return u.fillUnmarshalerStruct(fieldType, value, mapValue.(string))
 	default:
 		return u.processFieldPrimitive(fieldType, value, mapValue, opts, fullName)
 	}
@@ -1087,10 +1101,6 @@ func getValueWithChainedKeys(m valuerWithParent, keys []string) (any, bool) {
 	}
 }
 
-func implementsJsonUnmarshaler(t reflect.Type) bool {
-	return t.Implements(reflect.TypeOf((*json.Unmarshaler)(nil)).Elem())
-}
-
 func join(elem ...string) string {
 	var builder strings.Builder
 


Test patch:
diff --git a/core/mapping/unmarshaler_test.go b/core/mapping/unmarshaler_test.go
index 77434fe0..2e16df83 100644
--- a/core/mapping/unmarshaler_test.go
+++ b/core/mapping/unmarshaler_test.go
@@ -5761,7 +5761,7 @@ func TestUnmarshalWithIgnoreFields(t *testing.T) {
 	}
 }
 
-func TestUnmarshal_JsonUnmarshaler(t *testing.T) {
+func TestUnmarshal_Unmarshaler(t *testing.T) {
 	t.Run("success", func(t *testing.T) {
 		v := struct {
 			Foo *mockUnmarshaler `json:"name"`
@@ -5778,6 +5778,30 @@ func TestUnmarshal_JsonUnmarshaler(t *testing.T) {
 		body := `{"name": "hello"}`
 		assert.Error(t, UnmarshalJsonBytes([]byte(body), &v))
 	})
+
+	t.Run("not json unmarshaler", func(t *testing.T) {
+		v := struct {
+			Foo *struct {
+				Name string
+			} `key:"name"`
+		}{}
+		u := NewUnmarshaler(defaultKeyName)
+		assert.Error(t, u.Unmarshal(map[string]any{
+			"name": "hello",
+		}, &v))
+	})
+
+	t.Run("not with json key", func(t *testing.T) {
+		v := struct {
+			Foo *mockUnmarshaler `json:"name"`
+		}{}
+		u := NewUnmarshaler(defaultKeyName)
+		// with different key, ignore
+		assert.NoError(t, u.Unmarshal(map[string]any{
+			"name": "hello",
+		}, &v))
+		assert.Nil(t, v.Foo)
+	})
 }
 
 func BenchmarkDefaultValue(b *testing.B) {