如何将自定义UnmarshalJSON方法应用于嵌入式结构?

时间:2017-10-10 04:07:31

标签: go

所以,我有结构P.我需要将一些json数据解组成P,但有时它是嵌入式struct,Embedded。在任何一种情况下,我从API解组json并需要格式化“格式化”字段。在嵌入式案例中,我的unmarshaller似乎没有被调用。

have the following code

package main

import (
    "encoding/json"
    "fmt"
)

type P struct {
    Name      string `json:"name"`
    Formatted string `json:"formatted"`
}

type Embedded struct {
    A struct {
        B struct {
            *P
        } `json:"b"`
    } `json:"a"`
}

func (p *P) UnmarshalJSON(b []byte) error {
    type Alias P

    a := &struct {
        *Alias
    }{
        Alias: (*Alias)(p),
    }

    if err := json.Unmarshal(b, &a); err != nil {
        return err
    }

    a.Formatted = fmt.Sprintf("Hi, my name is %v", a.Name)

    return nil
}

func simple() {
    b := []byte(`{"name":"bob"}`)

    p := &P{}
    if err := json.Unmarshal(b, &p); err != nil {
        panic(err)
    }

    fmt.Printf("normal: %+v\n", p)
}

func embedded() {
    b := []byte(`{"a":{"b":{"name":"bob"}}}`)

    e := &Embedded{}
    if err := json.Unmarshal(b, &e); err != nil {
        panic(err)
    }

    fmt.Printf("embedded: %+v\n", e.A.B.P)
}

func main() {
    simple()

    embedded()

}

(我意识到我可以摆脱自定义unmarshaller并创建一个格式化名称的方法,但想知道这种方式是否可行。)

2 个答案:

答案 0 :(得分:2)

我不知道解释所有原因,我只列出哪些有效,哪些无效。更有知识的人可以填写你背后的原因。

当B为*struct时,以下情况有效,不确定原因。

type Embedded struct {
    A struct {
        B *struct {
            P
        } `json:"b"`
    } `json:"a"`
}

以下也有效。我猜测使用匿名结构在最后一个结果中有一些效果,因为这里不需要*struct

type embedP struct {
            P
}

type Embedded struct {
    A struct {
        B embedP `json:"b"`
    } `json:"a"`
}

如果*P已初始化,则以下情况有效。

type embedP struct {
            *P
}

type intermediate struct {
        B embedP `json:"b"`
}

type Embedded struct {
    A  intermediate `json:"a"`
}

e := &Embedded{A:intermediate{embedP{P:&P{}}}}

但同样的事情不适用于匿名结构。

type Embedded struct {
    A struct {
        B struct {
            *P
        } `json:"b"`
    } `json:"a"`
}

e := &Embedded{A : struct{B struct{*P}`json:"b"`}{B: struct{*P}{&P{}}}}

Play link

其他改进

如果p := &P{}已经是指针,则无需在json.Unmarshal中传递& p。 json.Unmarshal(b, p)就足够了。与e := &Embedded{}相同。

答案 1 :(得分:2)

为了扩展@ John的答案,请查看json解码器的源代码,特别是方法indirect(v reflect.Value, decodingNull bool)第442-483行。

  

//间接行走v根据需要分配指针,
  //直到它到达非指针。
  //如果遇到Unmarshaler,间接停止并返回   //如果decodeNull为true,则间接在最后一个指针处停止,因此可以将其设置为nil。

该方法返回(vec (concat [:aggs] [:bucket-aggregation :aggs])) json.Unmarshalerencoding.TextUnmarshaler的值。 在当前实现中,在方法内部,基本上执行了以下步骤

  1. 如果参数v 不是指针,它将立即返回,而不检查v是否实现v。无论json.Unmarshaler/encoding.TextUnmarshaler是否实现自定义unmarshaller,该方法都会为两个unmarshaller分配nil
  2. 如果参数B 是指针,它将检查v是否实现v。在这种情况下,如果json.Unmarshaler/encoding.TextUnmarshaler为零,则会为v分配一个新值。
  3. 如果v定义为

    Embedded

    当将type Embedded struct { A struct { B struct { *P } `json:"b"` } `json:"a"` } 解码为字段"b":{"name":"bob"}时,由于B不是指针,因此(1)适用。结果,自定义unmarshaller返回为B,因此永远不会被调用。 json解码器使用默认的unmarshaller将json值解码为nil的字段。

    如果B定义为

    Embedded

    因为字段type Embedded struct { A struct { *B struct { P } `json:"b"` } `json:"a"` } 是指针,所以(2)适用。解码器将新B分配给struct{*P},检测到B实现了自定义unmarshaller,然后按预期调用它。以下声明

    B
    如果预先分配type Embedded struct { A struct { *B struct { *P } `json:"b"` } `json:"a"` }

    也可以。

    P

    如果未预先分配,则在(2)中,解码器会将//... e := Embedded{} e.A.B = &struct{ *P }{P: &P{}} //... 分配给&struct{*P}{},然后使用B调用自定义的unmarshaller。结果,在unmarshall期间,B.P == nil无法捕获json值。

    注意
    我不确定它是否是所希望的行为,我无法找到与嵌入式结构相关的明确文档。