2 回答
TA贡献1946条经验 获得超4个赞
这是预期的结果,记录在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}
TA贡献1801条经验 获得超16个赞
接受的答案很好,但我不得不重新搜索足够多的时间,所以我想通过示例给出关于编组/解组的完整答案,所以下次我可以只复制粘贴作为起点:)
我经常搜索的内容包括:
将自定义类型编码到 sql 数据库
json 将 enum int 编码为字符串
json编码映射键但不编码值
在这个例子中,我创建了一个自定义的 Weekday 类型,它匹配 time.Weekday int 值,但允许请求/响应 json 和数据库中的字符串值
同样的事情可以用任何使用 iota 的 int 枚举来完成,以便在 json 和数据库中具有人类可读的值
游乐场测试示例:https://go.dev/play/p/aUxxIJ6tY9K
重要的一点在这里:
var (
// read/write from/to json values
_ json.Marshaler = (*Weekday)(nil)
_ json.Unmarshaler = (*Weekday)(nil)
// read/write from/to json keys
_ encoding.TextMarshaler = (*Weekday)(nil)
_ encoding.TextUnmarshaler = (*Weekday)(nil)
// read/write from/to sql
_ sql.Scanner = (*Weekday)(nil)
_ driver.Valuer = (*Weekday)(nil)
)
// MarshalJSON marshals the enum as a quoted json string
func (w Weekday) MarshalJSON() ([]byte, error) {
return []byte(`"` + w.String() + `"`), nil
}
func (w Weekday) MarshalText() (text []byte, err error) {
return []byte(w.String()), nil
}
func (w *Weekday) UnmarshalJSON(b []byte) error {
return w.UnmarshalText(b)
}
func (w *Weekday) UnmarshalText(b []byte) error {
var dayName string
if err := json.Unmarshal(b, &dayName); err != nil {
return err
}
d, err := ParseWeekday(dayName)
if err != nil {
return err
}
*w = d
return nil
}
// Value is used for sql exec to persist this type as a string
func (w Weekday) Value() (driver.Value, error) {
return w.String(), nil
}
// Scan implements sql.Scanner so that Scan will be scanned correctly from storage
func (w *Weekday) Scan(src interface{}) error {
switch t := src.(type) {
case int:
*w = Weekday(t)
case int64:
*w = Weekday(int(t))
case string:
d, err := ParseWeekday(t)
if err != nil {
return err
}
*w = d
case []byte:
d, err := ParseWeekday(string(t))
if err != nil {
return err
}
*w = d
default:
return errors.New("Weekday.Scan requires a string or byte array")
}
return nil
}
请注意,var 块只是强制您正确地实现这些方法,否则它不会编译。
另请注意,如果您排除MarshalJSON然后 go 将在它存在时使用MarshalText,因此如果您只希望键具有自定义编组但具有值的默认行为那么您不应该在您的主要类型上使用这些方法,而是具有仅用于映射键的包装器类型
type MyType struct{}
type MyTypeKey MyType
var (
// read/write from/to json keys
_ encoding.TextMarshaler = (*MyTypeKey)(nil)
_ encoding.TextUnmarshaler = (*MyTypeKey)(nil)
)
func (w MyTypeKey) MarshalText() (text []byte, err error) {
return []byte(w.String()), nil
}
func (w *MyTypeKey) UnmarshalText(b []byte) error {
*w = MyTypeKey(ParseMyType(string(b)))
return nil
}
随意改进这个答案,我希望其他人发现它有帮助,我希望下次我能再次找到它并自己再次搜索它:)
- 2 回答
- 0 关注
- 81 浏览
添加回答
举报