为了账号安全,请及时绑定邮箱和手机立即绑定

将json解组为结构时如何设置默认值

将json解组为结构时如何设置默认值

Go
30秒到达战场 2022-04-20 20:50:32
从 json 字符串解组时,我想为字段设置默认值。我知道我可以在解组之前设置我想要的值,我认为这不是一个漂亮的方式。还有其他方法吗,例如使用“默认”标签?func main() {    in := "{}"    myStruct := StructTest{}    json.Unmarshal([]byte(in), &myStruct)    fmt.Println(myStruct)}type StructTest struct {    V int64 `default:1`}
查看完整描述

2 回答

?
九州编程

TA贡献1785条经验 获得超4个赞

您可以做的是定义一个自定义解组函数并在那里决定是否要使用默认值。如果您有其他字段,StructTest您将需要为StructTestin创建一个别名,UnmarshalJSON以便其他字段仍将被视为相同,而V将被覆盖。


下面的代码片段显示了一种方法,还可以查看这个有效的 Go 游乐场示例。


type StructTest struct {

    V     int64

    Other string // this field should be unmarshaled the regular way

}


func (st *StructTest) UnmarshalJSON(data []byte) error {

    // create alias to prevent endless loop

    type Alias StructTest

    tmp := struct {

        *Alias

        V *int64

    }{

        Alias: (*Alias)(st),

    }


    // unmarshal into temporary struct

    err := json.Unmarshal(data, &tmp)

    if err != nil {

        return err

    }


    // check if V was supplied in JSON and set default value if it wasn't

    if tmp.V == nil {

        st.V = 1 // default

    } else {

        st.V = *tmp.V

    }


    return nil

}

编辑:


实际上对于这个简单的例子,它可以做得更简单:


func (st *StructTest) UnmarshalJSON(data []byte) error {

    st.V = 1 // set default value before unmarshaling

    type Alias StructTest // create alias to prevent endless loop

    tmp := (*Alias)(st)


    return json.Unmarshal(data, tmp)

}


查看完整回答
反对 回复 2022-04-20
?
临摹微笑

TA贡献1982条经验 获得超2个赞

简短的回答是否定的,对于这样的情况:


type T struct {

    Field1 int

    Field2 int

}

...

func foo(data []byte) error {

    var x T

    if err := json.Unmarshal(data, &x); err != nil {

        return err

    }

    // now set Field1 and/or Field2 to default values if needed

    // ... but this is hard to do ...

    use(x)

    return nil

}

以另一种方式简单易行,即:


func foo(data []byte) error {

    x := T{Field1: 99 /* red ballons */, Field2: 42 /* The Answer */ }


    if err := json.Unmarshal(data, &x); err != nil {

        return err

    }

    // now Field1 and/or Field2 are already set to default values if needed

    use(x)

    return nil

}

但是,如果默认值难以计算怎么办?例如:


type T2 struct {

    Answer   int

    Question string

}

和函数foo应该像以前一样有一个默认的答案 42,但是默认的问题应该是老鼠试图计算的那个,显然我们不想花几千年的时间来计算它,如果我们不需要的话。所以我们不能预先初始化x,我们需要知道是否提供了问题。1


当然,另一种选择是解码为带有指针的结构,然后将该可为空的东西转换为没有指针的结构;我们知道可空变量的字段是否已填写,因为如果已填写,则它是非零的。这会产生这种代码:


type T_ struct {

    Answer   int

    Question *string

}

并填写一个x_类型的变量T_:


func foo(data []byte) error {

    var x T2

    x_ := T_{Answer: 42}

    if err := json.Unmarshal(data, &x_); err != nil {

        return err

    }

    x.Answer = x_.Answer

    if x_.Question = nil {

        x.Question = computeTheQuestion(x_.Answer)

    } else {

        x.Question = *x_.Question

    }

    use(x)

    return nil

}

然而,我再次感叹(至少是轻微地)使用像这个假设接口这样的代码解组 json 数据的能力:


func foo(data []byte) error {

    var objectish map[string]interface{}

    if err := json.Unmarshal(data, &objectish); err != nil {

        return err

    }

    x := T2{Answer: 42}

    if err := json.ReUnmarshal(objectish, &x); err != nil {

        return err

    }


    // We now know that the object-ish decoded JSON has the right

    // "shape" for a T, and variable x is filled in with a default

    // Answer.  Its Question is the empty string if there was an

    // empty Question, or if there was *no* Question at all, so

    // now let's find out if we need to compute the right Question.

    if _, ok := objectish["Question"]; !ok {

        x.Question = computeTheQuestion(x.Answer)

    }

    use(x) // pass x to a hoopy frood

    return nil

}

这个假设ReUnmarshal——它实际上可能只是它Unmarshal自己,真的——如果给定一个interface{},将把它的值视为从较早的结果得到的,Unmarshal并且只是重新输入结果。如果给定 a map[string]interface{},它将重新键入对象。如果给定 amap[string]map[string]interface{}它也会在这里做明显的事情,依此类推。用户看到的唯一更改是Unmarshal(或者ReUnmarshal,如果更改类型签名过于粗鲁)现在需要:


[]byte, 或者

string,可能,只是为了方便,或者

json.RawMessage,因为这已经做了几乎正确的事情,或者

map[string]T因为 T 是它接受的任何类型(包括map递归)

它对每一个都做了明显的事情。


请注意,这与使用json.RawMessage. 但是,当使用 时json.RawMessage,我们马上又要写出原始结构类型的变体,具有相同的字段名称。请参阅Go Playground 中的此示例,我们必须在其中声明x_(而不是objectish)使用json.RawMessage参数的类型。


(或者,您可以创建一个具有自己的解组函数的类型,如lmazgon 的回答。但是您必须再次发明一种类型,而不仅仅是直接解码为已经提供的目标类型。)


1在这种情况下,我们可以使用Question *string,或者假设不允许使用空字符串,或者其他什么,但这个例子是非常简化的。相反,假设我们应该计算一个具有精确 pi 到一些位置的 bignum,或者任何其他困难但现实的计算。这里的要点是,在某些情况下预加载默认值相对昂贵,因此我们希望避免这种情况。


查看完整回答
反对 回复 2022-04-20
  • 2 回答
  • 0 关注
  • 389 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信