英文:
Proper json unmarshaling in Go with the empty interface
问题
我目前正在学习golang,并且(可能和之前的许多人一样)我正在努力理解空接口。
作为练习,我正在阅读由Postman生成的一个大型JSON文件,并尝试访问其中的一个字段(而不是所有可用的字段)。
以下是一个简单的JSON表示,去除了我不想读取的不必要字段(但它们仍然存在):
{
"results": [
{
"times": [
1,
2,
3,
4
]
}
]
}
由于JSON对象很大,我选择不使用自定义结构进行解组,而是决定使用空接口interface{}
。
经过一段时间,我设法编写了一些可行的代码,但我相当确定这不是正确的方法。
byteValue, _ := ioutil.ReadAll(jsonFile)
var result map[string]interface{}
err = json.Unmarshal(byteValue, &result)
if err != nil {
log.Fatalln(err)
}
// 特别丑陋
r := result["results"].([]interface{})
r1 := r[0].(map[string]interface{})
r2 := r1["times"].([]interface{})
times := make([]float64, len(r2))
for i := range r2 {
times[i] = r2[i].(float64)
}
有没有更好的方法在我的JSON对象中导航,而不必每次深入对象时都实例化新变量?
英文:
I'm currently learning golang and (probably as many others before me) I'm trying to properly understand the empty interface.
As an exercise, I'm reading a big json file produced by Postman and trying to access just one field (out of the many available).
Here is a simple representation of the json without the unnecessary fields I don't want to read (but that are still there):
{
"results": [
{
"times": [
1,
2,
3,
4
]
}
]
}
Since the json object is big, I opted out of unmarshaling it with a custom struct, and rather decided to use the empty interface interface{}
After some time, I managed to get some working code, but I'm quite sure this isn't the correct way of doing it.
byteValue, _ := ioutil.ReadAll(jsonFile)
var result map[string]interface{}
err = json.Unmarshal(byteValue, &result)
if err != nil {
log.Fatalln(err)
}
// ESPECIALLY UGLY
r := result["results"].([]interface{})
r1 := r[0].(map[string]interface{})
r2 := r1["times"].([]interface{})
times := make([]float64, len(r2))
for i := range r2 {
times[i] = r2[i].(float64)
}
Is there a better way to navigate through my json object without having to instantiate new variables every time i move deeper and deeper into the object?
答案1
得分: 4
- 即使JSON很大,你只需要定义你真正关心的字段。
- 只有在键不是有效的Go标识符时(在这种情况下键是标识符),你才需要使用JSON标签,即使在这种情况下,有时候可以通过使用
map[string]something
来避免使用JSON标签。 - 除非你需要将子结构体用于某些函数或其他用途,否则你不需要定义它们。
- 除非你需要重用该类型,否则你甚至不需要定义它,你可以在声明时直接定义结构体。
示例:
package main
import (
"encoding/json"
"fmt"
)
const s = `
{
"results": [
{
"times": [1, 2, 3, 4]
}
]
}
func main() {
var t struct {
Results []struct {
Times []int
}
}
json.Unmarshal([]byte(s), &t)
fmt.Printf("%+v\n", t) // {Results:[{Times:[1 2 3 4]}]}
}
英文:
- Even if the JSON is large, you only have to define the fields you actually care about
- You only need to use JSON tags if the keys aren't valid Go
identifiers (keys are idents in this case), even then you can sometimes avoid it by using amap[string]something
- Unless you need the sub
struct
s for some function or whatnot, you don't need to define them - Unless you need to reuse the type, you don't even have to define that, you can just define the struct at declaration time
Example:
package main
import (
"encoding/json"
"fmt"
)
const s = `
{
"results": [
{
"times": [1, 2, 3, 4]
}
]
}
`
func main() {
var t struct {
Results []struct {
Times []int
}
}
json.Unmarshal([]byte(s), &t)
fmt.Printf("%+v\n", t) // {Results:[{Times:[1 2 3 4]}]}
}
答案2
得分: 2
> [...]尝试访问一个字段(而不是许多可用的字段)。
对于这个具体的用例,我会使用一个库来查询和访问已知路径中的单个值,比如:
https://github.com/jmespath/go-jmespath
另一方面,如果你正在练习如何访问JSON中的嵌套值,我建议你尝试编写一个递归函数,以相同的方式(但更简单)跟随未知结构中的路径,就像go-jmespath一样。
好的,我接受了挑战,并花了一个小时编写了这个代码。它可以工作。不确定性能或错误,而且它的功能非常有限
https://play.golang.org/p/dlIsmG6Lk-p
package main
import (
"encoding/json"
"errors"
"fmt"
"strings"
)
func main() {
// 我只是添加了一些更多的数据到结构中,以便能够测试不同的路径
fileContent := []byte(`
{"results": [
{"times": [
1,
2,
3,
4
]},
{"times2": [
5,
6,
7,
8
]},
{"username": "rosadabril"},
{"age": 42},
{"location": [41.5933262, 1.8376757]}
],
"more_results": {
"nested_1": {
"nested_2":{
"foo": "bar"
}
}
}
}`)
var content map[string]interface{}
if err := json.Unmarshal(fileContent, &content); err != nil {
panic(err)
}
// 一些要测试的路径
valuePaths := []string{
"results.times",
"results.times2",
"results.username",
"results.age",
"results.doesnotexist",
"more_results.nested_1.nested_2.foo",
}
for _, p := range valuePaths {
breadcrumbs := strings.Split(p, ".")
value, err := search(breadcrumbs, content)
if err != nil {
fmt.Printf("\n在'%s'中搜索时出错:%s\n", p, err)
continue
}
fmt.Printf("\n在%s中找到一个值\n", p)
fmt.Printf("类型:%T\n值:%#v\n", value, value)
}
}
// search是我们神奇的递归函数!这个函数的思路是以一种非常基本的方式在结构中搜索,对于复杂的查询,请使用jmespath
func search(breadcrumbs []string, content map[string]interface{}) (interface{}, error) {
// 我们永远不应该到达这个点,但是安全起见,我们可能会遇到一个超出范围的错误(检查第82行)
if len(breadcrumbs) == 0 {
return nil, errors.New("面包屑用完了 :'(")
}
// 表示我们是否到达了旅程的终点,应该返回值而不进行更多的检查
lastBreadcrumb := len(breadcrumbs) == 1
// 当前面包屑始终是第一个元素。
currentBreadcrumb := breadcrumbs[0]
if value, found := content[currentBreadcrumb]; found {
if lastBreadcrumb {
return value, nil
}
// 如果值是map[string]interface{},则继续递归下去
if aMap, isAMap := value.(map[string]interface{}); isAMap {
// 我们调用自己,弹出第一个面包屑,并传递当前的map
return search(breadcrumbs[1:], aMap)
}
// 如果它是一个接口数组,事情就变得复杂了 :(
if anArray, isArray := value.([]interface{}); isArray {
for _, something := range anArray {
if aMap, isAMap := something.(map[string]interface{}); isAMap && len(breadcrumbs) > 1 {
if v, err := search(breadcrumbs[1:], aMap); err == nil {
return v, nil
}
}
}
}
}
return nil, errors.New("糟糕,这里什么都没有")
}
希望这可以帮助到你!
英文:
> [...] trying to access just one field (out of the many available).
For this concrete use case I would use a library to query and access to a single value in a known path like:
https://github.com/jmespath/go-jmespath
In the other hand, if you're practicing how to access nested values in a JSON, I would recommend you to give a try to write a recursive function that follows a path in an unknown structure the same way (but simple) like go-jmespath does.
Ok, I challenged myself and spent an hour writing this. It works. Not sure about performance or bugs and it's really limited
https://play.golang.org/p/dlIsmG6Lk-p
package main
import (
"encoding/json"
"errors"
"fmt"
"strings"
)
func main() {
// I Just added a bit more of data to the structure to be able to test different paths
fileContent := []byte(`
{"results": [
{"times": [
1,
2,
3,
4
]},
{"times2": [
5,
6,
7,
8
]},
{"username": "rosadabril"},
{"age": 42},
{"location": [41.5933262, 1.8376757]}
],
"more_results": {
"nested_1": {
"nested_2":{
"foo": "bar"
}
}
}
}`)
var content map[string]interface{}
if err := json.Unmarshal(fileContent, &content); err != nil {
panic(err)
}
// some paths to test
valuePaths := []string{
"results.times",
"results.times2",
"results.username",
"results.age",
"results.doesnotexist",
"more_results.nested_1.nested_2.foo",
}
for _, p := range valuePaths {
breadcrumbs := strings.Split(p, ".")
value, err := search(breadcrumbs, content)
if err != nil {
fmt.Printf("\nerror searching '%s': %s\n", p, err)
continue
}
fmt.Printf("\nFOUND A VALUE IN: %s\n", p)
fmt.Printf("Type: %T\nValue: %#v\n", value, value)
}
}
// search is our fantastic recursive function! The idea is to search in the structure in a very basic way, for complex querying use jmespath
func search(breadcrumbs []string, content map[string]interface{}) (interface{}, error) {
// we should never hit this point, but better safe than sorry and we could incurr in an out of range error (check line 82)
if len(breadcrumbs) == 0 {
return nil, errors.New("ran out of breadcrumbs :'(")
}
// flag that indicates if we are at the end of our trip and whe should return the value without more checks
lastBreadcrumb := len(breadcrumbs) == 1
// current breadcrumb is always the first element.
currentBreadcrumb := breadcrumbs[0]
if value, found := content[currentBreadcrumb]; found {
if lastBreadcrumb {
return value, nil
}
// if the value is a map[string]interface{}, go down the rabbit hole, recursion!
if aMap, isAMap := value.(map[string]interface{}); isAMap {
// we are calling ourselves popping the first breadcrumb and passing the current map
return search(breadcrumbs[1:], aMap)
}
// if it's an array of interfaces the thing gets complicated :(
if anArray, isArray := value.([]interface{}); isArray {
for _, something := range anArray {
if aMap, isAMap := something.(map[string]interface{}); isAMap && len(breadcrumbs) > 1 {
if v, err := search(breadcrumbs[1:], aMap); err == nil {
return v, nil
}
}
}
}
}
return nil, errors.New("woops, nothing here")
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论