# #go
Вопрос:
Я выполняю очень простые манипуляции с JSON, чтобы изучить некоторые из них, и это работает, за исключением того, что одна вещь кажется неправильной, я должен выделить .(map[string]interface{})
и .([]interface{})
получить доступ к записям в JSON, особенно если они являются детьми детей детей и т. Д.
Смотрите здесь (также на игровой площадке Go: 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("%sn", JSON)
var d map[string]interface{}
json.Unmarshal(JSON, amp;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("%sn", 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? И если да, то в чем причина такого дизайна? А если нет, то как мне это сделать?
Ответ №1:
Предисловие: Я оптимизировал и улучшил приведенное ниже решение и выпустил его в виде библиотеки здесь: github.com/icza/dyno
.
Самым простым способом было бы создать предопределенные типы (структуры struct
), которые моделируют ваш JSON, и не привязывать к значению этого типа, и вы можете просто ссылаться на элементы с помощью селекторов (для struct
типов) и выражений индекса (для карт и срезов).
Однако, если ваш ввод не имеет предопределенной структуры, я предлагаю вам следующие 2 вспомогательные функции: get()
и set()
. Первый обращается (возвращает) к произвольному элементу, указанному произвольным путем (список string
ключей карты и/или int
индексов среза), второй изменяет (устанавливает) значение, указанное произвольным путем (реализации этих вспомогательных функций находятся в конце ответа).
Вам нужно только один раз включить эти 2 функции в свой проект/приложение.
И теперь, используя этих помощников, задачи, которые вы хотите выполнить, становятся такими простыми (точно так же, как решение 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.
Примечание — Дальнейшее Упрощение:
Вы даже можете сохранить путь в переменной и повторно использовать его, чтобы еще больше упростить приведенный выше код:
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]
}
}
}
}
Комментарии:
1. Лично я бы написал набор примерно так: play.golang.org/p/ltSkD0rISL Он делает все то, что
!last
сначала, а затем делает набор. Как и в вашей версии, он предполагает, что путь действителен, и в противном случае будет паниковать (например, если len(путь)
Ответ №2:
Нет, это не самый правильный способ обработки структурированных данных JSON в Go. Вместо этого лучше создать «иерархию структур» и разделить ваш JSON на структуры. Например.
type Data struct {
Key1 string
Key2 struct {
C1Key1 string
}
Key3 []Key3
}
type Key3 struct {
C2Key1 struct {
C3Key1 string
}
}
Такой подход:
- дает вам больше контроля над тем, как ваши данные будут (не)упорядочены (с помощью тегов структуры
json.Unmarshaler
иjson.Marshaler
интерфейсов и) - избавляет вас от утверждений типа
- вместо этого обеспечивает большую безопасность типов
- имеет лучшую производительность, так как доступ к структуре в основном бесплатный по сравнению с доступом к карте.
Детская площадка: https://play.golang.org/p/9XIh8DX1Ms.
Комментарии:
1. Часто в json есть динамические ключи. Например, используя дату в качестве ключа. Выполнение того же запроса завтра приведет к созданию новой структуры, которую вы не можете наметить. Для тех, кто столкнется с этим ответом, обратите внимание, что вы не можете сделать вышеизложенное.
2. @jacksexton Не можете ли вы сказать, что какое-то свойство представляет собой карту со строковым ключом и типом значения структуры?
Ответ №3:
Вот полный пример использования 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), amp;d)
d.Key3[0].C2Key1.C3Key1 = "change1"
b, e := json.Marshal(d)
if e != nil {
panic(e)
}
os.Stdout.Write(b)
}