JSON (JavaScript Object Notation) - это простой формат обмена данными. Синтаксически это напоминает объекты и списки JavaScript. Он чаще всего используется для связи между веб-интерфейсами и программами JavaScript, работающими в браузере, но также используется и во многих других местах. Его домашняя страница, json.org, предоставляет четкое и краткое определение стандарта.
С пакетом json легко и быстро читать и записывать данные JSON из ваших программ Go.
Кодирование
Для кодирования данных в JSON используется функция Marshal.
func Marshal(v interface{}) ([]byte, error)
Задав структуру данных Go, Message,
type Message struct {
Name string
Body string
Time int64
}
и экземпляр сообщения
m := Message{"Alice", "Hello", 1294706395881547000}
мы можем получить JSON-кодированную версию m, используя json.Marshal:
b, err := json.Marshal(m)
Если все хорошо, err будет nil, а b будет []byte, содержащим JSON данные:
b == []byte(`{"Name":"Alice","Body":"Hello","Time":1294706395881547000}`)
Будут закодированы только структуры данных, которые могут быть представлены валидным JSON:
- Объекты JSON поддерживают только строки в качестве ключей; чтобы кодировать Go тип map он должен иметь вид map[string]T (где T - любой тип Go, поддерживаемый пакетом json).
- Channel, complex и func типы не могут быть закодированы.
- Циклические структуры данных не поддерживаются; они приведут к попаданию Marshal в бесконечный цикл.
- Указатели будут закодированы как значения, на которые они указывают (или 'null', если указатель равен nil).
Пакет json обращается только к экспортированным полям struct типов (те, которые начинаются с заглавной буквы). Поэтому в выводе JSON будут присутствовать только экспортированные поля структуры.
Декодирование
Для декодирования JSON данных используем функцию Unmarshal.
func Unmarshal(data []byte, v interface{}) error
Сначала нужно создать место, где будут храниться декодированные данные
var m Message
и вызвать json.Unmarshal, передав ему []byte JSON данных и указатель на m
err := json.Unmarshal(b, &m)
Если b содержит допустимый JSON, который соответствует m, после вызова err будет nil и данные из b будут сохранены в структуре m, как если бы это было сделано с помощью присваивания, подобного следующему:
m = Message{
Name: "Alice",
Body: "Hello",
Time: 1294706395881547000,
}
Как Unmarshal идентифицирует поля для хранения декодированных данных? Для заданного ключа JSON "Foo" Unmarshal просматривает поля структуры назначения, чтобы найти (в порядке предпочтения):
- Экспортированное поле с тегом "Foo"
- Экспортированное поле с именем "Foo"
- Экспортированное поле с именем "FOO" или "FoO" или другое нечувствительное к регистру совпадение "Foo"
Что происходит, когда структура данных JSON не совсем соответствует типу Go?
b := []byte(`{"Name":"Bob","Food":"Pickle"}`)
var m Message
err := json.Unmarshal(b, &m)
Unmarshal будет декодировать только те поля, которые он может найти в типе назначения. В этом случае будет заполнено только поле Name в m, а поле Food будет игнорироваться. Это поведение особенно полезно, когда вы хотите выбрать только несколько определенных полей из большого JSON-объекта. Это также означает, что Unmarshal не затронет любые неэкспортированные поля в структуре назначения.
Но что, если вы заранее не знаете структуру данных JSON?
Универсальный JSON с interface{}
Тип interface{} (пустой интерфейс) описывает интерфейс с нулевыми методами. Каждый тип Go реализует как минимум ноль методов и поэтому удовлетворяет пустому интерфейсу.
Пустой интерфейс служит общим типом контейнера:
var i interface{}
i = "a string"
i = 2011
i = 2.777
Утверждение типа обращается к подлежащему конкретному типу:
r := i.(float64)
fmt.Println("the circle's area", math.Pi*r*r)
Или, если базовый тип неизвестен, переключатель типа определяет тип:
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 ни один из типов выше
}
Пакет json использует значения map[string]interface{} и []interface{} для хранения произвольных объектов и массивов JSON; он с радостью разархивирует любой допустимый большой бинарный объект JSON (blob) в простое значение interface{}. Конкретные типы Go по умолчанию:
- bool для логических выражений JSON
- float64 для JSON чисел
- string для JSON строк
- nil для JSON null
Декодирование произвольных данных
Рассмотрим JSON данные, хранящиеся в переменной b:
b := []byte(`{"Name":"Wednesday","Age":6,"Parents":["Gomez","Morticia"]}`)
Не зная структуры этих данных, мы можем декодировать их в значение interface{} с помощью Unmarshal:
var f interface{}
err := json.Unmarshal(b, &f)
В этот момент значением Go в f будет карта, ключи которой являются строками, а сами значения хранятся как пустые значения интерфейса:
f = map[string]interface{}{
"Name": "Wednesday",
"Age": 6,
"Parents": []interface{}{
"Gomez",
"Morticia",
},
}
Чтобы получить доступ к этим данным, мы можем использовать утверждение типа для доступа к подлежащему map[string]interface{}:
m := f.(map[string]interface{})
Затем мы можем перебрать карту с помощью оператора range и использовать переключатель типа для доступа к ее значениям в качестве их конкретных типов:
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")
}
}
Таким образом, вы можете работать с неизвестными JSON данными, сохраняя при этом преимущества безопасности типов.
Ссылочные типы
Определим тип Go, который будет содержать данные из предыдущего примера:
type FamilyMember struct {
Name string
Age int
Parents []string
}
var m FamilyMember
err := json.Unmarshal(b, &m)
Демонстрация этих данных в значение FamilyMember работает, как и ожидалось, но если мы посмотрим внимательнее, то увидим, что произошла замечательная вещь. С помощью оператора var мы выделили структуру FamilyMember, а затем предоставили указатель на это значение для Unmarshal, но в то время поле Parents было нулевым значением среза. Чтобы заполнить поле Parents, Unmarshal выделил новый срез за кулисами. Это типично для того, как Unmarshal работает с поддерживаемыми ссылочными типами (указатели, срезы и карты).
Рассмотрим конвертирование в эту структуру данных:
type Foo struct {
Bar *Bar
}
Если бы в объекте JSON было поле Bar, Unmarshal выделил бы новый Bar и заполнил его. Если нет, Bar будет оставлен как нулевой указатель.
Из этого вытекает полезный шаблон: если у вас есть приложение, которое получает несколько различных типов сообщений, вы можете определить структуру "получателя", например:
type IncomingMessage struct {
Cmd *Command
Msg *Message
}
и отправляющая сторона может заполнить поле Cmd и/или поле Msg объекта JSON верхнего уровня, в зависимости от типа сообщения, которое они хотят передать. Unmarshal при декодировании JSON в структуру IncomingMessage будет выделять только структуры данных, присутствующие в JSON данных. Чтобы узнать, какие сообщения обрабатывать, программисту просто нужно проверить, что Cmd или Msg не ноль.
Потоковые кодировщики и декодеры
Пакет json предоставляет типы Decoder и Encoder для поддержки обычной операции чтения и записи потоков данных JSON. Функции NewDecoder и NewEncoder оборачивают типы интерфейсов io.Reader и io.Writer.
func NewDecoder(r io.Reader) *Decoder
func NewEncoder(w io.Writer) *Encoder
Вот пример программы, которая читает серию объектов JSON из стандартного ввода, удаляет все, кроме поля Name, из каждого объекта, а затем записывает объекты в стандартный вывод:
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)
}
}
}
Из-за повсеместного распространения программ чтения и записи эти типы кодировщиков и декодеров могут использоваться в широком диапазоне сценариев, таких как чтение и запись в соединения HTTP, веб-сокеты или файлы.
Пример использования тегов в структуре
Использование тегов в структуре кодируемой в JSON позволяет получить названия полей в результирующем JSON, отличающиеся от названия полей в структуре. В следующем примере в результирующем JSON поле BrandID будет выглядеть как brand_id:
package main
import (
"encoding/json"
"fmt"
)
type Item struct {
ID uint `json:"id"`
Title string `json:"title"`
BrandID uint `json:"brand_id"`
}
func main() {
item := Item{ID: 1, Title: "Car", BrandID: 1}
jitem, err := json.Marshal(item)
if err != nil {
fmt.Println(err.Error())
return
}
fmt.Println(string(jitem))
}
Вывод:
{"id":1,"title":"Car","brand_id":1}
Запустить пример в песочнице play.golang.org
Читайте также:
1 комментарий:
Спасибо за пост, очень сложно было понять как работать с Json, особенно после прихода в Go с Ruby.
Ошибки связанные с json иногда настолько непонятные, что сложно отлаживать, пример:
invalid character 'a' in literal true (expecting 'r')
Отправить комментарий