Показаны сообщения с ярлыком JSON в Golang. Показать все сообщения
Показаны сообщения с ярлыком JSON в Golang. Показать все сообщения

четверг, 28 мая 2020 г.

Нет JSON после маршалинга

Почему json.Marshal создает пустые структуры в текстовом выводе JSON?

type Person struct {
    name string
    age  int
}

p := Person{"Alice", 22}
jsonData, _ := json.Marshal(p)
fmt.Println(string(jsonData))

{}

Ответ

В выводе JSON будут присутствовать только экспортированные поля структуры Go.

type Person struct {
    Name string // Изменено на заглавную N
    Age  int    // Изменено на заглавную A
}

p := Person{"Alice", 22}

jsonData, _ := json.Marshal(p)
fmt.Println(string(jsonData))

{"Name":"Alice","Age":22}

Вы можете указать имя поля JSON явно с помощью тега json: .

type Person struct {
    Name string `json:"name"`
    Age  int    `json:"age"`
}

p := Person{"Alice", 22}

jsonData, _ := json.Marshal(p)
fmt.Println(string(jsonData))

{"name":"Alice","age":22}


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


пятница, 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"}


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


четверг, 14 ноября 2019 г.

Работа с JSON в Golang

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


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