You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Go嵌套结构体同JSON标签字段重命名序列化问题

解决方案:自定义序列化实现嵌套结构体字段的JSON标签重命名

嘿,这个问题我之前也碰到过,默认的encoding/json确实没法直接修改嵌入结构体字段的JSON标签,但我们可以通过自定义MarshalJSON方法来实现需求,不用写大量冗余的字段复制代码,一起来看看吧~

核心思路

默认情况下,当外层结构体(Bar)和嵌入结构体(Foo)有同名字段时,JSON序列化会优先使用外层字段的值,且不会输出嵌入的同名字段。要保留两个ID并修改嵌入字段的标签,我们需要手动控制序列化过程,把Foo的ID映射到foo_id字段,同时保留Bar自己的id。

方案一:给Bar实现自定义MarshalJSON方法

这是最简洁且易维护的方案,我们可以在序列化Bar时,临时构建一个符合JSON输出要求的结构体,把原Bar和Foo的字段映射过去:

package main
import (
 "encoding/json"
 "fmt"
)

type Foo struct {
 ID int `json:"id"`
 Stuff string `json:"stuff"`
}

type Bar struct {
 ID int `json:"id"`
 Foo
 OtherStuff string `json:"other_stuff"`
}

// 给Bar实现自定义的MarshalJSON方法
func (b Bar) MarshalJSON() ([]byte, error) {
    // 定义一个临时结构体,包含我们需要的所有字段和正确的JSON标签
    type barJSON struct {
        ID         int    `json:"id"`
        FooID      int    `json:"foo_id"` // 把Foo的ID映射到这个字段
        Stuff      string `json:"stuff"`
        OtherStuff string `json:"other_stuff"`
    }

    // 将Bar和Foo的字段值赋值给临时结构体
    temp := barJSON{
        ID:         b.ID,
        FooID:      b.Foo.ID,
        Stuff:      b.Foo.Stuff,
        OtherStuff: b.OtherStuff,
    }

    // 序列化临时结构体
    return json.Marshal(temp)
}

func main() {
 foo := Foo{ID: 123, Stuff: "abc" }
 bar := Bar{ID: 456, OtherStuff: "xyz", Foo: foo }
 fooJsonStr, _ := json.Marshal(foo)
 barJsonStr, _ := json.Marshal(bar)
 fmt.Printf("Foo: %s\n", fooJsonStr)
 fmt.Printf("Bar: %s\n", barJsonStr)
}

运行结果

Foo: {"id":123,"stuff":"abc"}
Bar: {"id":456,"foo_id":123,"stuff":"abc","other_stuff":"xyz"}

方案优势

  • 完全控制JSON输出结构,精准满足需求
  • 不需要修改原结构体的定义,保持代码原有结构
  • 代码简洁,仅在序列化时做一次字段映射,避免冗余

方案二:反射实现通用序列化(适合字段较多的场景)

如果你的Foo或Bar结构体有大量字段,手动映射太麻烦,可以用反射自动遍历字段并修改标签。不过这个方案会增加代码复杂度,性能略有下降,适合字段较多的情况:

import "reflect"

func (b Bar) MarshalJSON() ([]byte, error) {
    // 创建一个map来存储所有需要序列化的键值对
    data := make(map[string]interface{})

    // 处理Bar自己的字段
    barVal := reflect.ValueOf(b)
    barType := reflect.TypeOf(b)
    for i := 0; i < barType.NumField(); i++ {
        field := barType.Field(i)
        // 跳过嵌入的Foo字段,后面单独处理
        if field.Anonymous && field.Type == reflect.TypeOf(Foo{}) {
            continue
        }
        jsonTag := field.Tag.Get("json")
        if jsonTag == "-" {
            continue
        }
        data[jsonTag] = barVal.Field(i).Interface()
    }

    // 处理Foo的字段,把ID改成foo_id
    fooVal := reflect.ValueOf(b.Foo)
    fooType := reflect.TypeOf(b.Foo)
    for i := 0; i < fooType.NumField(); i++ {
        field := fooType.Field(i)
        jsonTag := field.Tag.Get("json")
        if jsonTag == "-" {
            continue
        }
        // 替换ID的标签为foo_id
        if jsonTag == "id" {
            jsonTag = "foo_id"
        }
        data[jsonTag] = fooVal.Field(i).Interface()
    }

    // 序列化map
    return json.Marshal(data)
}

这个方案会自动遍历所有字段,不用手动添加每个字段,适合结构体字段较多的场景,但可读性不如第一个方案。

总结

如果你的结构体字段不多,方案一是最优选择,代码简洁直观,容易维护;如果字段很多,再考虑用反射的方案来减少重复代码。

内容的提问来源于stack exchange,提问作者TPPZ

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.28 09:28:22