0

0

在 Go 中维护未解析的 JSON 字段的最佳实践

霞舞

霞舞

发布时间:2025-10-29 13:10:16

|

906人浏览过

|

来源于php中文网

原创

在 go 中维护未解析的 json 字段的最佳实践

本文介绍了在 Go 语言中使用 `encoding/json` 包处理 JSON 数据时,如何保留未解析的动态字段。针对需要在 Go 结构体中解码、操作后再编码回 JSON,但又不想丢失原始 JSON 中结构体未定义的字段的情况,提供了使用 `json.RawMessage` 类型和自定义 `Unmarshaler`/`Marshaler` 接口的解决方案,并简要提及了其他库(如 `mgo/bson`)中类似功能的实现。

在使用 Go 语言处理 JSON 数据时,经常会遇到这样的场景:我们需要将 JSON 数据解码到 Go 结构体中进行操作,然后再将修改后的结构体编码回 JSON。然而,原始 JSON 数据中可能包含一些我们事先未知的、或者暂时不需要处理的字段。如果直接使用 json.Unmarshal 将 JSON 数据解码到结构体中,这些未定义的字段就会被忽略,导致信息丢失。

那么,如何在 Go 中既能方便地使用结构体进行数据操作,又能保留原始 JSON 数据中的未解析字段呢?本文将介绍几种可行的方案。

使用 json.RawMessage 类型

json.RawMessage 是 encoding/json 包提供的一个类型,它本质上是 []byte 的别名。我们可以将结构体中的某个字段定义为 json.RawMessage 类型,这样在解码 JSON 数据时,该字段对应的 JSON 片段会被原封不动地存储为字节数组,而不会被进一步解析。

例如,假设我们有以下 JSON 数据:

{
  "name": "Joe Smith",
  "age": 42,
  "phone": "614-555-1212",
  "debug": true,
  "codeword": "wolf"
}

我们希望将其中的 name、age 和 address 字段解码到结构体中,而保留其他字段。可以定义如下结构体:

package main

import (
    "encoding/json"
    "fmt"
)

type Person struct {
    Name    string          `json:"name"`
    Age     uint            `json:"age"`
    Address json.RawMessage `json:"address"` // 包含 "phone", "debug", "codeword" 等字段
}

func main() {
    jsonData := []byte(`{ "name": "Joe Smith", "age": 42, "address": { "phone": "614-555-1212", "debug": true, "codeword": "wolf" } }`)

    var p Person
    err := json.Unmarshal(jsonData, &p)
    if err != nil {
        fmt.Println("Error:", err)
        return
    }

    fmt.Printf("Name: %s\n", p.Name)
    fmt.Printf("Age: %d\n", p.Age)
    fmt.Printf("Address (Raw): %s\n", string(p.Address)) // 输出原始 JSON 片段

    // 修改 Age
    p.Age++

    // 重新编码为 JSON
    newData, err := json.Marshal(p)
    if err != nil {
        fmt.Println("Error:", err)
        return
    }

    fmt.Printf("New JSON: %s\n", string(newData))
}

在这个例子中,Address 字段被定义为 json.RawMessage 类型。在解码 JSON 数据时,address 字段对应的 JSON 对象 { "phone": "614-555-1212", "debug": true, "codeword": "wolf" } 会被存储到 p.Address 中。当我们重新编码 Person 结构体为 JSON 时,address 字段的内容会被原封不动地放回 JSON 数据中。

注意事项:

  • 使用 json.RawMessage 时,需要确保 JSON 数据中的对应字段是一个有效的 JSON 片段。
  • 如果需要对 json.RawMessage 中存储的 JSON 数据进行进一步处理,需要手动进行解码。

自定义 Unmarshaler 和 Marshaler 接口

另一种方案是实现自定义的 Unmarshaler 和 Marshaler 接口。这种方案更加灵活,可以实现更复杂的逻辑,但同时也需要编写更多的代码。

Kaiber
Kaiber

Kaiber是一个视频生成引擎,用户可以根据自己的图片或文字描述创建视频

下载

Unmarshaler 接口定义了 UnmarshalJSON 方法,该方法用于将 JSON 数据解码到对象中。Marshaler 接口定义了 MarshalJSON 方法,该方法用于将对象编码为 JSON 数据。

我们可以实现自定义的 UnmarshalJSON 方法,将 JSON 数据解码到一个 map[string]interface{} 中,然后将需要处理的字段提取到结构体中,并将剩余的字段存储到 map[string]interface{} 中。在实现 MarshalJSON 方法时,将结构体中的字段和 map[string]interface{} 中的字段合并,然后编码为 JSON 数据。

以下是一个示例:

package main

import (
    "encoding/json"
    "fmt"
)

type Person struct {
    Name    string                 `json:"name"`
    Age     uint                   `json:"age"`
    Extra   map[string]interface{} `json:"-"` // 忽略,用于存储未解析的字段
}

// UnmarshalJSON 自定义解码逻辑
func (p *Person) UnmarshalJSON(data []byte) error {
    // 定义一个中间类型,避免无限递归
    type Alias Person
    aux := &struct {
        *Alias
    }{
        Alias: (*Alias)(p),
    }

    // 先将 JSON 数据解码到 map[string]interface{} 中
    var tmp map[string]interface{}
    if err := json.Unmarshal(data, &tmp); err != nil {
        return err
    }

    // 将需要处理的字段提取到结构体中
    if name, ok := tmp["name"].(string); ok {
        p.Name = name
        delete(tmp, "name")
    }
    if age, ok := tmp["age"].(float64); ok { // 注意:JSON 中数字默认是 float64
        p.Age = uint(age)
        delete(tmp, "age")
    }

    // 将剩余的字段存储到 Extra 中
    p.Extra = tmp
    return nil
}

// MarshalJSON 自定义编码逻辑
func (p *Person) MarshalJSON() ([]byte, error) {
    // 创建一个新的 map,包含结构体中的字段和 Extra 中的字段
    tmp := make(map[string]interface{})
    tmp["name"] = p.Name
    tmp["age"] = p.Age
    for k, v := range p.Extra {
        tmp[k] = v
    }

    // 将 map 编码为 JSON 数据
    return json.Marshal(tmp)
}

func main() {
    jsonData := []byte(`{ "name": "Joe Smith", "age": 42, "phone": "614-555-1212", "debug": true, "codeword": "wolf" }`)

    var p Person
    err := json.Unmarshal(jsonData, &p)
    if err != nil {
        fmt.Println("Error:", err)
        return
    }

    fmt.Printf("Name: %s\n", p.Name)
    fmt.Printf("Age: %d\n", p.Age)
    fmt.Printf("Extra: %+v\n", p.Extra)

    // 修改 Age
    p.Age++

    // 添加新的字段
    p.Extra["new_field"] = "new_value"

    // 重新编码为 JSON
    newData, err := json.Marshal(p)
    if err != nil {
        fmt.Println("Error:", err)
        return
    }

    fmt.Printf("New JSON: %s\n", string(newData))
}

在这个例子中,Person 结构体包含一个 Extra 字段,类型为 map[string]interface{}。UnmarshalJSON 方法将 JSON 数据解码到 map[string]interface{} 中,并将 name 和 age 字段提取到结构体中,将剩余的字段存储到 Extra 中。MarshalJSON 方法将结构体中的字段和 Extra 中的字段合并,然后编码为 JSON 数据。

注意事项:

  • 在实现 UnmarshalJSON 方法时,需要注意处理 JSON 数据中的类型转换。例如,JSON 中的数字默认是 float64 类型,需要将其转换为 uint 类型。
  • 为了避免无限递归,可以在 UnmarshalJSON 方法中使用一个中间类型。

其他库

除了 encoding/json 包,还有一些其他的库也提供了类似的功能。例如,labix.org/v2/mgo/bson 库的 inline tag flag 可以将结构体中的字段内联到 BSON 文档中,从而实现保留未解析字段的功能。

总结

本文介绍了在 Go 语言中使用 encoding/json 包处理 JSON 数据时,如何保留未解析的动态字段。我们可以使用 json.RawMessage 类型或者自定义 Unmarshaler 和 Marshaler 接口来实现这个功能。选择哪种方案取决于具体的应用场景和需求。如果只需要简单地保留未解析的字段,可以使用 json.RawMessage 类型。如果需要实现更复杂的逻辑,可以自定义 Unmarshaler 和 Marshaler 接口。

相关专题

更多
json数据格式
json数据格式

JSON是一种轻量级的数据交换格式。本专题为大家带来json数据格式相关文章,帮助大家解决问题。

403

2023.08.07

json是什么
json是什么

JSON是一种轻量级的数据交换格式,具有简洁、易读、跨平台和语言的特点,JSON数据是通过键值对的方式进行组织,其中键是字符串,值可以是字符串、数值、布尔值、数组、对象或者null,在Web开发、数据交换和配置文件等方面得到广泛应用。本专题为大家提供json相关的文章、下载、课程内容,供大家免费下载体验。

529

2023.08.23

jquery怎么操作json
jquery怎么操作json

操作的方法有:1、“$.parseJSON(jsonString)”2、“$.getJSON(url, data, success)”;3、“$.each(obj, callback)”;4、“$.ajax()”。更多jquery怎么操作json的详细内容,可以访问本专题下面的文章。

308

2023.10.13

go语言处理json数据方法
go语言处理json数据方法

本专题整合了go语言中处理json数据方法,阅读专题下面的文章了解更多详细内容。

74

2025.09.10

string转int
string转int

在编程中,我们经常会遇到需要将字符串(str)转换为整数(int)的情况。这可能是因为我们需要对字符串进行数值计算,或者需要将用户输入的字符串转换为整数进行处理。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

313

2023.08.02

golang结构体相关大全
golang结构体相关大全

本专题整合了golang结构体相关大全,想了解更多内容,请阅读专题下面的文章。

194

2025.06.09

golang结构体方法
golang结构体方法

本专题整合了golang结构体相关内容,请阅读专题下面的文章了解更多。

186

2025.07.04

硬盘接口类型介绍
硬盘接口类型介绍

硬盘接口类型有IDE、SATA、SCSI、Fibre Channel、USB、eSATA、mSATA、PCIe等等。详细介绍:1、IDE接口是一种并行接口,主要用于连接硬盘和光驱等设备,它主要有两种类型:ATA和ATAPI,IDE接口已经逐渐被SATA接口;2、SATA接口是一种串行接口,相较于IDE接口,它具有更高的传输速度、更低的功耗和更小的体积;3、SCSI接口等等。

994

2023.10.19

php源码安装教程大全
php源码安装教程大全

本专题整合了php源码安装教程,阅读专题下面的文章了解更多详细内容。

150

2025.12.31

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
WEB前端教程【HTML5+CSS3+JS】
WEB前端教程【HTML5+CSS3+JS】

共101课时 | 8.1万人学习

JS进阶与BootStrap学习
JS进阶与BootStrap学习

共39课时 | 3.1万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号