Принимая строку JSON, разархивируя ее в интерфейс map[string] {}, редактируя и сортируя ее в байт [], кажется более сложным, чем должно быть

# #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)
}