# #mysql #json #rest #go
Вопрос:
У меня есть схема базы данных, в которой возраст не является обязательным. Но когда в Go, через мой спокойный интерфейс, я вывожу данные в формате JSON, я получаю очень неприятный ответ, например {"ID":1,"Name":"John","age":{"Int64":0,"Valid":false}}
, для возраста. Я ожидал, что будет пропущено нулевое значение. Что я упускаю?
// CREATE TABLE users (
// id INT AUTO_INCREMENT PRIMARY KEY,
// name VARCHAR(50) NOT NULL,
// age INT
// );
// Age is optional
type user struct {
ID int `db:"id"`
Name string `db:"name"`
Age sql.NullInt64 `db:"age" json:"age,omitempty"`
}
func main() {
u := user{ID: 1, Name: "John"}
j, _ := json.Marshal(u)
fmt.Printf("%s", j)
}
// Expected: {"ID":1,"Name":"John"} since age is NULL/empty
Ответ №1:
sql.NullInt64
существует, потому что SQL null
не может быть представлен как Go int
. Это третье состояние, которое не может быть представлено никаким int
значением.
Одним из решений этой проблемы было бы представление таких значений SQL , как *int
, но для этого потребуется выделение для случаев, когда значения нет null
в базе данных, а распределение плохо сказывается на производительности.
Разработчики пакета SQL разработали NullInt64
решение, которое кодирует третье состояние null в качестве дополнительного Valid
логического значения. Это не очень хорошее решение, но это лучшее, что мы можем получить.
Я не уверен, что можно написать JSON marshaller, чтобы NullInt64
это работало так, как вы ожидаете.
Все еще существует проблема «третьего состояния» при маршалировании в JSON. С ,omitempty
0
int также будет опущено, так как вы можете отличить 0 от «не существует»/null?
В любом случае, они не писали пользовательский маршаллер NullInt64
, поэтому он просто кодируется как структура, которой он является.
Вы можете создать тип псевдонима для NullInt64
, написать маршаллер JSON для кодирования так, как вы хотите, чтобы JSON (вам нужен псевдоним, потому что вы не можете добавлять методы к типам из других пакетов). Вам также нужно будет выбрать между вашим NullInt64
и sql.NullInt64
.
Ответ №2:
Вы можете использовать пользовательский тип для null int64. Этот тип работает так, как вы ожидали.
Пример использования:
package main
import (
"encoding/json"
"database/sql/driver"
"fmt"
"strconv"
)
type user struct {
ID int `db:"id"`
Name string `db:"name"`
// pointer needed to omitempty work, otherwise output will be "age": null
Age *NullInt64 `db:"age" json:"age,omitempty"`
}
func main() {
u := user{ID: 1, Name: "John"}
j, _ := json.Marshal(u)
fmt.Printf("%sn", j)
}
// Output:
// {"ID":1,"Name":"John"}
type NullInt64 struct {
Val int64
IsValid bool
}
func NewNullInt64(val interface{}) NullInt64 {
ni := NullInt64{}
ni.Set(val)
return ni
}
func (ni *NullInt64) Scan(value interface{}) error {
ni.Val, ni.IsValid = value.(int64)
return nil
}
func (ni NullInt64) Value() (driver.Value, error) {
if !ni.IsValid {
return nil, nil
}
return ni.Val, nil
}
func (ni *NullInt64) Set(val interface{}) {
ni.Val, ni.IsValid = val.(int64)
}
func (ni NullInt64) MarshalJSON() ([]byte, error) {
if !ni.IsValid {
return []byte(`null`), nil
}
return []byte(strconv.FormatInt(ni.Val, 10)), nil
}
func (ni *NullInt64) UnmarshalJSON(data []byte) error {
if data == nil || string(data) == `null` {
ni.IsValid = false
return nil
}
val, err := strconv.ParseInt(string(data), 10, 64)
if err != nil {
ni.IsValid = false
return err
}
ni.Val = val
ni.IsValid = true
return nil
}
func (ni NullInt64) String() string {
if !ni.IsValid {
return `<nil>`
}
return strconv.FormatInt(ni.Val, 10)
}
Комментарии:
1. Почему вы не можете просто работать
sql.NullInt64
и не определять свои собственныеNullInt64
? это потому, что это единственный способ создать пользовательский маршаллер JSON?2. да, Джсон. Маршалер, fmt.Стрингер, также мне нужно время ожидания, поэтому я решил написать все, что мне нужно, в одном месте 🙂