Golang中如何实现仅忽略字段的Marshal但不忽略Unmarshal

Golang中如何实现仅忽略字段的Marshal但不忽略Unmarshal 我正在使用Go API来访问我的SQL表。我的所有表都有created_atupdated_at字段,我希望这些字段由SQL控制,而不是Go。例如:

query, err := GetObjects[SomeSchema]("table_name") // 调用表,然后反序列化到 []SomeSchema
assert.Equal(query[0], SomeSchema{id: "whatever", created_at: /* 某个时间对象 */, updated_at: ... })

我希望找到一种简单的方法,在序列化时忽略所有此类模式对象中的created_atupdated_at字段,这样它们就不会覆盖SQL中的值,但在反序列化时它们会正常读取。

我曾希望嵌入一个包含两个*time.Time对象的结构体可以解决这个问题,使其在序列化时忽略这些字段,但遗憾的是,MarshalJSON()重写会被提升到父对象。据我理解,我知道的最简单的方法是,为我的10多个结构体中的每一个编写一个自定义的MarshalJSON(),但这并不容易,因为我有大量的对象。代码看起来像这样:

func (s SomeSchema) MarshalJSON() ([]byte, error) {
    type SomeSchemaWithoutThoseFields struct {
        Id string `json:"id"
    }
    return json.Marshal(SomeSchemaWithoutThoseFields{id: s.Id})
}

有没有更简单、更符合Go语言习惯的方法来实现这个?


更多关于Golang中如何实现仅忽略字段的Marshal但不忽略Unmarshal的实战教程也可以访问 https://www.itying.com/category-94-b0.html

2 回复

实现此目标的一种方法是使用结构体组合,并嵌入包含您希望在JSON编组中排除的字段的结构体。以下是一个示例:

type Timestamps struct {
    CreatedAt time.Time `json:"-"`
    UpdatedAt time.Time `json:"-"`
}

type SomeSchema struct {
    Timestamps
    ID string `json:"id"`
    // 其他字段...
}

在这种方法中,Timestamps 被嵌入到 SomeSchema 中,但字段 CreatedAtUpdatedAt 被标记为 json:"-",这向JSON编组器指示在编组过程中应忽略这些字段。

这样一来,您无需为每个结构体编写自定义的 MarshalJSON() 方法,这些字段在编组时将被忽略,而在解组时仍能正常读取。

更多关于Golang中如何实现仅忽略字段的Marshal但不忽略Unmarshal的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


在Go中实现仅忽略字段的Marshal但不忽略Unmarshal,可以使用结构体嵌入结合json标签的-标记和指针类型。以下是几种实现方式:

方法1:使用组合结构体(推荐)

type Timestamps struct {
    CreatedAt *time.Time `json:"created_at,omitempty"`
    UpdatedAt *time.Time `json:"updated_at,omitempty"`
}

type SomeSchema struct {
    ID string `json:"id"`
    Timestamps
}

// MarshalJSON 重写,忽略Timestamps字段
func (s SomeSchema) MarshalJSON() ([]byte, error) {
    type Alias SomeSchema
    return json.Marshal(&struct {
        *Alias
        Timestamps *struct{} `json:"timestamps,omitempty"`
    }{
        Alias: (*Alias)(&s),
    })
}

方法2:使用接口和自定义Marshaler

type Timestamped interface {
    GetTimestamps() Timestamps
}

type BaseModel struct {
    CreatedAt time.Time `json:"created_at"`
    UpdatedAt time.Time `json:"updated_at"`
}

func (b BaseModel) GetTimestamps() Timestamps {
    return Timestamps{
        CreatedAt: &b.CreatedAt,
        UpdatedAt: &b.UpdatedAt,
    }
}

type SomeSchema struct {
    ID string `json:"id"`
    BaseModel
}

// 为所有需要此功能的结构体实现MarshalJSON
func (s SomeSchema) MarshalJSON() ([]byte, error) {
    return json.Marshal(struct {
        ID string `json:"id"`
    }{
        ID: s.ID,
    })
}

方法3:使用反射生成MarshalJSON(适用于多个结构体)

func GenerateMarshalJSON(skipFields ...string) func(interface{}) ([]byte, error) {
    return func(v interface{}) ([]byte, error) {
        val := reflect.ValueOf(v)
        if val.Kind() == reflect.Ptr {
            val = val.Elem()
        }
        
        typ := val.Type()
        result := make(map[string]interface{})
        
        for i := 0; i < val.NumField(); i++ {
            field := typ.Field(i)
            fieldName := field.Tag.Get("json")
            if fieldName == "" {
                fieldName = field.Name
            }
            
            // 跳过指定的字段
            skip := false
            for _, skipField := range skipFields {
                if strings.Split(fieldName, ",")[0] == skipField {
                    skip = true
                    break
                }
            }
            
            if !skip {
                result[fieldName] = val.Field(i).Interface()
            }
        }
        
        return json.Marshal(result)
    }
}

// 使用方式
type SomeSchema struct {
    ID        string    `json:"id"`
    CreatedAt time.Time `json:"created_at"`
    UpdatedAt time.Time `json:"updated_at"`
}

func (s SomeSchema) MarshalJSON() ([]byte, error) {
    marshal := GenerateMarshalJSON("created_at", "updated_at")
    return marshal(s)
}

方法4:使用匿名结构体包装器

type MarshalExcluder struct{}

func (m MarshalExcluder) ExcludeTimestamps(v interface{}) ([]byte, error) {
    data, err := json.Marshal(v)
    if err != nil {
        return nil, err
    }
    
    var obj map[string]interface{}
    if err := json.Unmarshal(data, &obj); err != nil {
        return nil, err
    }
    
    delete(obj, "created_at")
    delete(obj, "updated_at")
    
    return json.Marshal(obj)
}

// 使用方式
type SomeSchema struct {
    ID        string    `json:"id"`
    CreatedAt time.Time `json:"created_at"`
    UpdatedAt time.Time `json:"updated_at"`
}

func (s SomeSchema) MarshalJSON() ([]byte, error) {
    excluder := MarshalExcluder{}
    return excluder.ExcludeTimestamps(s)
}

方法5:使用代码生成工具

对于大量结构体,可以考虑使用go generate和模板生成MarshalJSON方法:

//go:generate go run generate_marshal.go

// generate_marshal.go
package main

import (
    "go/ast"
    "go/parser"
    "go/token"
    "os"
    "strings"
    "text/template"
)

const marshalTemplate = `
func ({{.Receiver}} {{.TypeName}}) MarshalJSON() ([]byte, error) {
    type Alias {{.TypeName}}
    return json.Marshal(&struct {
        *Alias
        CreatedAt *time.Time ` + "`json:\"created_at,omitempty\"`" + `
        UpdatedAt *time.Time ` + "`json:\"updated_at,omitempty\"`" + `
    }{
        Alias: (*Alias)(&{{.Receiver}}),
    })
}
`

第一种方法是最简洁且符合Go习惯的,它通过结构体嵌入和MarshalJSON重写实现了仅序列化时忽略特定字段的需求,同时保持了解序列化的正常功能。

回到顶部