The Go Blog

JSON и Go

Andrew Gerrand
25 января 2011

Введение

JSON (JavaScript Object Notation) — это простой формат обмена данными. Синтаксически он напоминает объекты и списки JavaScript. Наиболее часто используется для связи между веб-бэкендами и программами на JavaScript, выполняющимися в браузере, но также применяется и в других местах. Домашняя страница JSON, json.org, содержит прекрасно ясное и краткое определение стандарта.

С помощью пакета json очень просто читать и записывать JSON-данные из ваших Go-программ.

Кодирование

Для кодирования JSON-данных используется функция Marshal.

<code>func Marshal(v interface{}) ([]byte, error)
</code>

Для заданной структуры данных Go, Message,

<code>type Message struct {
  Name string
  Body string
  Time int64
}
</code>

и экземпляра Message

<code>m := Message{"Alice", "Hello", 1294706395881547000}
</code>

можно закодировать версию m в формате JSON с помощью json.Marshal:

<code>b, err := json.Marshal(m)
</code>

Если всё прошло успешно, err будет nil, а b будет содержать []byte с этими JSON-данными:

<code>b == []byte(`{"Name":"Alice","Body":"Hello","Time":1294706395881547000}`)
</code>

Кодируются только те структуры данных, которые могут быть представлены как корректный JSON:

  • JSON-объекты поддерживают только строки в качестве ключей; для кодирования типа Go map необходимо использовать форму map[string]T (где T — любой тип Go, поддерживаемый пакетом json).

  • Каналы, комплексные и функциональные типы не могут быть закодированы.

  • Циклические структуры данных не поддерживаются; они приведут к бесконечному циклу в Marshal.

  • Указатели будут закодированы как значения, на которые они указывают (или 'null', если указатель равен nil).

Пакет json работает только с экспортируемыми полями структур (такими, которые начинаются с заглавной буквы). Следовательно, в JSON-выводе будут присутствовать только экспортируемые поля структуры.

Декодирование

Для декодирования JSON-данных используется функция Unmarshal.

<code>func Unmarshal(data []byte, v interface{}) error
</code>

Сначала необходимо создать место, куда будут сохранены декодированные данные

<code>var m Message
</code>

и вызвать json.Unmarshal, передав ему []byte с JSON-данными и указатель на m

<code>err := json.Unmarshal(b, &m)
</code>

Если b содержит действительный JSON, который помещается в m, после вызова err будет равно nil, а данные из b будут сохранены в структуре m, как если бы это было присваивание следующего вида:

<code>m = Message{
  Name: "Alice",
  Body: "Hello",
  Time: 1294706395881547000,
}
</code>

Как Unmarshal определяет поля, в которые нужно сохранить раскодированные данные? Для заданного JSON ключа "Foo", Unmarshal будет искать в полях целевой структуры (в порядке предпочтения):

Что происходит, когда структура JSON данных не совпадает точно с типом Go?

<code>b := []byte(`{"Name":"Bob","Food":"Pickle"}`)
var m Message
err := json.Unmarshal(b, &m)
</code>

Unmarshal декодирует только те поля, которые он может найти в целевом типе. В этом случае будет заполнено только поле Name структуры m, а поле Food будет проигнорировано. Такое поведение особенно полезно, когда вы хотите извлечь лишь несколько конкретных полей из большого JSON-блоба. Это также означает, что любые неэкспортируемые поля в целевой структуре не будут затронуты Unmarshal.

Но что делать, если вы заранее не знаете структуру ваших JSON данных?

Обобщённый JSON с интерфейсом

Тип interface{} (пустой интерфейс) описывает интерфейс без методов. Каждый тип Go реализует как минимум ноль методов и, следовательно, удовлетворяет пустому интерфейсу.

Пустой интерфейс служит общим типом контейнера:

<code>var i interface{}
i = "a string"
i = 2011
i = 2.777
</code>

Утверждение типа позволяет получить доступ к базовому конкретному типу:

<code>r := i.(float64)
fmt.Println("the circle's area", math.Pi*r*r)
</code>

Или, если базовый тип неизвестен, переключение по типу (type switch) определяет тип:

<code>switch v := i.(type) {
  case int:
    fmt.Println("twice i is", v*2)
    case float64:
      fmt.Println("the reciprocal of i is", 1/v)
      case string:
        h := len(v) / 2
        fmt.Println("i swapped by halves is", v[h:]+v[:h])
        default:
          // i isn't one of the types above
        }
        </code>

Пакет json использует значения map[string]interface{} и []interface{} для хранения произвольных JSON объектов и массивов; он с радостью раскодирует любой действительный JSON-блоб в простое значение interface{}. Стандартные конкретные Go типы:

Декодирование произвольных данных

Рассмотрим следующие данные JSON, хранящиеся в переменной b:

<code>b := []byte(`{"Name":"Wednesday","Age":6,"Parents":["Gomez","Morticia"]}`)
</code>

Не зная структуры этих данных, вы можете декодировать их в значение типа interface{} с помощью функции Unmarshal:

<code>var f interface{}
err := json.Unmarshal(b, &f)
</code>

На этом этапе значение Go в f будет картой, ключи которой являются строками, а значения хранятся как пустые значения интерфейса:

<code>f = map[string]interface{}{
  "Name": "Wednesday",
  "Age":  6,
  "Parents": []interface{}{
    "Gomez",
    "Morticia",
  },
}
</code>

Чтобы получить доступ к этим данным, можно использовать утверждение типа, чтобы получить доступ к базовой структуре map[string]interface{}:

<code>m := f.(map[string]interface{})
</code>

Затем можно перебирать карту с помощью инструкции range и использовать переключатель типа (type switch) для получения значений в их конкретных типах:

<code>for k, v := range m {
  switch vv := v.(type) {
    case string:
      fmt.Println(k, "is string", vv)
      case float64:
        fmt.Println(k, "is float64", vv)
        case []interface{}:
          fmt.Println(k, "is an array:")
          for i, u := range vv {
            fmt.Println(i, u)
          }
          default:
            fmt.Println(k, "is of a type I don't know how to handle")
          }
        }
        </code>

Таким образом, можно работать с неизвестными данными JSON, сохраняя при этом преимущества типобезопасности.

Ссылочные типы

Определим тип Go, который будет содержать данные из предыдущего примера:

<code>type FamilyMember struct {
  Name    string
  Age     int
  Parents []string
}
var m FamilyMember
err := json.Unmarshal(b, &m)
</code>

Декодирование этих данных в значение типа FamilyMember работает так, как ожидалось, но если взглянуть внимательно, можно заметить, что произошло нечто замечательное. С помощью инструкции var мы выделили структуру FamilyMember, а затем передали указатель на это значение функции Unmarshal, но в этот момент поле Parents было nil срезом. Чтобы заполнить поле Parents, Unmarshal выделил новый срез за кулисами. Это типично для работы Unmarshal со ссылочными типами (указатели, срезы и карты).

Рассмотрим декодирование в следующую структуру данных:

<code>type Foo struct {
  Bar *Bar
}
</code>

Если бы в объекте JSON присутствовало поле Bar, Unmarshal выделил бы новый Bar и заполнил его. Если бы его не было, поле Bar осталось бы равным nil указателю.

Из этого вытекает полезный паттерн: если у вас есть приложение, которое получает несколько различных типов сообщений, можно определить структуру «приемника», например:

<code>type IncomingMessage struct {
  Cmd *Command
  Msg *Message
}
</code>

и отправляющая сторона может заполнять поле Cmd и/или поле Msg в верхнеуровневом объекте JSON, в зависимости от типа передаваемого сообщения. Когда Unmarshal декодирует JSON в структуру IncomingMessage, он выделяет только те структуры данных, которые присутствуют в JSON-данных. Чтобы определить, какие сообщения обрабатывать, программисту нужно просто проверить, что либо Cmd, либо Msg не равно nil.

Потоковые кодировщики и декодировщики

Пакет json предоставляет типы Decoder и Encoder для поддержки общих операций чтения и записи потоков данных JSON. Функции NewDecoder и NewEncoder оборачивают интерфейсные типы io.Reader и io.Writer.

<code>func NewDecoder(r io.Reader) *Decoder
func NewEncoder(w io.Writer) *Encoder
</code>

Вот пример программы, которая читает серию JSON-объектов из стандартного ввода, удаляет из каждого объекта все поля кроме Name, а затем записывает объекты в стандартный вывод:

<code>package main
import (
  "encoding/json"
  "log"
  "os"
)
func main() {
  dec := json.NewDecoder(os.Stdin)
  enc := json.NewEncoder(os.Stdout)
  for {
    var v map[string]interface{}
    if err := dec.Decode(&v); err != nil {
      log.Println(err)
      return
    }
    for k := range v {
      if k != "Name" {
        delete(v, k)
      }
    }
    if err := enc.Encode(&v); err != nil {
      log.Println(err)
    }
  }
}
</code>

Из-за широкого распространения типов Reader и Writer, эти типы Encoder и Decoder могут использоваться в широком диапазоне сценариев, например, при чтении и записи через HTTP-соединения, WebSockets или файлы.

Ссылки

Для получения дополнительной информации смотрите документацию пакета json. Пример использования json можно найти в исходных файлах пакета jsonrpc.

Следующая статья: Go становится более стабильным
Предыдущая статья: Go Slices: usage and internals
Индекс блога

GoRu.dev Golang на русском

На сайте представлена адаптированная под русский язык документация языка программирования Golang