Taking a JSON string, unmarshaling it into a map[string]interface{}, editing, and marshaling it into a []byte seems more complicated than it should be

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

Taking a JSON string, unmarshaling it into a map[string]interface{}, editing, and marshaling it into a []byte seems more complicated than it should be

问题

我正在进行非常基本的JSON操作,以学习一些Go语言,它能正常工作,但有一件事似乎不对,我不得不写很多.(map[string]interface{}).([]interface{})来访问JSON中的条目,特别是如果它们是子级的子级的子级等等。

在这里可以看到(也可以在Go Playground上查看:https://play.golang.org/p/Wd-pzHqTsU):

package main

import (
	"fmt"
	"encoding/json"
)

func main() {
	JSON := []byte(`{"key1":"val1","key2":{"c1key1":"c1val1"},"key3":[{"c2key1":{"c3key1":"c3val1"}}]}`)
	fmt.Printf("%s\n", JSON)
	var d map[string]interface{}
	json.Unmarshal(JSON, &d)
	fmt.Println(d["key3"].([]interface{})[0].(map[string]interface{})["c2key1"].(map[string]interface{})["c3key1"])
	d["key3"].([]interface{})[0].(map[string]interface{})["c2key1"].(map[string]interface{})["c3key1"] = "change1"
	fmt.Println(d["key3"].([]interface{})[0].(map[string]interface{})["c2key1"].(map[string]interface{})["c3key1"])
	JSON, _ = json.Marshal(d)
	fmt.Printf("%s\n", JSON)
}

运行结果如下:

{"key1":"val1","key2":{"c1key1":"c1val1"},"key3":[{"c2key1":{"c3key1":"c3val1"}}]}
c3val1
change1
{"key1":"val1","key2":{"c1key1":"c1val1"},"key3":[{"c2key1":{"c3key1":"change1"}}]}

现在在Python中,我直接访问键/值,而不是每次都定义我要访问的类型,即不是fmt.Println(d["key3"].([]interface{})[0].(map[string]interface{})["c2key1"].(map[string]interface{})["c3key1"]),而是print d["key3"][0]["c2key1"]["c3key1"]

Python示例:

import json

JSON = '{"key3": [{"c2key1": {"c3key1": "c3val1"}}], "key2": {"c1key1": "c1val1"}, "key1": "val1"}'
print JSON
d = json.loads(JSON)
print d["key3"][0]["c2key1"]["c3key1"]
d["key3"][0]["c2key1"]["c3key1"] = "change1"
print d["key3"][0]["c2key1"]["c3key1"]
JSON = json.dumps(d)
print JSON

所以我在Go中做得对吗?如果是这样,这种设计的原因是什么?如果不是,我应该如何做?

英文:

I'm doing very basic JSON manipulation to learn some Go, and it works, except one thing seems off, I have to write allot of .(map[string]interface{}) and .([]interface{}) to access entries in the JSON, especially if they are children of children of children, etc.

See here (also on Go Playground: https://play.golang.org/p/Wd-pzHqTsU):

package main

import (
	"fmt"
	"encoding/json"
)

func main() {
	JSON := []byte(`{"key1":"val1","key2":{"c1key1":"c1val1"},"key3":[{"c2key1":{"c3key1":"c3val1"}}]}`)
	fmt.Printf("%s\n", JSON)
	var d map[string]interface{}
	json.Unmarshal(JSON, &d)
	fmt.Println(d["key3"].([]interface{})[0].(map[string]interface{})["c2key1"].(map[string]interface{})["c3key1"])
	d["key3"].([]interface{})[0].(map[string]interface{})["c2key1"].(map[string]interface{})["c3key1"] = "change1"
	fmt.Println(d["key3"].([]interface{})[0].(map[string]interface{})["c2key1"].(map[string]interface{})["c3key1"])
	JSON, _ = json.Marshal(d)
	fmt.Printf("%s\n", JSON)
}

Which returns:

{"key1":"val1","key2":{"c1key1":"c1val1"},"key3":[{"c2key1":{"c3key1":"c3val1"}}]}
c3val1
change1
{"key1":"val1","key2":{"c1key1":"c1val1"},"key3":[{"c2key1":{"c3key1":"change1"}}]}

Now in Python I just access key/values directly instead of defining the type of what I'm accessing every time, that is instead of fmt.Println(d["key3"].([]interface{})[0].(map[string]interface{})["c2key1"].(map[string]interface{})["c3key1"]) you do print d["key3"][0]["c2key1"]["c3key1"]

Python example:

import json

JSON = '{"key3": [{"c2key1": {"c3key1": "c3val1"}}], "key2": {"c1key1": "c1val1"}, "key1": "val1"}'
print JSON
d = json.loads(JSON)
print d["key3"][0]["c2key1"]["c3key1"]
d["key3"][0]["c2key1"]["c3key1"] = "change1"
print d["key3"][0]["c2key1"]["c3key1"]
JSON = json.dumps(d)
print JSON

So am I doing this right in Go? And if so, what's the reason for this design? Or if not, how should I do it?

答案1

得分: 17

**前言:**我优化并改进了下面的解决方案,并将其发布为一个库,可以在这里找到:github.com/icza/dyno


最清晰的方法是创建预定义类型(结构体 struct),以模拟你的 JSON,并将其解组为该类型的值,你可以使用选择器(用于 struct 类型)和索引表达式(用于映射和切片)来简单地引用元素。

然而,如果你的输入不是预定义的结构,我建议你使用以下两个辅助函数:get()set()。第一个函数通过指定的任意路径(字符串映射键和/或整数切片索引的列表)访问(返回)一个任意元素,第二个函数更改(设置)由任意路径指定的值(这些辅助函数的实现在答案的末尾)。

你只需要在你的项目/应用中包含这两个函数一次。

现在,使用这些辅助函数,你想要完成的任务就变得非常简单(就像 Python 的解决方案一样):

fmt.Println(get(d, "key3", 0, "c2key1", "c3key1"))
set("NEWVALUE", d, "key3", 0, "c2key1", "c3key1")
fmt.Println(get(d, "key3", 0, "c2key1", "c3key1"))

输出:

change1
NEWVALUE

Go Playground上尝试修改后的应用程序。

注意 - 进一步简化:

你甚至可以将路径保存在一个变量中,并重复使用它以进一步简化上面的代码:

path := []interface{}{"key3", 0, "c2key1", "c3key1"}

fmt.Println(get(d, path...))
set("NEWVALUE", d, path...)
fmt.Println(get(d, path...))

下面是 get()set() 的实现。注意:省略了检查路径是否有效的部分。这个实现使用了类型开关

func get(m interface{}, path ...interface{}) interface{} {
    for _, p := range path {
        switch idx := p.(type) {
        case string:
            m = m.(map[string]interface{})[idx]
        case int:
            m = m.([]interface{})[idx]
        }
    }
    return m
}

func set(v interface{}, m interface{}, path ...interface{}) {
    for i, p := range path {
        last := i == len(path)-1
        switch idx := p.(type) {
        case string:
            if last {
                m.(map[string]interface{})[idx] = v
            } else {
                m = m.(map[string]interface{})[idx]
            }
        case int:
            if last {
                m.([]interface{})[idx] = v
            } else {
                m = m.([]interface{})[idx]
            }
        }
    }
}
英文:

Foreword: I optimized and improved the below solution, and released it as a library here: github.com/icza/dyno.


The cleanest way would be to create predefined types (structures struct) that model your JSON, and unmarshal to a value of that type, and you can simply refer to elements using Selectors (for struct types) and Index expressions (for maps and slices).

However if your input is not of a predefined structure, I suggest you the following 2 helper functions: get() and set(). The first one accesses (returns) an arbitrary element specified by an arbitrary path (list of string map keys and/or int slice indices), the second changes (sets) the value specified by an arbitrary path (implementations of these helper functions are at the end of the answer).

You only have to include these 2 functions once in your project/app.

And now using these helpers, the tasks you want to do becomes this simple (just like the python solution):

fmt.Println(get(d, "key3", 0, "c2key1", "c3key1"))
set("NEWVALUE", d, "key3", 0, "c2key1", "c3key1")
fmt.Println(get(d, "key3", 0, "c2key1", "c3key1"))

Output:

change1
NEWVALUE

Try your modified app on the Go Playground.

Note - Further Simplification:

You can even save the path in a variable and reuse it to simplify the above code further:

path := []interface{}{"key3", 0, "c2key1", "c3key1"}

fmt.Println(get(d, path...))
set("NEWVALUE", d, path...)
fmt.Println(get(d, path...))

And the implementations of get() and set() are below. Note: checks whether the path is valid is omitted. This implementation uses Type switches:

func get(m interface{}, path ...interface{}) interface{} {
	for _, p := range path {
		switch idx := p.(type) {
		case string:
			m = m.(map[string]interface{})[idx]
		case int:
			m = m.([]interface{})[idx]
		}
	}
	return m
}

func set(v interface{}, m interface{}, path ...interface{}) {
	for i, p := range path {
		last := i == len(path)-1
		switch idx := p.(type) {
		case string:
			if last {
				m.(map[string]interface{})[idx] = v
			} else {
				m = m.(map[string]interface{})[idx]
			}
		case int:
			if last {
				m.([]interface{})[idx] = v
			} else {
				m = m.([]interface{})[idx]
			}
		}
	}
}

答案2

得分: 3

不,这不是在Go中处理结构化JSON数据的最正确的方法。相反,最好创建一个“结构层次结构”,并将JSON解组为结构体。例如:

type Data struct {
    Key1 string
    Key2 struct {
        C1Key1 string
    }
    Key3 []Key3
}

type Key3 struct {
    C2Key1 struct {
        C3Key1 string
    }
}

这种方法:

  • 让您对数据的(解)组织方式有更多控制(通过结构标签和json.Unmarshalerjson.Marshaler接口)
  • 摆脱了类型断言
  • 相反,提供更多类型安全性
  • 性能更好,因为与映射访问相比,结构体访问基本上是免费的。

Playground: https://play.golang.org/p/9XIh8DX1Ms.

英文:

No, this is not the most correct way to handle structured JSON data in Go. Instead, it is better to create a "struct hierarchy" and unmarshal your JSON into structs. E.g.

type Data struct {
    Key1 string
    Key2 struct {
        C1Key1 string
    }
    Key3 []Key3
}

type Key3 struct {
    C2Key1 struct {
        C3Key1 string
    }
}

This approach:

  • gives you more control over how your data will be (un)marshaled (via struct tags and json.Unmarshaler and json.Marshaler interfaces)
  • gets you rid of type assertions
  • instead, gives you more type safety
  • has better performance, since struct access is basically free compared to map access.

Playground: https://play.golang.org/p/9XIh8DX1Ms.

答案3

得分: 0

这是一个完整的示例,使用了一个struct并进行了编辑:

package main

import (
   "encoding/json"
   "os"
)

const s = `
{
   "key1": "val1",
   "key2": {"c1key1": "c1val1"},
   "key3": [{"c2key1": {"c3key1": "c3val1"}}]
}

func main() {
   var d struct {
      Key1 string
      Key2 struct { C1Key1 string }
      Key3 []struct {
         C2Key1 struct { C3Key1 string }
      }
   }
   json.Unmarshal([]byte(s), &d)
   d.Key3[0].C2Key1.C3Key1 = "change1"
   b, e := json.Marshal(d)
   if e != nil {
      panic(e)
   }
   os.Stdout.Write(b)
}
英文:

Here is a full example, using a struct and with editing:

package main

import (
   "encoding/json"
   "os"
)

const s = `
{
   "key1": "val1",
   "key2": {"c1key1": "c1val1"},
   "key3": [{"c2key1": {"c3key1": "c3val1"}}]
}
`

func main() {
   var d struct {
      Key1 string
      Key2 struct { C1Key1 string }
      Key3 []struct {
         C2Key1 struct { C3Key1 string }
      }
   }
   json.Unmarshal([]byte(s), &d)
   d.Key3[0].C2Key1.C3Key1 = "change1"
   b, e := json.Marshal(d)
   if e != nil {
      panic(e)
   }
   os.Stdout.Write(b)
}

huangapple
  • 本文由 发表于 2015年3月5日 20:05:54
  • 转载请务必保留本文链接:https://go.coder-hub.com/28877512.html
匿名

发表评论

匿名网友

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

确定