从包含单引号的 JSON 键进行反序列化

huangapple go评论97阅读模式
英文:

Unmarshaling from JSON key containing a single quote

问题

我对此感到非常困惑。
我需要加载一些数据(来自法国数据库),这些数据以JSON格式序列化,其中一些键包含单引号。

这是一个简化版本:

package main

import (
	"encoding/json"
	"fmt"
)

type Product struct {
	Name string `json:"nom"`
	Cost int64  `json:"prix d'achat"`
}

func main() {
	var p Product
	err := json.Unmarshal([]byte(`{"nom":"savon", "prix d'achat": 170}`), &p)
	fmt.Printf("product cost: %d\nerror: %s\n", p.Cost, err)
}

// 输出:
// product cost: 0
// error: <nil>

解组(Unmarshal)没有出现错误,但是 "prix d'achat"(p.Cost)没有被正确解析。

当我解组到 map[string]interface{} 时,"prix d'achat" 键被解析得如我所期望:

package main

import (
	"encoding/json"
	"fmt"
)

func main() {
	blob := map[string]interface{}{}
	err := json.Unmarshal([]byte(`{"nom":"savon", "prix d'achat": 170}`), &blob)
	fmt.Printf("blob: %f\nerror: %s\n", blob["prix d'achat"], err)
}

// 输出:
// blob: 170.000000
// error: <nil>

我查看了 json.Marshal 文档中关于结构体标签的说明,但我没有找到任何与我尝试处理的数据有关的问题。

我是否漏掉了一些明显的东西?
如何使用结构体标签解析包含单引号的 JSON 键?

非常感谢您的任何见解!

英文:

I feel quite puzzled by this.
I need to load some data (coming from a French database) that is serialized in JSON and in which some keys have a single quote.

Here is a simplified version:

package main

import (
	&quot;encoding/json&quot;
	&quot;fmt&quot;
)

type Product struct {
	Name string `json:&quot;nom&quot;`
	Cost int64  `json:&quot;prix d&#39;achat&quot;`
}

func main() {
	var p Product
	err := json.Unmarshal([]byte(`{&quot;nom&quot;:&quot;savon&quot;, &quot;prix d&#39;achat&quot;: 170}`), &amp;p)
	fmt.Printf(&quot;product cost: %d\nerror: %s\n&quot;, p.Cost, err)
}

// product cost: 0
// error: %!s(&lt;nil&gt;)

Unmarshaling leads to no errors however the "prix d'achat" (p.Cost) is not correctly parsed.

When I unmarshal into a map[string]any, the "prix d'achat" key is parsed as I would expect:

package main

import (
	&quot;encoding/json&quot;
	&quot;fmt&quot;
)

func main() {
	blob := map[string]any{}
	err := json.Unmarshal([]byte(`{&quot;nom&quot;:&quot;savon&quot;, &quot;prix d&#39;achat&quot;: 170}`), &amp;blob)
	fmt.Printf(&quot;blob: %f\nerror: %s\n&quot;, blob[&quot;prix d&#39;achat&quot;], err)
}

// blob: 170.000000
// error: %!s(&lt;nil&gt;)

I checked the json.Marshal documentation on struct tags and I cannot find any issue with the data I'm trying to process.

Am I missing something obvious here?
How can I parse a JSON key containing a single quote using struct tags?

Thanks a lot for any insight!

答案1

得分: 1

我在文档中没有找到相关内容,但是JSON编码器将单引号视为标签名称中的保留字符

func isValidTag(s string) bool {
	if s == "" {
		return false
	}
	for _, c := range s {
		switch {
		case strings.ContainsRune("!#$%&()*+-./:;<=>?@[]^_{|}~ ", c):
			// 反斜杠和引号字符是保留字符,但是标点符号字符在标签名称中是允许的。
		case !unicode.IsLetter(c) && !unicode.IsDigit(c):
			return false
		}
	}
	return true
}

我认为在这里提出问题是合理的。与此同时,您需要实现json.Unmarshaler和/或json.Marshaler。以下是一个起点:

func (p *Product) UnmarshalJSON(b []byte) error {
	type product Product // 防止递归
	var _p product

	if err := json.Unmarshal(b, &_p); err != nil {
		return err
	}

	*p = Product(_p)

	return unmarshalFieldsWithSingleQuotes(p, b)
}

func unmarshalFieldsWithSingleQuotes(dest interface{}, b []byte) error {
	// 查找JSON标签。如果存在包含单引号的标签,再次对b进行解组,这次解组为map。然后解组与标签对应的map键的值(如果有)。
	var m map[string]json.RawMessage

	t := reflect.TypeOf(dest).Elem()
	v := reflect.ValueOf(dest).Elem()

	for i := 0; i < t.NumField(); i++ {
		tag := t.Field(i).Tag.Get("json")
		if !strings.Contains(tag, "'") {
			continue
		}

		if m == nil {
			if err := json.Unmarshal(b, &m); err != nil {
				return err
			}
		}

		if j, ok := m[tag]; ok {
			if err := json.Unmarshal(j, v.Field(i).Addr().Interface()); err != nil {
				return err
			}
		}
	}

	return nil
}

在Playground上尝试一下:https://go.dev/play/p/aupACXorjOO

英文:

I didn't find anything in the documentation, but the JSON encoder considers single quote to be a reserved character in tag names.

func isValidTag(s string) bool {
	if s == &quot;&quot; {
		return false
	}
	for _, c := range s {
		switch {
		case strings.ContainsRune(&quot;!#$%&amp;()*+-./:;&lt;=&gt;?@[]^_{|}~ &quot;, c):
			// Backslash and quote chars are reserved, but
			// otherwise any punctuation chars are allowed
			// in a tag name.
		case !unicode.IsLetter(c) &amp;&amp; !unicode.IsDigit(c):
			return false
		}
	}
	return true
}

I think opening an issue is justified here. In the meantime, you're going to have to implement json.Unmarshaler and/or json.Marshaler. Here is a start:

func (p *Product) UnmarshalJSON(b []byte) error {
	type product Product // revent recursion
	var _p product

	if err := json.Unmarshal(b, &amp;_p); err != nil {
		return err
	}

	*p = Product(_p)

	return unmarshalFieldsWithSingleQuotes(p, b)
}

func unmarshalFieldsWithSingleQuotes(dest interface{}, b []byte) error {
	// Look through the JSON tags. If there is one containing single quotes,
	// unmarshal b again, into a map this time. Then unmarshal the value
	// at the map key corresponding to the tag, if any.
	var m map[string]json.RawMessage

	t := reflect.TypeOf(dest).Elem()
	v := reflect.ValueOf(dest).Elem()

	for i := 0; i &lt; t.NumField(); i++ {
		tag := t.Field(i).Tag.Get(&quot;json&quot;)
		if !strings.Contains(tag, &quot;&#39;&quot;) {
			continue
		}

		if m == nil {
			if err := json.Unmarshal(b, &amp;m); err != nil {
				return err
			}
		}

		if j, ok := m[tag]; ok {
			if err := json.Unmarshal(j, v.Field(i).Addr().Interface()); err != nil {
				return err
			}
		}
	}

	return nil
}

Try it on the playground: https://go.dev/play/p/aupACXorjOO

huangapple
  • 本文由 发表于 2022年12月9日 19:24:00
  • 转载请务必保留本文链接:https://go.coder-hub.com/74742431.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定