Idiomatic way to embed struct with custom MarshalJSON() method

前端 未结 5 580
星月不相逢
星月不相逢 2021-02-04 09:30

Given the following structs:

type Person {
    Name string `json:\"name\"`
}

type Employee {
    Person
    JobRole string `json:\"jobRole\"`
}
<
5条回答
  •  不知归路
    2021-02-04 10:08

    While this produces a different output than what the OP wants, I think it is still useful as a technique to prevent MarshalJSON of embedded structs from breaking the marshaling of structs that contain them.

    There is a proposal for encoding/json to recognize an inline option in struct tags. If that ever gets implemented, then I think avoiding embedding structs for cases like in OP's example might be the best bet.


    Currently, a reasonable workaround was described in a comment on the Go issue tracker and is the basis for this answer. It consists of defining a new type that will have the same memory layout as the original struct being embedded, but none of the methods:

    https://play.golang.org/p/BCwcyIqv0F7

    package main
    
    import (
        "encoding/json"
        "fmt"
        "strings"
    )
    
    type Person struct {
        Name string `json:"name"`
    }
    
    func (p *Person) MarshalJSON() ([]byte, error) {
        return json.Marshal(struct {
            Name string `json:"name"`
        }{
            Name: strings.ToUpper(p.Name),
        })
    }
    
    // person has all the fields of Person, but none of the methods.
    // It exists to be embedded in other structs.
    type person Person
    
    type EmployeeBroken struct {
        *Person
        JobRole string `json:"jobRole"`
    }
    
    type EmployeeGood struct {
        *person
        JobRole string `json:"jobRole"`
    }
    
    func main() {
        {
            p := Person{"Bob"}
            e := EmployeeBroken{&p, "Sales"}
            output, _ := json.Marshal(e)
            fmt.Printf("%s\n", string(output))
        }
        {
            p := Person{"Bob"}
            e := EmployeeGood{(*person)(&p), "Sales"}
            output, _ := json.Marshal(e)
            fmt.Printf("%s\n", string(output))
        }
    }
    

    Outputs:

    {"name":"BOB"}
    {"name":"Bob","jobRole":"Sales"}
    

    The OP wants {"name":"BOB","jobRole":"Sales"}. To achieve that, one would need to "inline" the object returned by Person.MarshalJSON into the object produced by Employee.MashalJSON, excluding the fields defined in Person.

提交回复
热议问题