пятница, 15 мая 2020 г.

Как использовать JSON в Golang

Формат обмена данными JSON удобен для чтения и записи человеком и эффективен для анализа и генерации машинами.

Типы по умолчанию

Типы Go по умолчанию для декодирования и кодирования JSON:

  • bool для логических выражений JSON
  • float64 для JSON чисел
  • string JSON для строк
  • nil для JSON null

Кроме того, time.Time и числовые типы в пакете math/big могут автоматически кодироваться как строки JSON.

Обратите внимание, что JSON не поддерживает базовые целочисленные типы. Они часто могут быть аппроксимированы числами с плавающей точкой.

Кодировать (marshal) структуру в JSON

Функция json.Marshal в пакете encoding/json генерирует данные JSON.

type FruitBasket struct {
    Name    string
    Fruit   []string
    Id      int64  `json:"ref"`
    private string // Неэкспортируемое поле не кодируется.
    Created time.Time
}

basket := FruitBasket{
    Name:    "Standard",
    Fruit:   []string{"Apple", "Banana", "Orange"},
    Id:      999,
    private: "Second-rate",
    Created: time.Now(),
}

var jsonData []byte
jsonData, err := json.Marshal(basket)
if err != nil {
    log.Println(err)
}
fmt.Println(string(jsonData))

Вывод:

{"Name":"Standard","Fruit":["Apple","Banana","Orange"],"ref":999,"Created":"2018-04-09T23:00:00Z"}

Только данные, которые могут быть представлены как JSON, будут закодированы.

  • Только экспортируемые (публичные) поля структуры будут присутствовать в выводе JSON. Другие поля игнорируются.
  • Поле с тегом json: сохраняется с именем тега вместо имени поля структуры.
  • Указатели будут закодированы как значения, на которые они указывают, или равны null, если указатель равен nil.

Отформатированная печать

Замените json.Marshal на json.MarshalIndent в примере выше, чтобы сделать отступ для вывода JSON.

jsonData, err := json.MarshalIndent(basket, "", "    ")

Вывод:

{
    "Name": "Standard",
    "Fruit": [
        "Apple",
        "Banana",
        "Orange"
    ],
    "ref": 999,
    "Created": "2018-04-09T23:00:00Z"
}

Декодировать (unmarshal) JSON в структуру

Функция json.Unmarshal в пакете encoding/json декодирует данные JSON.

type FruitBasket struct {
    Name    string
    Fruit   []string
    Id      int64 `json:"ref"`
    Created time.Time
}

jsonData := []byte(`
{
    "Name": "Standard",
    "Fruit": [
        "Apple",
        "Banana",
        "Orange"
    ],
    "ref": 999,
    "Created": "2018-04-09T23:00:00Z"
}`)

var basket FruitBasket
err := json.Unmarshal(jsonData, &basket)
if err != nil {
    log.Println(err)
}
fmt.Println(basket.Name, basket.Fruit, basket.Id)
fmt.Println(basket.Created)

Вывод:

Standard [Apple Banana Orange] 999
2018-04-09 23:00:00 +0000 UTC

Обратите внимание, что Unmarshal выделил новый срез самостоятельно. Вот как работает демаршалинг для срезов, карт и указателей.

Для заданного JSON ключа Foo Unmarshal попытается сопоставить поля структуры в следующем порядке:

  • экспортированное (публичное) поле с тегом json: "Foo"
  • экспортированное поле с именем Foo
  • экспортированное поле с именем FOO, FoO или другим сопоставлением без учета регистра

Только поля, которые найдены в типе назначения, будут декодированы:

  • Это полезно, когда вы хотите выбрать только несколько конкретных полей.
  • В частности, любые неэкспортированные поля в структуре назначения не будут затронуты.

Произвольные объекты и массивы

Пакет encoding/json использует

  • map[string]interface{} для хранения произвольных объектов JSON
  • []interface{} для хранения произвольных массивов JSON

Любые допустимые данные JSON будут декодированы в обычном interface{} значении.

Рассмотрим данные JSON:

{
    "Name": "Eve",
    "Age": 6,
    "Parents": [
        "Alice",
        "Bob"
    ]
}

Функция json.Unmarshal будет декодировать их в карту, ключи которой являются строками, а сами значения хранятся как пустые значения интерфейса:

map[string]interface{}{
    "Name": "Eve",
    "Age":  6.0,
    "Parents": []interface{}{
        "Alice",
        "Bob",
    },
}

Мы можем перебрать карту с помощью оператора range и использовать переключатель типа для доступа к ее значениям.

jsonData := []byte(`{"Name":"Eve","Age":6,"Parents":["Alice","Bob"]}`)

var v interface{}
json.Unmarshal(jsonData, &v)
data := v.(map[string]interface{})

for k, v := range data {
    switch v := v.(type) {
    case string:
        fmt.Println(k, v, "(string)")
    case float64:
        fmt.Println(k, v, "(float64)")
    case []interface{}:
        fmt.Println(k, "(array):")
        for i, u := range v {
            fmt.Println("    ", i, u)
        }
    default:
        fmt.Println(k, v, "(unknown)")
    }
}

Вывод:

Name Eve (string)
Age 6 (float64)
Parents (array):
     0 Alice
     1 Bob

Пример JSON файла

Типы json.Decoder и json.Encoder в пакете encoding/json обеспечивают поддержку чтения и записи потоков данных JSON, например, файлов.

Код в этом примере

  • читает поток объектов JSON из Reader (strings.Reader),
  • удаляет поле Age из каждого объекта,
  • и затем записывает объекты в Writer (os.Stdout).

const jsonData = `
    {"Name": "Alice", "Age": 25}
    {"Name": "Bob", "Age": 22}
`
reader := strings.NewReader(jsonData)
writer := os.Stdout

dec := json.NewDecoder(reader)
enc := json.NewEncoder(writer)

for {
    // Чтение одного объекта JSON и сохранение его в карте.
    var m map[string]interface{}
    if err := dec.Decode(&m); err == io.EOF {
        break
    } else if err != nil {
        log.Fatal(err)
    }

    // Удаление все пар ключ-значение 
    // с ключом == "Age" из карты.
    for k := range m {
        if k == "Age" {
            delete(m, k)
        }
    }

    //Запись карты как объект JSON.
    if err := enc.Encode(&m); err != nil {
        log.Println(err)
    }
}

Вывод:

{"Name":"Alice"}
{"Name":"Bob"}


Читайте также:


Комментариев нет:

Отправить комментарий