Golang动态JSON序列化与反序列化实践指南

Golang动态JSON序列化与反序列化实践指南

type General struct {
    Type string `json:"type"`
    LookUp []interface{} `json:"lookup"`
}

type Human struct {
    Name string `json:"name"`
    Feature string `json:"feature"`
}

type Animal struct {
    Name string `json:"name"`
    Type string `json:"type"`
    Example string `json:"example"`
}
str := `{ "type": "living", "lookup": [  {     "name": "human",    "feature": "think"  },  {   "name": "animal",   "type": "carnivores",   "example": "lion"  }, {   "name": "animal",   "type": "herbivores",   "example": "zebra"  } ] }`

基于 “name” 字段,我们如何进行动态的 JSON 反序列化与序列化?


更多关于Golang动态JSON序列化与反序列化实践指南的实战教程也可以访问 https://www.itying.com/category-94-b0.html

4 回复

以下是我项目中一个实际场景的示例。这里的字段是任意的,旨在复现我正在使用的模型。

实际问题是,我需要形成一个包含不同类型屏幕(如商业广告屏幕、统计屏幕等)的混合播放列表。

结构体如下:

type PlayList struct {
ID string `json:"id"`
…
Screens []interface `json:"screens"`
}
type Commercials struct {
Brand string `json:"brand"`
…
}
type Stats struct {
Product string `json:"product"   Growth int `json:"growth"`
…
}

播放列表中的屏幕字段可以包含数量不等的统计屏幕、商业广告屏幕等。

更多关于Golang动态JSON序列化与反序列化实践指南的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


方案一:重新审视问题领域。JSON数据是否真的必须是不同数据类型的混杂体?能否在JSON数据中将HumanAnimal分离?能否生成在读取时可预测的JSON数据?

方案二:重新思考数据结构。Human结构体是否必须与Animal结构体不同?两者是否都可以由Feature、Type和Example字段组成?换句话说,能否使用一个统一的单一结构体来解决问题?

方案三:寻找合适的第三方JSON包来解决问题。(在godoc.org、pkg.go.dev、github.com等网站搜索)

方案四:自行实现动态反序列化。关于此方案的详细解释,我将留给动态反序列化领域的专家来阐述……

如果我对你的问题理解正确,你有一个名为“PlayList”的容器,当它返回时包含一个Screen列表,而这些Screen的字段是可变的。

如果你每次都知道会返回哪些字段,可以创建一个“Screen”类型,并关联所有可能包含在“Screen”类型中的字段,然后用“omitempty”标签标记它们。

type Screen struct {
    Brand   string `json:"brand,omitempty"`
    Product string `json:"product,omitempty"`
    XAxis   int    `json:"x_axis,omitempty"`
    Spend   int    `json:"total_spend,omitempty"`
    // 等等...
}

你的容器结构体看起来会是这样。

type PlayList struct {
    ID      string   `json:"id"`
    Screens []Screen `json:"screens"`
}

如果你不知道Screen类型中可能返回哪些字段,那么情况就会变得稍微复杂一些。Golang是一种强类型语言,因此处理可预测的数据相当不错,但对于不可预测或非强类型的数据则表现不佳。

我最近不得不编写一个程序,它需要处理没有可预测字段的数据,并且仍然要能够生成结构体并对其进行反序列化。我发现唯一可靠的方法是进行代码生成,即解构JSON数据,确定数据类型、字段名和值,并根据这些信息创建一个struct.go文件。

我写了一个包来处理大部分繁重的工作。它并不完美,还需要很多改进。但我把它贴在这里,也许对你有帮助。

GitHub gnosthi/jsonDeStruct

一个解构JSON响应的包。通过在GitHub上创建账户来为gnosthi/jsonDeStruct的开发做出贡献。

在Go中实现基于字段值的动态JSON序列化与反序列化,可以使用json.RawMessage结合类型断言。以下是具体实现:

package main

import (
    "encoding/json"
    "fmt"
)

type General struct {
    Type   string          `json:"type"`
    LookUp []LookUpElement `json:"lookup"`
}

type LookUpElement struct {
    RawData json.RawMessage `json:"-"`
    Name    string          `json:"name"`
    Data    interface{}     `json:"-"`
}

type Human struct {
    Name    string `json:"name"`
    Feature string `json:"feature"`
}

type Animal struct {
    Name    string `json:"name"`
    Type    string `json:"type"`
    Example string `json:"example"`
}

// 自定义UnmarshalJSON实现动态反序列化
func (l *LookUpElement) UnmarshalJSON(data []byte) error {
    // 先解析出name字段
    var temp map[string]interface{}
    if err := json.Unmarshal(data, &temp); err != nil {
        return err
    }
    
    l.Name = temp["name"].(string)
    l.RawData = data
    
    // 根据name字段动态解析
    switch l.Name {
    case "human":
        var human Human
        if err := json.Unmarshal(data, &human); err != nil {
            return err
        }
        l.Data = human
    case "animal":
        var animal Animal
        if err := json.Unmarshal(data, &animal); err != nil {
            return err
        }
        l.Data = animal
    default:
        l.Data = temp
    }
    
    return nil
}

// 自定义MarshalJSON实现动态序列化
func (l LookUpElement) MarshalJSON() ([]byte, error) {
    if l.Data != nil {
        return json.Marshal(l.Data)
    }
    return l.RawData, nil
}

func main() {
    str := `{ "type": "living", "lookup": [  {     "name": "human",    "feature": "think"  },  {   "name": "animal",   "type": "carnivores",   "example": "lion"  }, {   "name": "animal",   "type": "herbivores",   "example": "zebra"  } ] }`
    
    // 反序列化
    var general General
    if err := json.Unmarshal([]byte(str), &general); err != nil {
        panic(err)
    }
    
    // 访问解析后的数据
    for _, item := range general.LookUp {
        switch v := item.Data.(type) {
        case Human:
            fmt.Printf("Human: %+v\n", v)
        case Animal:
            fmt.Printf("Animal: %+v\n", v)
        }
    }
    
    // 序列化
    output, err := json.MarshalIndent(general, "", "  ")
    if err != nil {
        panic(err)
    }
    fmt.Println("Serialized JSON:")
    fmt.Println(string(output))
}

另一种更灵活的方法是使用mapstructure库:

package main

import (
    "encoding/json"
    "fmt"
    "github.com/mitchellh/mapstructure"
)

type General struct {
    Type   string        `json:"type"`
    LookUp []interface{} `json:"lookup"`
}

type Human struct {
    Name    string `json:"name" mapstructure:"name"`
    Feature string `json:"feature" mapstructure:"feature"`
}

type Animal struct {
    Name    string `json:"name" mapstructure:"name"`
    Type    string `json:"type" mapstructure:"type"`
    Example string `json:"example" mapstructure:"example"`
}

func main() {
    str := `{ "type": "living", "lookup": [  {     "name": "human",    "feature": "think"  },  {   "name": "animal",   "type": "carnivores",   "example": "lion"  }, {   "name": "animal",   "type": "herbivores",   "example": "zebra"  } ] }`
    
    // 反序列化
    var data map[string]interface{}
    if err := json.Unmarshal([]byte(str), &data); err != nil {
        panic(err)
    }
    
    lookup := data["lookup"].([]interface{})
    var result []interface{}
    
    for _, item := range lookup {
        m := item.(map[string]interface{})
        
        switch m["name"] {
        case "human":
            var human Human
            mapstructure.Decode(m, &human)
            result = append(result, human)
        case "animal":
            var animal Animal
            mapstructure.Decode(m, &animal)
            result = append(result, animal)
        }
    }
    
    general := General{
        Type:   data["type"].(string),
        LookUp: result,
    }
    
    // 访问数据
    for _, item := range general.LookUp {
        switch v := item.(type) {
        case Human:
            fmt.Printf("Human: %+v\n", v)
        case Animal:
            fmt.Printf("Animal: %+v\n", v)
        }
    }
    
    // 序列化
    output, err := json.MarshalIndent(general, "", "  ")
    if err != nil {
        panic(err)
    }
    fmt.Println("Serialized JSON:")
    fmt.Println(string(output))
}

使用标准库的反射方案:

package main

import (
    "encoding/json"
    "fmt"
    "reflect"
)

type General struct {
    Type   string        `json:"type"`
    LookUp []interface{} `json:"lookup"`
}

func DynamicUnmarshal(data []byte) (*General, error) {
    var raw map[string]json.RawMessage
    if err := json.Unmarshal(data, &raw); err != nil {
        return nil, err
    }
    
    var general General
    if err := json.Unmarshal(raw["type"], &general.Type); err != nil {
        return nil, err
    }
    
    var lookups []json.RawMessage
    if err := json.Unmarshal(raw["lookup"], &lookups); err != nil {
        return nil, err
    }
    
    for _, lookup := range lookups {
        var base map[string]interface{}
        if err := json.Unmarshal(lookup, &base); err != nil {
            return nil, err
        }
        
        switch base["name"] {
        case "human":
            var human Human
            if err := json.Unmarshal(lookup, &human); err != nil {
                return nil, err
            }
            general.LookUp = append(general.LookUp, human)
        case "animal":
            var animal Animal
            if err := json.Unmarshal(lookup, &animal); err != nil {
                return nil, err
            }
            general.LookUp = append(general.LookUp, animal)
        }
    }
    
    return &general, nil
}

func DynamicMarshal(general *General) ([]byte, error) {
    var lookups []interface{}
    
    for _, item := range general.LookUp {
        v := reflect.ValueOf(item)
        switch v.Type().Name() {
        case "Human":
            lookups = append(lookups, item.(Human))
        case "Animal":
            lookups = append(lookups, item.(Animal))
        }
    }
    
    result := map[string]interface{}{
        "type":   general.Type,
        "lookup": lookups,
    }
    
    return json.MarshalIndent(result, "", "  ")
}

这些示例展示了如何基于"name"字段实现动态的JSON处理,第一种方案通过自定义UnmarshalJSONMarshalJSON方法提供了最完整的控制,第二种方案使用mapstructure库简化了映射过程,第三种方案使用反射提供了另一种实现思路。

回到顶部