duanpin2034
2017-10-10 04:07
浏览 275
已采纳

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

So, I have struct P. I need to unmarshal some json data into P but sometimes it comes embedded struct, Embedded. In either case, I unmarshal the json from the API and need to format the "Formatted" field. It seems in the Embedded case my unmarshaller doesn't get called.

I 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
", 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
", e.A.B.P)
}

func main() {
    simple()

    embedded()

}

(I realize I can get rid of the custom unmarshaller and create a method to format the name but wanted to see if this way was possible.)

  • 写回答
  • 关注问题
  • 收藏
  • 邀请回答

2条回答 默认 最新

  • dtgv52982 2017-10-10 06:09
    已采纳

    I don't know enough to explain all the reasons, I will just list what works and what doesn't. Someone more knowledgeable can fill you in on the reasons behind it.

    The following works when B is a *struct, not sure why.

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

    The following also works. I'm guessing that using an anonymous struct had some effect in the last one since a *struct is not required here.

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

    The following works if *P is initialised.

    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{}}}}
    

    But the same thing doesn't work with anonymous structs.

    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

    Other improvements

    If p := &P{} is already a pointer you don't need to pass &p in json.Unmarshal. json.Unmarshal(b, p) would suffice. Same with e := &Embedded{}.

    已采纳该答案
    打赏 评论
  • douqi3913 2017-10-10 08:29

    To extent @John's answer, take a look at the source code of json decoder, especially method indirect(v reflect.Value, decodingNull bool) line 442-483.

    // indirect walks down v allocating pointers as needed,
    // until it gets to a non-pointer.
    // if it encounters an Unmarshaler, indirect stops and returns that.
    // if decodingNull is true, indirect stops at the last pointer so it can be set to nil.

    The method returns, json.Unmarshaler, encoding.TextUnmarshaler and the value of v. In current implementation, inside the method, basically the following steps were executed

    1. If argument v is not a pointer, it will return immediately without checking whether v implements json.Unmarshaler/encoding.TextUnmarshaler or not. The method assigns nil for both unmarshaller regardless B implements custom unmarshaller or not.
    2. If argument v is a pointer, it will check whether v implements json.Unmarshaler/encoding.TextUnmarshaler or not. In this case, if v is nil, a new value will be assigned to v.

    If Embedded is defined as

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

    when, decoding "b":{"name":"bob"} to field B, since B is not a pointer, (1) is applicable. As the result, custom unmarshaller is returned as nil, thus never being called. The json decoder uses default unmarshaller to decode json value to B's fields.

    If Embedded is defined as

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

    since field B is a pointer, (2) is applicable. The decoder allocates new struct{*P} to B, detects that B implements custom unmarshaller, then call it as expected. The following declaration

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

    also works, if P is preallocated, i.e.

    //...
    e := Embedded{}
    e.A.B = &struct{ *P }{P: &P{}}
    //...
    

    If it's not preallocated, in (2) the decoder will assign &struct{*P}{} to B, then call the custom unmarshaller with B.P == nil. As the result, json value can't be captured by B.P during unmarshall.

    Note:
    I'm not sure whether it is desired behavior or not, and I can't find a clear documentation related to embedded struct.

    打赏 评论