为了账号安全,请及时绑定邮箱和手机立即绑定

如何在 JSON 中自定义封送映射键

如何在 JSON 中自定义封送映射键

Go
胡说叔叔 2023-03-29 16:11:41
我无法理解 custom marshal intto的奇怪行为string。这是一个例子:package mainimport (    "encoding/json"    "fmt")type Int intfunc (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我错过了什么?
查看完整描述

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}


查看完整回答
反对 回复 2023-03-29
?
侃侃尔雅

TA贡献1801条经验 获得超15个赞

接受的答案很好,但我不得不重新搜索足够多的时间,所以我想通过示例给出关于编组/解组的完整答案,所以下次我可以只复制粘贴作为起点:)

我经常搜索的内容包括:

  • 将自定义类型编码到 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

}

随意改进这个答案,我希望其他人发现它有帮助,我希望下次我能再次找到它并自己再次搜索它:)


查看完整回答
反对 回复 2023-03-29
  • 2 回答
  • 0 关注
  • 75 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信