The Go Blog
JSON и Go
Введение
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 будет искать в полях целевой структуры (в порядке предпочтения):
-
Экспортируемое поле с тегом
"Foo"(см. спецификацию Go для получения дополнительной информации о тегах структур), -
Экспортируемое поле с именем
"Foo", или -
Экспортируемое поле с именем
"FOO"или"FoO"или другое совпадение без учёта регистра"Foo".
Что происходит, когда структура 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 типы:
-
boolдля JSON логических значений, -
float64для JSON чисел, -
stringдля JSON строк, и -
nilдля JSON null.
Декодирование произвольных данных
Рассмотрим следующие данные 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
Индекс блога