在GO中,编码/JSON软件包提供了内置的JSON套和删除功能,使我们能够轻松地将GO数据结构转换为JSON,反之亦然。但是,在某些情况下,默认行为不足,例如处理复杂的数据结构,管理自定义字段名称或处理特殊数据类型。本文旨在提供有关如何在GO中处理JSON的介绍,一如既往地进行自定义。
免责声明:我倾向于每天犯很多错误。如果您注意到一个,如果您让我知道,我将不胜感激。
- 所有代码段都可以在我的Github repo中找到。
理解JSON的宣教和宣传
JSON(JavaScript对象表示法)是一种轻巧的数据交互格式,广泛用于Web服务和应用程序之间的通信。它将数据表示为键值对,阵列和嵌套结构,为数据交换提供了简单且可读的格式。
在GO中,编码/JSON软件包是使用JSON数据的强大工具。它提供了将GO数据结构转换为JSON的元帅函数,并将unarshal函数转换为将JSON逐个序列化为GO数据结构。这些功能会自动处理最常见的情况,将GO struct字段映射到JSON对象属性,另一种情况。但是,当处理更复杂的方案或需要对序列化和避免过程进行细粒度的控制时,JSON标签以及自定义的封装和解放功能发光。
JSON标签
处理JSON的方式围绕结构和现场标签。结构代表要序列化或进行序列化的数据结构,结构的每个字段都与JSON表示中的属性相对应。使用JSON结构标签定义的字段标签提供了控制序列化和反序列行为的元数据。通过利用字段标签,我们可以为JSON属性指定自定义名称,处理空值,控制字段遗漏等。让我们看看以下标签的示例:
type Person struct {
Name string `json:"full_name"`
Age int `json:"-"`
Email string `json:"email,omitempty"`
}
-
名称映射到JSON属性full_name
-
年龄将被完全忽略(无论如何都不会出现)
-
电子邮件将映射到电子邮件,而OmitEmpty是一个保留的标志,可确保如果电子邮件字段为空(例如,包含其类型的零值),则不会在序列化后出现进入JSON
让我们看看这些标签在行动中的工作方式:
pp := []Person{
{
Name: "first person",
Age: 20,
Email: "first@person.com",
},
{
Name: "second person",
Age: 25,
Email: "",
},
}
b, _ := json.MarshalIndent(pp, "", "\t")
fmt.Println(string(b))
[
{
"full_name": "first person",
"email": "first@person.com"
},
{
"full_name": "second person"
}
]
- 请注意,如果仅用用作JSON标签,则将被视为字段名称,而不是被视为标志。
type Person struct {
Name string `json:"omitempty"`
}
pp := []Person{
{
Name: "first person",
},
{
Name: "second person",
},
}
b, _ := json.MarshalIndent(pp, "", "\t")
fmt.Println(string(b))
[
{
"omitempty": "first person",
},
{
"omitempty": "second person"
}
]
注意:**重复的名称将无法静静地串行(例如,尚无错误,**无重复字段的出现在输出中),尽管您可能会被警告通过静态代码分析工具或Linter。
JSON标签(或者让我们说任何自定义标签)可以在运行时通过反射如下:
func getFieldTags(t reflect.Type) map[string][]string {
if t.Kind() != reflect.Struct {
panic(fmt.Sprintf("t should be struct but is %s", t))
}
tags := make(map[string][]string)
for i := 0; i < t.NumField(); i++ {
f := t.Field(i)
jTags := strings.SplitN(f.Tag.Get("json"), ",", -1)
tags[f.Name] = jTags
}
return tags
}
func main() {
tags := getFieldTags(reflect.TypeOf(Person{}))
fmt.Printf("%+v\n", tags) // map[Age:[-] Email:[email omitempty] Name:[full_name]]
}
定制的JSON编制/删除
为了实现自定义JSON元帅/Unmarshaler,您必须相应地实现JSON.MARSHALER或JSON.UNMARMARSHALER接口。让我们看看这些接口:
// Marshaler is the interface implemented by types that
// can marshal themselves into valid JSON.
type Marshaler interface {
MarshalJSON() ([]byte, error)
}
// Unmarshaler is the interface implemented by types
// that can unmarshal a JSON description of themselves.
// The input can be assumed to be a valid encoding of
// a JSON value. UnmarshalJSON must copy the JSON data
// if it wishes to retain the data after returning.
//
// By convention, to approximate the behavior of Unmarshal itself,
// Unmarshalers implement UnmarshalJSON([]byte("null")) as a no-op.
type Unmarshaler interface {
UnmarshalJSON([]byte) error
}
让我们必须呼叫一些远程API,以获取有关某些产品的信息,为了示例,让我们假设API响应如下:
{
"products": [
"prod A",
"prod B",
"prod C"
],
"prices": [
99.99,
199.99,
299.99
]
}
我们知道(并期望)相同的索引包含有关某个产品的信息,例如价格[0]属于产品[0]。为了更好地处理这些数据,我们希望(删除)API响应到以下结构中:
type Product struct {
Name string
Price float64
}
type Cargo struct {
Products []Product
}
为了使其起作用,我们需要为目的地(此处货物)结构声明一个umarshaljson方法:
func main() {
// our imaginary API
d := GetProductPrices()
cargo := &Cargo{}
err := json.Unmarshal(d, cargo)
if err != nil {
log.Fatal("failed to unmarshal to cargo:", err)
}
fmt.Printf("%+v\n", cargo)
}
func (c *Cargo) UnmarshalJSON(d []byte) error {
...
}
为了对我们的数据进行精细的控制,我们可以将其暂时将其放入任何所需的中介结构中:
func (c *Cargo) UnmarshalJSON(d []byte) error {
// desired intermediary structure
type temp struct {
Products []string `json:"products"`
Prices []float64 `json:"prices"`
}
tmp := &temp{}
err := json.Unmarshal(d, tmp)
if err != nil {
return fmt.Errorf("failed to marshal JSON in temp: %w", err)
}
...
}
,或者我们甚至可以使用完全可扩展的数据结构,例如map [string] any:
func (c *Cargo) UnmarshalJSON(d []byte) error {
// desired intermediary structure
tmp := make(map[string]any)
err := json.Unmarshal(d, &tmp)
if err != nil {
return fmt.Errorf("failed to marshal JSON in temp: %w", err)
}
...
}
拥有供应数据已准备好进行进一步处理,下一步是将其转换为我们想要的任何结构:
func (c *Cargo) UnmarshalJSON(d []byte) error {
type temp struct {
Products []string `json:"products"`
Prices []float64 `json:"prices"`
}
tmp := &temp{}
err := json.Unmarshal(d, tmp)
if err != nil {
return fmt.Errorf("failed to marshal JSON in temp: %w", err)
}
if len(tmp.Prices) != len(tmp.Products) {
return fmt.Errorf("length of products (%d) and prices (%d) does not match", len(tmp.Products), len(tmp.Prices))
}
for i := 0; i < len(tmp.Products); i++ {
c.Products = append(c.Products, Product{Name: tmp.Products[i], Price: tmp.Prices[i]})
}
return nil
}
最终,我们可以期望拥有一个完善的数据,如以下:
{
"Products": [
{
"Name": "prod A",
"Price": 100
},
{
"Name": "prod B",
"Price": 200
},
{
"Name": "prod C",
"Price": 300
}
]
}
当我尝试这些东西时,我提出了一个有趣的问题,这可能也可能困扰您:
如果我们的结构不实现JSON.MARSHALER或JSON.UNMARMARSHALER界面,Go仍然可以找到一种方法来序列化并进行序列化至我们的结构。怎么可能?
好吧,在引擎盖下,GO的真正做法是,它看起来可以查看给定的结构是否实现了这些接口中的任何一个。如何?你可能会问。好吧,再次感谢反思:
// json/decode.go
// check if a certain type implements "Unmarshaler" (v is reflect.Value)
if v.Type().NumMethod() > 0 && v.CanInterface() {
if u, ok := v.Interface().(Unmarshaler); ok {
return u, nil, reflect.Value{}
}
// json/encode.go
// check if a certain type implements "Marshaler" (t is reflect.Type)
marshalerType = reflect.TypeOf((*Marshaler)(nil)).Elem()
if t.Kind() != reflect.Pointer && allowAddr && reflect.PointerTo(t).Implements(marshalerType) {
return newCondAddrEncoder(addrMarshalerEncoder, newTypeEncoder(t, false))
}
如果类型无法通过测试(例如,没有实现JSON.MARSHALER和JSON.UNMARMALSHALER),则落在计划B之类的类似上。
说实话,我主要使用自定义的mashaling/删除功能来改变程序中嵌入的JSON文件,首先读取JSON文件,将其验证为我想要的任何结构,然后将其序列化回新的.json文件。这样的东西:
type CustomStruct struct {
// whatever field I want at the end
}
func (c *CustomStruct) UnmarshalJSON(d []byte) error {
// custom JSON unmarshalling
}
func main() {
// read, change, write
d, err := os.ReadFile("old.json")
if err != nil {
log.Fatal("failed to read file:", err)
}
st := &CustomStruct{}
err = json.Unmarshal(d, st)
if err != nil {
log.Fatal("failed to unmarshal:", err)
}
b, err := json.MarshalIndent(st, "", "\t")
if err != nil {
log.Fatal("failed to marshal struct:", err)
}
err = os.WriteFile("new.json", b, 0644)
if err != nil {
log.Fatal("failed to write new file:", err)
}
}
请注意,我在这里没有考虑两件事,一个是以流方式读取文件(因此,我不会通过将文件读取为一个整体),另一个是 partial in n of json中的。
,我们可能会看看这些主题。结论
自定义的JSON套和删除GO中的宣传使我们能够克服标准库提供的默认JSON处理功能的局限性。通过利用自定义的元帅和未宣传的人,我们可以很好地调整序列化和避免过程,从而使我们的代码更具表现力,高效和针对特定用例量身定制。我们已经探索了从实现技术到现实世界的示例,探索了自定义JSON处理的各个方面。我希望您喜欢我们的旅程。感谢您提前的支持!