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 關注
- 112 瀏覽
添加回答
舉報