How can I access a field in JSON data from an empty interface{} type?

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

How can I access a field in JSON data from an empty interface{} type?

问题

我是你的中文翻译助手,以下是翻译好的内容:

我刚开始学习Go语言,我知道这可能是一个简单的问题,但我在访问空接口中的JSON值时遇到了问题。数据来自Twilio Go Helper库(https://pkg.go.dev/github.com/twilio/twilio-go@v1.0.0/rest/lookups/v2#LookupsV2PhoneNumber.LineTypeIntelligence),我正在尝试访问对象中的一个值:

LineTypeIntelligence *interface{} `json:"line_type_intelligence,omitempty"`

如果我打印这个对象,它会显示为一个map:

fmt.Println(*resp.LineTypeIntelligence)

输出结果为:

map[carrier_name:Verizon Wireless error_code:<nil> mobile_country_code:311 mobile_network_code:489 type:mobile]

我尝试直接访问map中的一个值:

carrier_name := resp.LineTypeIntelligence["carrier_name"].(string)

不幸的是,这会生成一个编译器错误:

invalid operation: cannot index resp.LineTypeIntelligence (variable of type *interface{})

英文:

I am new to Go and I know this is probably a simple answer but I am getting tripped up accessing a JSON value in an empty interface. The data is coming from a Twilio Go Helper library (https://pkg.go.dev/github.com/twilio/twilio-go@v1.0.0/rest/lookups/v2#LookupsV2PhoneNumber.LineTypeIntelligence) and I am trying to access a value in the object
LineTypeIntelligence *interface{} `json:&quot;line_type_intelligence,omitempty&quot;`

If I print the object it shows as a map
fmt.Println(*resp.LineTypeIntelligence)
which
outputs
map[carrier_name:Verizon Wireless error_code:&lt;nil&gt; mobile_country_code:311 mobile_network_code:489 type:mobile]

I tried to directly access a value in the map using
carrier_name := resp.LineTypeIntelligence[&quot;carrier_name&quot;].(string)

This unfortunately generates a compiler error:
invalid operation: cannot index resp.LineTypeIntelligence (variable of type *interface{})

答案1

得分: 1

就这个问题而言:

carrier_name := resp.LineTypeIntelligence["carrier_name"].(string)

这样是行不通的,因为resp.LineTypeIntelligence的类型是*interface{}(或简写为*any)。你不能像访问map一样访问它。类型interface{}只是表示分配给该字段的任何值都实现了一个空接口(也就是说:任何值都实现了一个没有方法的接口,所以该值可以是任何类型)。

要访问数据本身,你需要进行一些类型断言/转换,并将值复制到一个更容易使用的map中:

LTIMap, ok := *resp.LineTypeIntelligence.(map[string]any)
if !ok {
    // return errors.Errorf("expected LineTypeIntelligence to be a map, instead saw %T (%#v)", *resp.LineTypeIntelligence, *resp.LineTypeIntelligence)
    panic("处理错误,因为我们期望的值是一个map,但它是其他类型")
}
// 现在 LTIMap 是 map[string]any
carrierName, ok := LTIMap["carrier_name"].(string)
if !ok {
    return errors.Ner("carrier name 不是一个字符串")
}
// 等等

这当然相当繁琐。考虑到你已经知道map将包含一个carrier_name键,为什么不将数据解组为不同的类型呢?

看一下数据,这应该可以工作:

type LTI struct {
    CarrierName string  `json:"carrier_name"`
    ErrCode     *int    `json:"error_code,omitempty"` // 或者是string,或者其他类型
    MobCC       int     `json:"mobile_country_code"` // twilio可能对这些使用string类型
    MobNC       int     `json:"mobile_network_code"`
    Type        string  `json:"type"`
}

这不是最高效的方法,但将数据快速转换为自己的对象的最快方法是将其重新编组,并在自己的类型中解组:

b, _ := json.Marshal(resp.LineTypeIntelligence)
lti := LTI{}
if err := json.Unmarshal(b, &lti); err != nil {
    // 处理错误
}
fmt.Printf("carrier name: %s\n", lti.CarrierName)

或许有些吹毛求疵,但是Golang经常因为在格式/编写代码方面“过于主观”而受到批评。然而,这种严格的格式化被证明是一件好事。因为代码格式相同,人们可以查看任何人编写的代码,并专注于“它做什么”,而不是“它是如何编写的”。

由于在项目/代码库中保持一致性的好处,除了gofmt之外,Golang存储库还有一个维基页面,涵盖了大量的其他约定:Code Review Comments

你的问题中包含了像carrier_name这样的变量。在Golang中,建议使用snake_case命名变量。

你还做了类似resp.LineTypeIntelligence["carrier_name"].(string)的操作。然而,从map中获取值不应与类型转换结合在一起。安全的map访问应该像这样:

v, ok := m["carrier_name"]
if !ok {
    // v 将是你的map值类型的零值
    // 例如 map[string]int =&gt; v == 0
    // map[string]string =&gt; v == ""
    // map[string]*int   =&gt; v == nil
    // 如果需要,你可以将v初始化为安全值,或返回错误
}
// ok 为 true,map确实包含键"carrier_name"的值
name, ok := v.(string)
if !ok {
    // 尽管map包含了所需的键,但它的值不是字符串...
}
// 类型断言成功:map包含了该键,并且值确实是字符串
英文:

As far as this goes:

carrier_name  := resp.LineTypeIntelligence[&quot;carrier_name&quot;].(string)

That won't work, as resp.LineTypeIntelligence is of type *interface{} (or *any for short). You cannot access it as though it were a map. The type interface{} simply denotes that whatever value is assigned to this field, it implements an empty interface (which is to say: everything implements an interface of 0 methods, so the value could be anything).

To access the data itself, you'll need some type assertions/casts, and copy the values into a map that is easier to use:

LTIMap, ok := *resp.LineTypeIntelligence.(map[string]any)
if !ok {
    // return errors.Errorf(&quot;expected LineTypeIntelligence to be a map, instead saw %T (%#v)&quot;, *resp.LineTypeIntelligence, *resp.LineTypeIntelligence)
    panic(&quot;handle error because we expected the value to be a map, but it&#39;s something else&quot;)
}
// now LTIMap is map[string]any
carrierName, ok := LTIMap[&quot;carrier_name&quot;].(string)
if !ok {
    return errors.Ner(&quot;carrier name not a string&quot;)
}
// and so on

This is, of course rather tedious. Considering you already know the map will contain a carrier_name key, why not unmarshal the data into a different type?

Looking at the data, this ought to work:

type LTI struct {
    CarrierName string  `json:&quot;carrier_name&quot;`
    ErrCode     *int    `json:&quot;error_code,omitempty&quot;` // or string, or whatever
    MobCC       int     `json:&quot;mobile_country_code&quot;` // twilio might use string for these
    MobNC       int     `json:&quot;mobile_network_code&quot;`
    Type        string  `json:&quot;type&quot;`
}

It's not the most efficient way to go about things, but the quickest way to get this data into your own object would be to marshal it again, and unmarshal it in your own type:

b, _ := json.Marshal(resp.LineTypeIntelligence)
lti := LTI{}
if err := json.Unmarshal(b, &amp;lti); err != nil {
    // handle error
}
fmt.Printf(&quot;carrier name: %s\n&quot;, lti.CarrierName)

Pedantry, perhaps, but golang has been often criticised for being "too opinionated" WRT how you should format/write your code. That criticism has died down because the strict formatting has proven to be a good thing. Because the code is formatted the same, people can just look at code written by anyone and focus on what it does rather than how it was written.

Because of the benefits of conformity across projects/code-bases, in addition to gofmt, the golang repo has a wiki entry covering a ton of additional conventions: Code Review Comments

Your question contains variables like carrier_name. In golang, it's recommended to snakeCase variable names.

You also do something like resp.LineTypeIntelligence[&quot;carrier_name&quot;].(string). Fetching values from a map should not be combined with a cast, however. Safe map access looks like this:

v, ok := m[&quot;carrier_name&quot;]
if !ok {
    // v will be the nil value of the value type of your map
    // ie map[string]int =&gt; v == 0
    // map[string]string =&gt; v == &quot;&quot;
    // map[string]*int   =&gt; v == nil
    // you can initialise v to a safe value if needed, or return an error
}
// ok was true, the map does indeed contain a value for the key &quot;carrier_name&quot;
name, ok := v.(string)
if !ok {
    // though the map contained the required key, it turned out not to be a string...
}
// type assertion was successful: map contained the key, and the value was indeed a string

huangapple
  • 本文由 发表于 2022年10月19日 21:57:27
  • 转载请务必保留本文链接:https://go.coder-hub.com/74126668.html
  • go
  • twilio

Go模板:如何将array[]string打印到 :?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定