Repository: go-playground/validator
Pull request #917 (2022-03-19 09:01:39)

Enhanced ValidationCtx method to support nested map in slice #915
## Fixes Or Enhances
Enhanced ValidationCtx method to support nested map in slice https://github.com/go-playground/validator/issues/915

**Make sure that you've checked the boxes below before you submit PR:**
- [x] Tests exist or have been written that cover this particular change.

@go-playground/validator-maintainers

coveralls: 
[![Coverage Status](https://coveralls.io/builds/47511255/badge)](https://coveralls.io/builds/47511255)

Coverage increased (+0.1%) to 75.279% when pulling **1cc134570875182cdc964f3f9b577aef143d4418 on leftjs:master_enhanced** into **58d5778b183e89cc374ca4ebbf06da1eed088a63 on go-playground:master**.


Patch:
diff --git a/_examples/map-validation/main.go b/_examples/map-validation/main.go
index 92e282f..dfcfa4f 100644
--- a/_examples/map-validation/main.go
+++ b/_examples/map-validation/main.go
@@ -48,6 +48,16 @@ func validateNestedMap() {
 				"mother_name": "Hannah",
 			},
 			"salary": "1000",
+			"phones": []map[string]interface{}{
+				{
+					"number": "11-111-1111",
+					"remark": "home",
+				},
+				{
+					"number": "22-222-2222",
+					"remark": "work",
+				},
+			},
 		},
 	}
 
@@ -62,6 +72,10 @@ func validateNestedMap() {
 				"mother_name": "required,min=4,max=32",
 			},
 			"salary": "number",
+			"phones": map[string]interface{}{
+				"number": "required,min=4,max=32",
+				"remark": "required,min=1,max=32",
+			},
 		},
 	}
 
diff --git a/validator_instance.go b/validator_instance.go
index 6b67548..7324bba 100644
--- a/validator_instance.go
+++ b/validator_instance.go
@@ -154,15 +154,24 @@ func (v *Validate) SetTagName(name string) {
 func (v Validate) ValidateMapCtx(ctx context.Context, data map[string]interface{}, rules map[string]interface{}) map[string]interface{} {
 	errs := make(map[string]interface{})
 	for field, rule := range rules {
-		if reflect.ValueOf(rule).Kind() == reflect.Map && reflect.ValueOf(data[field]).Kind() == reflect.Map {
-			err := v.ValidateMapCtx(ctx, data[field].(map[string]interface{}), rule.(map[string]interface{}))
-			if len(err) > 0 {
-				errs[field] = err
+		if ruleObj, ok := rule.(map[string]interface{}); ok {
+			if dataObj, ok := data[field].(map[string]interface{}); ok {
+				err := v.ValidateMapCtx(ctx, dataObj, ruleObj)
+				if len(err) > 0 {
+					errs[field] = err
+				}
+			} else if dataObjs, ok := data[field].([]map[string]interface{}); ok {
+				for _, obj := range dataObjs {
+					err := v.ValidateMapCtx(ctx, obj, ruleObj)
+					if len(err) > 0 {
+						errs[field] = err
+					}
+				}
+			} else {
+				errs[field] = errors.New("The field: '" + field + "' is not a map to dive")
 			}
-		} else if reflect.ValueOf(rule).Kind() == reflect.Map {
-			errs[field] = errors.New("The field: '" + field + "' is not a map to dive")
-		} else {
-			err := v.VarCtx(ctx, data[field], rule.(string))
+		} else if ruleStr, ok := rule.(string); ok {
+			err := v.VarCtx(ctx, data[field], ruleStr)
 			if err != nil {
 				errs[field] = err
 			}


Test patch:
diff --git a/validator_test.go b/validator_test.go
index 933626f..1ebdea5 100644
--- a/validator_test.go
+++ b/validator_test.go
@@ -12134,6 +12134,97 @@ func TestPostCodeByIso3166Alpha2Field_InvalidKind(t *testing.T) {
 	t.Errorf("Didn't panic as expected")
 }
 
+func TestValidate_ValidateMapCtx(t *testing.T) {
+
+	type args struct {
+		data  map[string]interface{}
+		rules map[string]interface{}
+	}
+	tests := []struct {
+		name string
+		args args
+		want int
+	}{
+		{
+			name: "test nested map in slice",
+			args: args{
+				data: map[string]interface{}{
+					"Test_A": map[string]interface{}{
+						"Test_B": "Test_B",
+						"Test_C": []map[string]interface{}{
+							{
+								"Test_D": "Test_D",
+							},
+						},
+						"Test_E": map[string]interface{}{
+							"Test_F": "Test_F",
+						},
+					},
+				},
+				rules: map[string]interface{}{
+					"Test_A": map[string]interface{}{
+						"Test_B": "min=2",
+						"Test_C": map[string]interface{}{
+							"Test_D": "min=2",
+						},
+						"Test_E": map[string]interface{}{
+							"Test_F": "min=2",
+						},
+					},
+				},
+			},
+			want: 0,
+		},
+
+		{
+			name: "test nested map error",
+			args: args{
+				data: map[string]interface{}{
+					"Test_A": map[string]interface{}{
+						"Test_B": "Test_B",
+						"Test_C": []interface{}{"Test_D"},
+						"Test_E": map[string]interface{}{
+							"Test_F": "Test_F",
+						},
+						"Test_G": "Test_G",
+						"Test_I": []map[string]interface{}{
+							{
+								"Test_J": "Test_J",
+							},
+						},
+					},
+				},
+				rules: map[string]interface{}{
+					"Test_A": map[string]interface{}{
+						"Test_B": "min=2",
+						"Test_C": map[string]interface{}{
+							"Test_D": "min=2",
+						},
+						"Test_E": map[string]interface{}{
+							"Test_F": "min=100",
+						},
+						"Test_G": map[string]interface{}{
+							"Test_H": "min=2",
+						},
+						"Test_I": map[string]interface{}{
+							"Test_J": "min=100",
+						},
+					},
+				},
+			},
+			want: 1,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			validate := New()
+			if got := validate.ValidateMapCtx(context.Background(), tt.args.data, tt.args.rules); len(got) != tt.want {
+				t.Errorf("ValidateMapCtx() = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}
+
 func TestCreditCardFormatValidation(t *testing.T) {
 	tests := []struct {
 		value    string `validate:"credit_card"`