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

Go语言JSON动态类型反序列化的结构体重构复用方案咨询

JSON类型歧义处理的结构体重构方案

问题背景

需要处理存在类型歧义的JSON payload:service和serviceAttribute字段既可能是单个对象,也可能是对象数组,最终要统一转换为数组格式的结构体,避免后续代码重复处理类型解析。目前已实现可行代码,但存在结构体重复定义问题,且实际场景为深层嵌套的复杂结构,希望优化结构体简洁性、减少重复,咨询更优的结构体重构/复用方案、当前方案合理性及改进方向。

示例JSON

单对象格式

{
    "service": {
        "serviceName": "serviceName",
        "serviceAttribute": {
            "name": "name",
            "value": "value"
        }
    }
}

数组格式

{
    "service": [
        {
            "serviceName":"serviceName",
            "serviceAttribute": [
                {
                    "name":"name",
                    "value":"value"
                }
            ]
        }
    ]
}

目标输出格式

{
    "service": [
        {
            "serviceName":"serviceName",
            "serviceAttribute": [
                {
                    "name":"name",
                    "value":"value"
                }
            ]
        }
    ]
}

现有实现代码

package main

import (
    "encoding/json"
    "fmt"
)

type InType struct {
    Service json.RawMessage `json:"service,omitempty"`
}

type Service struct {
    ServiceName      string          `json:"serviceName,omitempty"`
    ServiceAttribute json.RawMessage `json:"serviceAttribute,omitempty"`
}

type ServiceAttribute struct {
    Name  string `json:"name,omitempty"`
    Value string `json:"value,omitempty"`
}

type OutType struct {
    Service []*OutService `json:"service,omitempty"`
}

type OutService struct {
    ServiceName      string              `json:"serviceName,omitempty"`
    ServiceAttribute []*ServiceAttribute `json:"serviceAttribute,omitempty"`
}

func main() {
    jsonDataSingle := `
    {
        "service": {
            "serviceName": "serviceName",
            "serviceAttribute": {
                "name": "name",
                "value": "value"
            }
        }
    }
    `

    jsonDataCollection := `
    {
        "service": [
            {
                "serviceName":"serviceName",
                "serviceAttribute": [
                    {
                        "name":"name",
                        "value":"value"
                    }
                ]
            }
        ]
    }
    `
    fmt.Println("processing single")
    err := UnmarshalAndResolve([]byte(jsonDataSingle))
    if err != nil {
        fmt.Println(err)
    }

    fmt.Println("----------------------")

    fmt.Println("processing collection")
    err = UnmarshalAndResolve([]byte(jsonDataCollection))
    if err != nil {
        fmt.Println(err)
    }
}

func UnmarshalAndResolve(data []byte) error {
    // loading part
    var in *InType

    err := json.Unmarshal(data, &in)
    if err != nil {
        return err
    }

    // resolving part
    service := resolveServiceType(in.Service)

    outServices := []*OutService{}
    for _, s := range service {

        serviceAttributes := resolveServiceAttributeType(s.ServiceAttribute)

        os := &OutService{
            ServiceName:      s.ServiceName,
            ServiceAttribute: serviceAttributes,
        }

        outServices = append(outServices, os)
    }

    // creating object with concrete types so i wont need anymore resolving steps further in the code
    out := &OutType{
        Service: outServices,
    }

    for _, s := range out.Service {
        fmt.Println(s.ServiceName)
        for _, sa := range s.ServiceAttribute {
            fmt.Println(sa.Name)
            fmt.Println(sa.Value)

        }
    }

    return nil
}

func resolveServiceType(service json.RawMessage) []*Service {
    fmt.Println("resolveServiceType")
    var single *Service
    var collection []*Service

    switch {
    case json.Unmarshal(service, &single) == nil:
        fmt.Println("single")
        return []*Service{single}
    case json.Unmarshal(service, &collection) == nil:
        fmt.Println("collection")
        return collection
    default:
        return []*Service{}
    }
}

func resolveServiceAttributeType(serviceAttribute json.RawMessage) []*ServiceAttribute {
    fmt.Println("resolveServiceAttributeType")
    var single *ServiceAttribute
    var collection []*ServiceAttribute

    switch {
    case json.Unmarshal(serviceAttribute, &single) == nil:
        fmt.Println("single")
        return []*ServiceAttribute{single}
    case json.Unmarshal(serviceAttribute, &collection) == nil:
        fmt.Println("collection")
        return collection
    default:
        fmt.Println("default")
        return []*ServiceAttribute{}
    }
}

方案评估与优化建议

当前方案合理性

当前方案通过json.RawMessage暂存歧义字段,再分别尝试解析为单个对象或数组,最终统一转为数组格式,逻辑清晰且能解决问题。但缺点明显:

  • 结构体重复定义:Service和OutService仅ServiceAttribute字段类型不同,深层嵌套场景下会产生大量重复代码。
  • 解析逻辑重复:resolveServiceType和resolveServiceAttributeType逻辑完全一致,仅处理的结构体类型不同,代码冗余。

优化方案

1. 自定义ArrayOrSingle泛型类型统一解析逻辑

利用Go 1.18+的泛型特性,封装通用的ArrayOrSingle类型,自动处理单个对象转数组的逻辑,避免重复编写解析函数。

package main

import (
	"encoding/json"
	"fmt"
)

// ArrayOrSingle 处理单个对象或数组的通用类型
type ArrayOrSingle[T any] []T

func (a *ArrayOrSingle[T]) UnmarshalJSON(data []byte) error {
	// 先尝试解析为数组
	var arr []T
	if err := json.Unmarshal(data, &arr); err == nil {
		*a = arr
		return nil
	}

	// 再尝试解析为单个对象
	var single T
	if err := json.Unmarshal(data, &single); err == nil {
		*a = []T{single}
		return nil
	}

	return fmt.Errorf("failed to unmarshal as single or array of %T", single)
}

// 基础结构体定义,无重复
type ServiceAttribute struct {
	Name  string `json:"name,omitempty"`
	Value string `json:"value,omitempty"`
}

type Service struct {
	ServiceName      string                  `json:"serviceName,omitempty"`
	ServiceAttribute ArrayOrSingle[ServiceAttribute] `json:"serviceAttribute,omitempty"`
}

type Root struct {
	Service ArrayOrSingle[Service] `json:"service,omitempty"`
}

func main() {
	jsonDataSingle := `
    {
        "service": {
            "serviceName": "serviceName",
            "serviceAttribute": {
                "name": "name",
                "value": "value"
            }
        }
    }
    `

	jsonDataCollection := `
    {
        "service": [
            {
                "serviceName":"serviceName",
                "serviceAttribute": [
                    {
                        "name":"name",
                        "value":"value"
                    }
                ]
            }
        ]
    }
    `

	fmt.Println("processing single")
	var rootSingle Root
	err := json.Unmarshal([]byte(jsonDataSingle), &rootSingle)
	if err != nil {
		fmt.Println(err)
	} else {
		printRoot(rootSingle)
	}

	fmt.Println("----------------------")

	fmt.Println("processing collection")
	var rootCollection Root
	err = json.Unmarshal([]byte(jsonDataCollection), &rootCollection)
	if err != nil {
		fmt.Println(err)
	} else {
		printRoot(rootCollection)
	}
}

func printRoot(root Root) {
	for _, s := range root.Service {
		fmt.Println(s.ServiceName)
		for _, sa := range s.ServiceAttribute {
			fmt.Println(sa.Name)
			fmt.Println(sa.Value)
		}
	}
}

2. 优化点说明

  • 消除结构体重复:仅保留基础的ServiceAttribute和Service结构体,通过ArrayOrSingle泛型类型处理歧义字段,无需定义InType、OutType、OutService等重复结构体。
  • 统一解析逻辑:将重复的解析逻辑封装到ArrayOrSingle的UnmarshalJSON方法中,所有需要处理单个/数组歧义的字段都可以复用该类型,深层嵌套场景下优势更明显。
  • 简化代码流程:直接将JSON解析到最终的Root结构体,无需额外的中间转换步骤,代码更简洁易维护。

额外改进方向

  • 错误处理增强:当前ArrayOrSingle的错误处理可以更详细,返回具体的解析失败原因,便于调试。
  • 空值处理:如果字段可能为null,可以在UnmarshalJSON中添加对null的判断,返回空数组或根据需求处理。
  • 性能优化:如果JSON数据量极大,重复解析两次(数组和单个对象)可能有性能损耗,可以先通过前缀判断(如[开头则是数组)减少一次解析尝试。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.01 22:05:24