使用\ u0000 \ x00转到json.Unmarshal键

时间:2015-09-08 08:56:39

标签: json string go unmarshalling

以下是Go playground链接。

基本上我的JSON字符串键中有一些特殊字符('\u0000'):

var j = []byte(`{"Page":1,"Fruits":["5","6"],"\u0000*\u0000_errorMessages":{"x":"123"},"*_successMessages":{"ok":"hi"}}`)

我想把它解组成一个结构:

type Response1 struct {
    Page   int
    Fruits []string
    Msg    interface{} `json:"*_errorMessages"`
    Msg1   interface{} `json:"\\u0000*\\u0000_errorMessages"`
    Msg2   interface{} `json:"\u0000*\u0000_errorMessages"`
    Msg3   interface{} `json:"\0*\0_errorMessages"`
    Msg4   interface{} `json:"\\0*\\0_errorMessages"`
    Msg5   interface{} `json:"\x00*\x00_errorMessages"`
    Msg6   interface{} `json:"\\x00*\\x00_errorMessages"`
    SMsg   interface{} `json:"*_successMessages"`
}

我尝试了很多,但它没有用。 此链接可能有助于golang.org/src/encoding/json/encode_test.go

3 个答案:

答案 0 :(得分:5)

简答:使用当前的json实施,不能仅使用struct tags

注意:这是实施限制,而不是规范限制。 (它是json包实施的限制,而不是struct tags specification的限制。)

某些背景信息:您使用raw string literal指定了代码:

  

原始字符串文字的值是由引号之间未解释的(隐式UTF-8编码)字符组成的字符串...

因此编译器在原始字符串文字的内容中不会发生任何转义或取消引用。

reflect.StructTag引用的struct tag值的约定:

  

按照惯例,标记字符串是可选的以空格分隔的键的串联:" value"对。每个键都是一个非空字符串,由空格以外的非控制字符组成(U + 0020''),引用(U + 0022'"'),和冒号(U + 003A':')。每个值都使用U + 0022'"'字符和Go字符串文字语法。

这意味着按惯例标记值是由空格分隔的(键:"值")对的列表。键有很多限制,但值可能是任何值,值(应该)使用" Go字符串文字语法",这意味着这些值将在运行时从代码中取消引用(通过调用strconv.Unquote(),名为from StructTag.Get(),在源文件reflect/type.go中,当前行#809)。

所以不需要双引号。请参阅简化示例:

type Response1 struct {
    Page   int
    Fruits []string
    Msg    interface{} `json:"\u0000_abc"`
}

现在输入以下代码:

t := reflect.TypeOf(Response1{})
fmt.Printf("%#v\n", t.Field(2).Tag)
fmt.Printf("%#v\n", t.Field(2).Tag.Get("json"))

打印:

"json:\"\\u0000_abc\""
"\x00_abc"

如您所见,json键的值部分为"\x00_abc",因此它正确包含零字符。

json包将如何使用此功能?

json包使用StructTag.Get()(来自reflect包)返回的值,正是我们所做的。您可以在json/encode.go源文件typeFields()函数中看到它,当前行#1032。到目前为止一切都很好。

然后它在json/tags.go源文件中调用未导出的json.parseTag()函数,当前行#17。这会在逗号后面切换部分(它变为"标记选项")。

最后在源文件json/encode.go中使用前一个值调用json.isValidTag()函数,当前行#731。此函数检查传递的string的符文,并且(除了一组预定义的允许字符"!#$%&()*+-./:<=>?@[]^_{|}~ ")拒绝所有不是unicode字母或数字的内容(由unicode.IsLetter()定义)和unicode.IsDigit()):

if !unicode.IsLetter(c) && !unicode.IsDigit(c) {
    return false
} 

'\u0000'不是预定义允许字符的一部分,正如您现在可以猜到的那样,它既不是字母也不是数字:

// Following code prints "INVALID":
c := '\u0000'
if !unicode.IsLetter(c) && !unicode.IsDigit(c) {
    fmt.Println("INVALID")
}

由于isValidTag()返回falsenamejson键的值,没有&#34;标记选项&#34;部分)将被丢弃(name = "")而不使用。因此,不会找到包含unicode零的struct字段的匹配项。

对于替代解决方案,请使用map或自定义json.Unmarshaler或使用json.RawMessage

但我非常不鼓励使用这种丑陋的json键。我理解你可能只是试图解析这样的json响应,它可能超出你的范围,但是你应该反对使用这些键,因为它们稍后会引起更多的问题(例如,如果存储在db中,通过检查记录它将会很难发现其中有'\u0000'个字符,因为它们可能显示为无效。

答案 1 :(得分:0)

由于:http://golang.org/ref/spec#Struct_types

,您不能这样做

但您可以解组到map[string]interface{},然后通过regexp检查该对象的字段名称。

答案 2 :(得分:0)

我不认为使用struct标签是可行的。您可以做的最好的事情是将其解组为map[string]interface{},然后手动获取值:

var b = []byte(`{"\u0000abc":42}`)
var m map[string]interface{}
err := json.Unmarshal(b, &m)
if err != nil {
    panic(err)
}
fmt.Println(m, m["\x00abc"])

游乐场:http://play.golang.org/p/RtS7Nst0d7