Golang中如何实现JSON序列化时忽略nil值(非自定义结构体)

Golang中如何实现JSON序列化时忽略nil值(非自定义结构体) 我使用 Azure SDK 中的一些类型,想将其转换为 JSON 作为工具的输出,但由于存在许多空值,输出内容相当冗杂,我只想要有意义的数据。

我知道可以创建自己的结构体并使用 omitempty 标签,但这些结构体来自其他包。

有什么想法吗?

5 回复

你的问题不够明确。

  • 你是只想将感兴趣的字段解析到结构体中吗?
  • 还是你想将数据解析到现有结构体中,但只打印该结构体中非空字段?

更多关于Golang中如何实现JSON序列化时忽略nil值(非自定义结构体)的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


我也是这么想的。

不过,我有个主意,我会使用 https://github.com/fatih/structs 将结构体转换为映射,然后将其序列化为 JSON 文本。

Azure SDK 返回的结构体不受我控制

我正在编写一个命令行工具来协助完成其他使用该 SDK 的任务。

我的命令行工具会将这些 SDK 结构体序列化为 JSON 格式,并将其响应写入标准输出。默认情况下,无论字段值如何,所有字段都会被写入 JSON 输出。

我希望省略 nil/null 值。

所以您的两个选项都不适用。我没有任何需要解析的内容。SDK 已经完成了解析工作。

请参考 json.Marshal 的文档:

“omitempty” 选项指定当字段值为空时应从编码中省略该字段。空值的定义包括:false、0、nil 指针、nil 接口值,以及任何空数组、切片、映射或字符串。

但如果你无法控制该类型,那就无能为力了。

你可以尝试使用类似 jq 的工具来处理 JSON。

在Go语言中,处理来自第三方包(如Azure SDK)的结构体时,确实无法直接修改其字段标签。不过,可以通过几种方式实现JSON序列化时忽略nil值。

方法1:使用自定义MarshalJSON方法

最直接的方式是为这些类型创建包装器并实现自定义的MarshalJSON方法:

package main

import (
    "encoding/json"
    "reflect"
)

// 创建通用函数处理nil值过滤
func marshalWithoutNil(v interface{}) ([]byte, error) {
    return json.Marshal(removeNilFields(v))
}

func removeNilFields(v interface{}) interface{} {
    val := reflect.ValueOf(v)
    
    // 如果是nil或者零值,直接返回
    if !val.IsValid() || val.IsZero() {
        return nil
    }
    
    // 如果是指针,解引用
    if val.Kind() == reflect.Ptr {
        if val.IsNil() {
            return nil
        }
        val = val.Elem()
    }
    
    // 处理结构体
    if val.Kind() == reflect.Struct {
        result := make(map[string]interface{})
        typ := val.Type()
        
        for i := 0; i < val.NumField(); i++ {
            field := val.Field(i)
            fieldName := typ.Field(i).Name
            
            // 跳过非导出字段
            if !field.CanInterface() {
                continue
            }
            
            fieldValue := removeNilFields(field.Interface())
            if fieldValue != nil {
                result[fieldName] = fieldValue
            }
        }
        
        if len(result) == 0 {
            return nil
        }
        return result
    }
    
    // 处理切片
    if val.Kind() == reflect.Slice {
        if val.Len() == 0 {
            return nil
        }
        
        result := make([]interface{}, 0, val.Len())
        for i := 0; i < val.Len(); i++ {
            item := removeNilFields(val.Index(i).Interface())
            if item != nil {
                result = append(result, item)
            }
        }
        
        if len(result) == 0 {
            return nil
        }
        return result
    }
    
    // 处理map
    if val.Kind() == reflect.Map {
        if val.Len() == 0 {
            return nil
        }
        
        result := make(map[string]interface{})
        iter := val.MapRange()
        for iter.Next() {
            key := iter.Key().Interface()
            value := removeNilFields(iter.Value().Interface())
            if value != nil {
                result[toString(key)] = value
            }
        }
        
        if len(result) == 0 {
            return nil
        }
        return result
    }
    
    return v
}

func toString(v interface{}) string {
    switch s := v.(type) {
    case string:
        return s
    default:
        return reflect.ValueOf(v).String()
    }
}

使用示例:

// 假设这是Azure SDK中的类型
type AzureResource struct {
    Name     *string
    ID       *string
    Location *string
    Tags     map[string]string
}

func main() {
    resource := &AzureResource{
        Name:     stringPtr("my-resource"),
        ID:       nil,
        Location: nil,
        Tags:     nil,
    }
    
    // 使用自定义序列化
    data, err := marshalWithoutNil(resource)
    if err != nil {
        panic(err)
    }
    
    fmt.Println(string(data))
    // 输出: {"Name":"my-resource"}
}

func stringPtr(s string) *string {
    return &s
}

方法2:使用反射创建过滤后的副本

func createFilteredCopy(src interface{}) interface{} {
    srcVal := reflect.ValueOf(src)
    if srcVal.Kind() == reflect.Ptr {
        if srcVal.IsNil() {
            return nil
        }
        srcVal = srcVal.Elem()
    }
    
    if srcVal.Kind() != reflect.Struct {
        return src
    }
    
    srcType := srcVal.Type()
    filtered := make(map[string]interface{})
    
    for i := 0; i < srcVal.NumField(); i++ {
        field := srcVal.Field(i)
        fieldName := srcType.Field(i).Name
        
        if !field.CanInterface() {
            continue
        }
        
        // 检查是否为nil或零值
        if !field.IsZero() {
            fieldVal := field.Interface()
            
            // 如果是指针,检查是否为nil
            if field.Kind() == reflect.Ptr {
                if !field.IsNil() {
                    filtered[fieldName] = field.Elem().Interface()
                }
            } else {
                filtered[fieldName] = fieldVal
            }
        }
    }
    
    return filtered
}

// 使用方式
func marshalAzureResource(resource interface{}) ([]byte, error) {
    filtered := createFilteredCopy(resource)
    return json.Marshal(filtered)
}

方法3:使用第三方库

如果允许使用第三方库,可以考虑使用支持灵活序列化选项的库:

import "github.com/tidwall/gjson"

// 或者使用更灵活的JSON处理库
func marshalWithOptions(v interface{}) ([]byte, error) {
    // 先序列化,然后使用gjson等库进行过滤
    data, err := json.Marshal(v)
    if err != nil {
        return nil, err
    }
    
    // 这里可以添加更复杂的过滤逻辑
    return data, nil
}

推荐使用第一种方法,因为它提供了最灵活的控制,能够递归处理嵌套结构体、切片和映射,确保所有层级的nil值都被正确过滤。

回到顶部