使用空接口在 Go 中正确 json 解组
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)
}
// 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)
}
是否有更好的方法来浏览我的 json 对象,而无需在每次深入对象时都实例化新变量?
- 即使JSON很大,你只需要定义你真正关心的字段
- 如果密钥无效,您只需要使用 JSON 标签 Go
标识符(在这种情况下键是标识),即便如此,有时您也可以通过使用
map[string]something
来避免它
- 除非您需要子
struct
用于某些功能或诸如此类的东西,否则您不需要定义它们
- 除非你需要重用类型,你甚至不必定义它,你可以在声明时定义结构
示例:
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]}]}
}
[...] trying to access just one field (out of the many available).
对于这个具体的用例,我会使用一个库来查询和访问已知路径中的单个值,例如:
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() {
// 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")
}
我目前正在学习 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)
}
// 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)
}
是否有更好的方法来浏览我的 json 对象,而无需在每次深入对象时都实例化新变量?
- 即使JSON很大,你只需要定义你真正关心的字段
- 如果密钥无效,您只需要使用 JSON 标签 Go
标识符(在这种情况下键是标识),即便如此,有时您也可以通过使用
map[string]something
来避免它
- 除非您需要子
struct
用于某些功能或诸如此类的东西,否则您不需要定义它们 - 除非你需要重用类型,你甚至不必定义它,你可以在声明时定义结构
示例:
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]}]}
}
[...] trying to access just one field (out of the many available).
对于这个具体的用例,我会使用一个库来查询和访问已知路径中的单个值,例如:
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() {
// 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")
}