dongwu5318 2015-03-05 12:05
浏览 61
已采纳

取一个JSON字符串,将其解组到map [string] interface {}中,进行编辑,然后将其编组为[] byte似乎要复杂得多,

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
", 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
", 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?

  • 写回答

2条回答 默认 最新

  • drvvvuyia15070493 2015-03-05 12:37
    关注

    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]
                }
            }
        }
    }
    
    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论
查看更多回答(1条)

报告相同问题?

悬赏问题

  • ¥15 树莓派与pix飞控通信
  • ¥15 自动转发微信群信息到另外一个微信群
  • ¥15 outlook无法配置成功
  • ¥30 这是哪个作者做的宝宝起名网站
  • ¥60 版本过低apk如何修改可以兼容新的安卓系统
  • ¥25 由IPR导致的DRIVER_POWER_STATE_FAILURE蓝屏
  • ¥50 有数据,怎么建立模型求影响全要素生产率的因素
  • ¥50 有数据,怎么用matlab求全要素生产率
  • ¥15 TI的insta-spin例程
  • ¥15 完成下列问题完成下列问题