Как настроить маршальные ключи карты в JSON - PullRequest
0 голосов
/ 04 сентября 2018

Я не могу понять странное поведение обычного маршала int до string.

Вот пример:

package main

import (
    "encoding/json"
    "fmt"
)

type Int int

func (a Int) MarshalJSON() ([]byte, error) {
    test := a / 10
    return json.Marshal(fmt.Sprintf("%d-%d", a, test))
}

func main() {

    array := []Int{100, 200}
    arrayJson, _ := json.Marshal(array)
    fmt.Println("array", string(arrayJson))

    maps := map[Int]bool{
        100: true,
        200: true,
    }
    mapsJson, _ := json.Marshal(maps)
    fmt.Println("map wtf?", string(mapsJson))
    fmt.Println("map must be:", `{"100-10":true, "200-20":true}`)
}

Вывод:

array ["100-10","200-20"]
map wtf? {"100":true,"200":true}
map must be: {"100-10":true, "200-20":true}

https://play.golang.org/p/iiUyL2Hc5h_P

Чего мне не хватает?

1 Ответ

0 голосов
/ 04 сентября 2018

Это ожидаемый результат, который задокументирован в json.Marshal():

Значения карты кодируются как объекты JSON. Тип ключа карты должен быть либо строкой, целочисленным типом, либо реализовывать encoding.TextMarshaler. Ключи карты сортируются и используются в качестве ключей объектов JSON с применением следующих правил при условии применения UTF-8, описанного для строковых значений выше:

- string keys are used directly
- encoding.TextMarshalers are marshaled
- integer keys are converted to strings

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

Согласно документу, если вы хотите, чтобы он работал и для ключей карты, внедрите encoding.TextMarshaler:

func (a Int) MarshalText() (text []byte, err error) {
    test := a / 10
    return []byte(fmt.Sprintf("%d-%d", a, test)), nil
}

(Обратите внимание, что MarshalText() должен возвращать «просто» простой текст, а не текст JSON, поэтому мы пропускаем маршалинг JSON в нем!)

При этом вывод будет (попробуйте на Go Playground ):

array ["100-10","200-20"] <nil>
map wtf? {"100-10":true,"200-20":true} <nil>
map must be: {"100-10":true, "200-20":true}

Обратите внимание, что достаточно encoding.TextMarshaler, поскольку это также проверяется при марсалировании в качестве значений, а не только для ключей карты. Таким образом, вам не нужно реализовывать encoding.TextMarshaler и json.Marshaler.

Если вы реализуете и то и другое, вы можете получить разные выходные данные, когда значение маршалируется как «простое» значение и как ключ карты, потому что json.Marshaler имеет приоритет при генерации значения:

func (a Int) MarshalJSON() ([]byte, error) {
    test := a / 100
    return json.Marshal(fmt.Sprintf("%d-%d", a, test))
}

func (a Int) MarshalText() (text []byte, err error) {
    test := a / 10
    return []byte(fmt.Sprintf("%d-%d", a, test)), nil
}

На этот раз выход будет (попробуйте на Go Playground ):

array ["100-1","200-2"] <nil>
map wtf? {"100-10":true,"200-20":true} <nil>
map must be: {"100-10":true, "200-20":true}
...