Как сериализовать и десериализовать структуры в Go
Что проверяют
Практику работы с encoding/json, а не определение слова «сериализация». Ключевые маркеры: экспортируемые поля, теги, указатель в Unmarshal, обработка ошибок и понимание, что происходит с неизвестными полями.
Как отвечать
Стандартная библиотека: json.Marshal превращает значение в []byte, json.Unmarshal(data, &v) разбирает обратно и требует адресуемое значение, иначе получите json: Unmarshal(non-pointer main.User).
type User struct {
Name string `json:"name"`
Email string `json:"email,omitempty"`
Age int `json:"age"`
Created time.Time `json:"created_at"`
Debug string `json:"-"`
secret string // неэкспортируемое поле в JSON не попадёт
}
func main() {
u := User{Name: "Alice", Age: 30, Created: time.Now()}
data, err := json.Marshal(u)
if err != nil {
log.Fatalf("marshal: %v", err)
}
fmt.Println(string(data))
var back User
if err := json.Unmarshal(data, &back); err != nil {
log.Fatalf("unmarshal: %v", err)
}
fmt.Printf("%+v\n", back)
}Что стоит назвать сверх примера:
- сериализуются только экспортируемые поля: рефлексия не видит поля со строчной буквы, тег на них не поможет;
omitemptyвыбрасывает нулевые значения, поэтому0,falseи пустая строка пропадут. Чтобы отличить «не задано» от «нулевое», поле делают указателем*int;- при разборе неизвестные поля молча игнорируются. Строгий режим включается декодером:
dec := json.NewDecoder(r); dec.DisallowUnknownFields(); - разбор в
map[string]anyдаёт все числа какfloat64, целые числа теряют точность на больших значениях. Для этого естьdec.UseNumber(); Marshalпо умолчанию экранирует<,>и&в escape-последовательности, отключается только черезenc.SetEscapeHTML(false);- потоковая работа:
json.NewEncoder(w).Encode(v)иjson.NewDecoder(r).Decode(&v)не держат весь документ в памяти, что важно для HTTP-хендлеров; - нестандартное представление типа: реализовать
json.Marshalerиjson.Unmarshaler, для отложенного разбора части документа подойдётjson.RawMessage.
Другие форматы: encoding/xml с тегами xml:"...", encoding/gob для обмена только между Go-процессами (требует регистрации конкретных типов при передаче интерфейсов), protobuf или msgpack там, где важны размер и скорость.
Где спотыкаются
Забывают, что поле с маленькой буквы не сериализуется, получают пустой JSON и начинают искать ошибку в тегах. Второе по частоте: передают в Unmarshal значение вместо указателя.