通过反射遍历Go结构体字段与map[string]interface{}不匹配的情况

huangapple go评论97阅读模式
英文:

Walking through go struct fields with reflect doesn't match case map[string]interface{}

问题

我有一个不寻常的任务:

  1. 将 JSON 消息解析为 Go 结构体。
  2. 验证 JSON 中的所有字段是否在特定限制范围内:
    • 字符串字段长度不再是固定常量。
    • 映射中的元素数量不超过固定数目。
    • 如果映射键的值是嵌套结构体,则验证以上两个规则。

为了做到这一点,我使用反射(reflect),然后迭代元素,并进行类型检查:

  • 如果是 int 或 float,无需进行验证。
  • 如果是字符串,验证长度(如果失败则返回)。
  • 如果是映射,验证映射长度(如果失败则返回),然后迭代映射值并递归检查它们的字段是否违反了字符串/映射规则。
  • 默认情况下(我假设这是嵌套的结构化 JSON 结构),将其转换为接口切片并进行递归调用。

问题是:
在 JSON 中,我可能会有不同的映射值类型,例如:

  • map[string]MyStruct1
  • map[string]MyStruct2
  • 等等。

所以当我进行类型检查时,我写了:
case map[string]interface{}

但在我的程序中,这个 case 从未匹配到,而是进入了默认的 case,导致一些错误。

有可能匹配到 case - map[string]interface{} 的方法吗?

以下是我参考的代码:
http://play.golang.org/p/IVXHLBRuPK

func validate(vals []interface{}) bool {
	result := true
	for _, elem := range vals {
		switch v := elem.(type) {
		case int, float64:
			fmt.Println("Got int or float:", v)
		case string:
			fmt.Println("Got string", v)
			if len(elem.(string)) > 5 {
				fmt.Println("String rule Violation!")
				result = false
				break
				fmt.Println("After Break")
			}
		case map[string]interface{}:
			fmt.Println("Got map", v)
			if len(elem.(map[string]interface{})) > 1 || !validate(elem.([]interface{})) {
				fmt.Println("Map length rule Violation!")
				result = false
				break
			}
		default:
			fmt.Println("Got struct:", v)

			// Convert to interface list all other structures no string/int/float/map:
			new_v := reflect.ValueOf(elem)
			new_values := make([]interface{}, new_v.NumField())
			for j := 0; j < new_v.NumField(); j++ {
				new_values[j] = new_v.Field(j).Interface()
			}

			// Recursively call for validate nested structs
			if !validate(new_values) {
				result = false
				break
			}
		}
	}
	fmt.Println("After Break 2")
	return result
}

func main() {
	// Test struct:
	x := C{1, B{"abc", A{10, 0.1, map[string]Host{"1,2,3,4": Host{"1.2.3.4"}}}}}

	// Conversion:
	v := reflect.ValueOf(x)
	values := make([]interface{}, v.NumField())
	for i := 0; i < v.NumField(); i++ {
		values[i] = v.Field(i).Interface()
	}

	// Validate function verification
	fmt.Println(validate(values))
}

在这个例子中,我永远无法匹配到 case: map[string]interface{}。

非常感谢您提供的有用建议!

英文:

I have unusual task:

  1. parse json message to Go struct
  2. verify that all fields in JSON are within specific limits:
  • string fields length no longer fixed constant
  • maps contain no more than fixed number elements
  • if values of map keys are nested structs verify for above 2 rules

To do this I use reflect, then iterating over elements,
and doing type checking:

  • if int or float - nothing to do - no verification
  • if string - verify length (and return if failed)
  • if map verify map length (and return if failed), then iterate over map values and recursively check if their fields violate string/map rules
  • default (I assume that this is struct nested JSON structure): convert it to interface slice and do recursive call.

Problem:
In JSON, I would have different map value types like:

  • map[string]MyStruct1
  • map[string]MyStruct2
  • etc.

So when I'm doing type checking I write:
case map[string]interface{}

But in my program this case is never matched and goes to case default,
causing some error.

Any possible way to match type with case - map[string]interface{} ????

Here is my code for reference:
http://play.golang.org/p/IVXHLBRuPK

func validate(vals []interface{}) bool {
result := true
for _, elem := range vals {
switch v := elem.(type) {
case int, float64:
fmt.Println(&quot;Got int or float: &quot;, v)
case string:
fmt.Println(&quot;Got string&quot;, v)
if len(elem.(string)) &gt; 5 {
fmt.Println(&quot;String rule Violation!&quot;)
result = false
break
fmt.Println(&quot;After Break&quot;)
}
case map[string]interface{}:
fmt.Println(&quot;Got map&quot;, v)
if len(elem.(map[string]interface{})) &gt; 1 || !validate(elem.([]interface{})) {
fmt.Println(&quot;Map length rule Violation!&quot;)
result = false
break
}
default:
fmt.Println(&quot;Got struct:&quot;, v)
// Convert to interface list all other structures no string/int/float/map:
new_v := reflect.ValueOf(elem)
new_values := make([]interface{}, new_v.NumField())
for j := 0; j &lt; new_v.NumField(); j++ {
new_values[j] = new_v.Field(j).Interface()
}
// Recursively call for validate nested structs
if !validate(new_values) {
result = false
break
}
}
}
fmt.Println(&quot;After Break 2&quot;)
return result
}
func main() {
// Test truct:
x := C{1, B{&quot;abc&quot;, A{10, 0.1, map[string]Host{&quot;1,2,3,4&quot;: Host{&quot;1.2.3.4&quot;}}}}}
// Conversion:
v := reflect.ValueOf(x)
values := make([]interface{}, v.NumField())
for i := 0; i &lt; v.NumField(); i++ {
values[i] = v.Field(i).Interface()
}
// Validate function verification
fmt.Println(validate(values))
}

In this example I can't ever reach case: map[string]interface{}

Big kudos on helpful suggestions!

答案1

得分: 2

问题是 case map[string]interface{} 无法匹配 map[string]Host,因此它将被解析为结构体,而实际上它不是。

你可以通过检查 new_v.Kind() 并使用反射处理映射,或者为 map[string]Host 添加一个特殊情况来解决这个问题。

英文:

The problem is case map[string]interface{} won't match map[string]Host so it will get parsed as a struct, which it isn't.

You will either have to check new_v.Kind() and handle maps via reflection or add a special case for map[string]Host.

huangapple
  • 本文由 发表于 2015年11月27日 06:38:38
  • 转载请务必保留本文链接:https://go.coder-hub.com/33947944.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定