如何为AWS ION实现interface{}的正确Unmarshal方法?
解决Ion反序列化时根据Type字段动态解析Data的问题
要实现MetaDataPayment的UnmarshalIon方法,核心是先获取Type字段的值,再根据该值动态指定Data的目标类型。由于Ion是流式序列化格式,我们可以先将整个MetaDataPayment的Ion数据读取为一个可重复访问的ion.Value,避免流式读取时的位置丢失问题。
以下是完整的实现代码:
package main import ( "fmt" "github.com/amazon-ion/ion-go/ion" ) type foo struct { DontCare string `ion:"dontcare" json:"dontcare"` Payment MetaDataPayment `ion:"payment" json:"payment,omitempty"` } type MetaDataPayment struct { Data interface{} `ion:"data" json:"data"` Type string `ion:"type" json:"type,omitempty"` } type TypeA struct { Invoice string `json:"invoice" ion:"invoice"` } type TypeB struct { Reference string `json:"reference" ion:"reference"` } // UnmarshalIon 实现自定义Ion反序列化逻辑 func (u *MetaDataPayment) UnmarshalIon(r ion.Reader) error { // 先将当前Reader指向的整个MetaDataPayment结构读取为ion.Value,方便重复访问 val, err := ion.ReadValue(r) if err != nil { return fmt.Errorf("failed to read MetaDataPayment ion value: %w", err) } // 从ion.Value中提取Type字段的值 structVal, ok := val.(ion.Struct) if !ok { return fmt.Errorf("expected ion.Struct for MetaDataPayment, got %T", val) } typeVal, exists := structVal.Get("type") if !exists { // 如果Type字段不存在,默认保留为map[string]interface{} return val.Unmarshal(u) } paymentType, ok := typeVal.(string) if !ok { return fmt.Errorf("type field must be string, got %T", typeVal) } // 根据Type值初始化对应的Data类型 var targetData interface{} switch paymentType { case "typeA": targetData = &TypeA{} case "typeB": targetData = &TypeB{} default: // 未知类型时,默认解析为map targetData = make(map[string]interface{}) } // 先将基础字段(Type)反序列化到u中 if err := structVal.UnmarshalField("type", &u.Type); err != nil { return fmt.Errorf("failed to unmarshal type field: %w", err) } // 提取Data字段的ion.Value并反序列化到目标类型 dataVal, exists := structVal.Get("data") if exists { if err := dataVal.Unmarshal(targetData); err != nil { return fmt.Errorf("failed to unmarshal data field as %s: %w", paymentType, err) } u.Data = targetData } return nil } // 测试示例 func main() { // 测试typeA的Ion数据 ionDataA := `{ dontcare: "test", payment: { type: "typeA", data: {invoice: "INV-12345"} } }` var fooA foo if err := ion.Unmarshal([]byte(ionDataA), &fooA); err != nil { fmt.Printf("Unmarshal typeA failed: %v\n", err) return } fmt.Printf("TypeA Data: %+v\n", fooA.Payment.Data.(*TypeA)) // 测试typeB的Ion数据 ionDataB := `{ dontcare: "test", payment: { type: "typeB", data: {reference: "REF-67890"} } }` var fooB foo if err := ion.Unmarshal([]byte(ionDataB), &fooB); err != nil { fmt.Printf("Unmarshal typeB failed: %v\n", err) return } fmt.Printf("TypeB Data: %+v\n", fooB.Payment.Data.(*TypeB)) }
关键逻辑说明
- 读取为ion.Value:先把整个
MetaDataPayment的Ion结构读取为ion.Value,避免流式读取时无法回头重新读取Data字段的问题,方便先获取Type值再处理Data。 - 提取Type字段:从
ion.Struct中取出Type字段的值,判断当前应该使用哪种Data类型。 - 动态反序列化Data:根据Type值初始化对应的结构体指针,再将Data字段的
ion.Value反序列化到该指针上,最后赋值给MetaDataPayment的Data字段。 - 兼容默认情况:如果Type字段不存在或为未知类型,会默认将Data解析为
map[string]interface{},保持原有行为的兼容性。
内容的提问来源于stack exchange,提问作者Arthur Saint-Genis
相关产品推荐
相关产品推荐

