【问题标题】:json.Marshal custom type as base64 stringjson.Marshal 自定义类型为 base64 字符串
【发布时间】:2019-07-01 01:49:35
【问题描述】:

我有一个自定义类型(哈希 [64] 字节),我正在尝试实现 MarshalJSON/UnmarshalJSON 以使其在 JSON 中编码/解码为 base64 字符串。相反,我在开始时收到有关无效字符的错误。

package main

import (
    "encoding/base64"
    "encoding/json"
    "fmt"
)

type Hash [64]byte

func FromString(data string) (Hash, error) {
    decoded, err := base64.StdEncoding.DecodeString(string(data))
    if err != nil {
        return Hash{}, err
    }

    hash := Hash{}
    for index := 0; index < 64; index++ {
        hash[index] = decoded[index]
    }

    return hash, nil
}

func (hash Hash) String() string {
    return base64.StdEncoding.EncodeToString([]byte(hash[:64]))
}

func (hash Hash) MarshalJSON() ([]byte, error) {
    return []byte(hash.String()), nil
}

func (hash *Hash) UnmarshalJSON(data []byte) error {
    decoded, err := FromString(string(data))
    if err != nil {
        return err
    }

    for index := 0; index < 64; index++ {
        hash[index] = decoded[index]
    }

    return nil
}


func main() {
    type TestStructure struct {
        Hash Hash
        Type string
    }

    object := TestStructure{
        Hash: Hash{0xbd, 0xfe, 0xe0, 0xb1, 0x6c, 0xff, 0xb4, 0x51, 0x4c, 0x7b, 0xed, 0x33, 0xc1, 0x6d, 0xac, 0x5e, 0x80, 0x51, 0xec, 0xcb, 0x31, 0x21, 0x8c, 0x54, 0xb, 0xec, 0xbc, 0x7e, 0xbf, 0x4a, 0xce, 0x92, 0x3b, 0xcb, 0xf8, 0xdd, 0x82, 0x45, 0x34, 0xae, 0x58, 0x5, 0x3a, 0x7b, 0x18, 0xdd, 0x30, 0x5c, 0x7e, 0xed, 0xc9, 0xaa, 0x1e, 0x3a, 0x9a, 0x95, 0x30, 0xc3, 0x6b, 0xf8, 0xf9, 0x92, 0x43, 0xc6},
        Type: "I'm a type",
    }

    data, err := json.Marshal(object)
    fmt.Println(data, err)
}

我收到以下错误:

$ go run hash.go 
[] json: error calling MarshalJSON for type main.Hash: invalid character 'v' looking for beginning of value

我做错了什么?

【问题讨论】:

  • 经验法则:不调用 json.Marshal 的 MarshalJSON 方法是可疑的。它很可能会产生无效的 JSON,就像在这种情况下一样。你可以在这里简单地做return json.Marshal(hash[:]),因为字节片是自动base64编码的。对于 unmarshal 方法也是如此。还可以考虑实施encoding.Text(Un)Marshaler。然后,您可以摆脱所有其他方法,同时支持 XML 和其他文本编码。
  • 这真是太棒了!我试试看

标签: json go encoding types marshalling


【解决方案1】:

感谢@Peter 写的关于 MarshalText/UnmarshalText 的评论,我找到了一个不错的解决方案。可能有比 for 循环更好的方法来复制值,但至少目前可行。

package main

import (
    "encoding/base64"
    "encoding/json"
    "fmt"
)

type Hash [64]byte

func FromString(data string) (Hash, error) {
    decoded, err := base64.StdEncoding.DecodeString(string(data))
    if err != nil {
        return Hash{}, err
    }

    hash := Hash{}
    for index := 0; index < 64; index++ {
        hash[index] = decoded[index]
    }

    return hash, nil
}

func (hash Hash) String() string {
    return base64.StdEncoding.EncodeToString([]byte(hash[:]))
}

func (hash Hash) MarshalText() (text []byte, err error) {
    return []byte(hash.String()), nil
}

func (hash *Hash) UnmarshalText(text []byte) error {
    decoded, err := base64.StdEncoding.DecodeString(string(text))
    if err != nil {
        return err
    }

    for index := 0; index < 64; index++ {
        hash[index] = decoded[index]
    }

    return nil
}

func main() {
    type TestStructure struct {
        Hash Hash
        Type string
    }

    object := TestStructure{
        Hash: Hash{0xbd, 0xfe, 0xe0, 0xb1, 0x6c, 0xff, 0xb4, 0x51, 0x4c, 0x7b, 0xed, 0x33, 0xc1, 0x6d, 0xac, 0x5e, 0x80, 0x51, 0xec, 0xcb, 0x31, 0x21, 0x8c, 0x54, 0xb, 0xec, 0xbc, 0x7e, 0xbf, 0x4a, 0xce, 0x92, 0x3b, 0xcb, 0xf8, 0xdd, 0x82, 0x45, 0x34, 0xae, 0x58, 0x5, 0x3a, 0x7b, 0x18, 0xdd, 0x30, 0x5c, 0x7e, 0xed, 0xc9, 0xaa, 0x1e, 0x3a, 0x9a, 0x95, 0x30, 0xc3, 0x6b, 0xf8, 0xf9, 0x92, 0x43, 0xc6},
        Type: "I'm a type",
    }

    data, err := json.Marshal(object)
    fmt.Println(string(data), err)

    ts := TestStructure{}
    err = json.Unmarshal(data, &ts)
    fmt.Printf("%+v\n", ts)
    fmt.Println(err)

    h, err := FromString(ts.Hash.String())
    fmt.Printf("%+v\n", h)
    fmt.Println(err)
}

【讨论】:

    【解决方案2】:

    您的MarshalJSON 方法需要在它返回的值中包含引号,否则您最终会得到无效的 JSON。比如:

    func (hash Hash) MarshalJSON() ([]byte, error) {
        return []byte(`"` + hash.String() + `"`), nil
    }
    

    应该可以。

    在您的错误消息中,'v' 是您的 base64 编码文本中的第一个字符,因此该消息显示它在查找有效的 JSON 类型(即字符串、数字、布尔值、对象、数组)时正在查找 'v' , 或 null),其中任何一个都不能以该字符开头。

    进行此更改并调整最后一行中的类型:

    package main
    
    import (
        "encoding/base64"
        "encoding/json"
        "fmt"
    )
    
    type Hash [64]byte
    
    func FromString(data string) (Hash, error) {
        decoded, err := base64.StdEncoding.DecodeString(string(data))
        if err != nil {
            return Hash{}, err
        }
    
        hash := Hash{}
        for index := 0; index < 64; index++ {
            hash[index] = decoded[index]
        }
    
        return hash, nil
    }
    
    func (hash Hash) String() string {
        return base64.StdEncoding.EncodeToString([]byte(hash[:64]))
    }
    
    func (hash Hash) MarshalJSON() ([]byte, error) {
        return []byte(`"` + hash.String() + `"`), nil
    }
    
    func (hash *Hash) UnmarshalJSON(data []byte) error {
        decoded, err := FromString(string(data[1 : len(data)-1]))
        if err != nil {
            return err
        }
    
        for index := 0; index < 64; index++ {
            hash[index] = decoded[index]
        }
    
        return nil
    }
    
    func main() {
        type TestStructure struct {
            Hash Hash
            Type string
        }
    
        object := TestStructure{
            Hash: Hash{0xbd, 0xfe, 0xe0, 0xb1, 0x6c, 0xff, 0xb4, 0x51, 0x4c, 0x7b, 0xed, 0x33, 0xc1, 0x6d, 0xac, 0x5e, 0x80, 0x51, 0xec, 0xcb, 0x31, 0x21, 0x8c, 0x54, 0xb, 0xec, 0xbc, 0x7e, 0xbf, 0x4a, 0xce, 0x92, 0x3b, 0xcb, 0xf8, 0xdd, 0x82, 0x45, 0x34, 0xae, 0x58, 0x5, 0x3a, 0x7b, 0x18, 0xdd, 0x30, 0x5c, 0x7e, 0xed, 0xc9, 0xaa, 0x1e, 0x3a, 0x9a, 0x95, 0x30, 0xc3, 0x6b, 0xf8, 0xf9, 0x92, 0x43, 0xc6},
            Type: "I'm a type",
        }
    
        data, err := json.Marshal(object)
        fmt.Println(string(data), err)
    }
    

    产生预期的输出:

    paul@mac:go64$ ./go64
    {"Hash":"vf7gsWz/tFFMe+0zwW2sXoBR7MsxIYxUC+y8fr9KzpI7y/jdgkU0rlgFOnsY3TBcfu3Jqh46mpUww2v4+ZJDxg==","Type":"I'm a type"} <nil>
    paul@mac:go64$ 
    

    显然,您还需要在解组期间处理引号。

    【讨论】:

    • 谢谢,这是一个非常好的解释!我完全错过了您需要用两个引号将字节数组包装起来。现在我使用的 encoding.Text(Un)Marshaler 看起来更加整洁,
    猜你喜欢
    • 2020-03-17
    • 2019-11-22
    • 1970-01-01
    • 2021-08-20
    • 1970-01-01
    • 1970-01-01
    • 2019-06-02
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多