好好爱自己!

golang embedded structs

golang 中把struct 转成json格式输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
package main
 
import (
  "encoding/json"
  "fmt"
)
 
type Person struct {
    Name string `json:"name,omitempty"`
    DoB  string `json:"dob,omitempty"`
   Age string `json:"-,omitempty"`
}
 
type Singer struct {
    Person
    MusicGenre string `json:"music_genre,omitempty"`
    Age string `json:"ageee,omitempty"`
}
 
func ToJSON(s interface{}) (string, error) {
    bs, err := json.Marshal(s)
    if err != nil {
        return "", err
    }
//  fmt.Println(bs)
    return string(bs), nil
}
 
func main() {
    s := Singer{
          Person{"John Singer",
           "01-02-1975",
            "13"},
         "pop", "12" }
    sJSON, _ := ToJSON(s)
    fmt.Println(sJSON)
    // {"name":"John Singer","dob":"01-02-1975","music_genre":"pop"}
}

  

 

---------------------------------

One of the things that I missed the most, apart from generics, when coming to Go while having Java background was the lack of inheritance. In JVM it was pretty simple, as you can define a parent and child classes, then have a common behavior present all the children objects. This is not the case in Go, and I had to learn how to work around this. Here, we don't have inheritance, we have the composition, and for that, we have a pretty useful technique called embedding structs.

Manual composition vs embedding

In the classical approach to composition, we add what we'd call a parent class as another field in the child one. In Go terms, we should first define a parent struct with common attributes:

type Person struct {
    Name string 
}

Then we should add an attribute of type Person to another struct which we want to use that common features from the parent:

type Singer struct {
    Parent     Person
    MusicGenre string
}

This may not seem like a huge improvement, but we can use a shorter notation and embedPerson into Singer by skipping the attribute name and just leave its type:

type Singer struct {
    Person
    MusicGenre string
}

In both cases, when creating an instance of Singer inline we need to specify a definition of parent struct with its name - when embedding it is the name of that struct:

// manual composition
s0 := embedding.Singer{
    Parent: embedding.Person{Name: "John Singer"},
    MusicGenre: "pop",
}

// embedding
s1 := embedding.Singer{
    Person: embedding.Person{Name: "John Singer"},
    MusicGenre: "pop",
}

The difference is larger than just saving a few characters in the struct definition. First of all, when doing it manually we'd have to use attribute name (Parent) all the time:

// manual composition
fmt.Println(s0.Parent.Name)

While with embedding we can (but don't have to) refer to the attributes of the embedded struct as if they were defined in the child:

// embedding
fmt.Println(s1.Parent.Name)
fmt.Println(s1.Name)

Another interesting thing is that if we want to build a Singer struct by adding attributes to an empty instance, again we get a feeling of the inheritance:

// manual composition
s0 := embedding.Singer{MusicGenre: "pop"}
s0.Parent.Name = "John Singer" // we have to do this explicitly
fmt.Println(s0.Parent.Name)

// embedding
s1 := embedding.Singer{MusicGenre: "pop"}
// we can be explicit with
// s1.Person.Name = "John Doe"
// but we can as well do this:
s1.Name = "John Singer"
fmt.Println(s1.Parent.Name)

Calling inherited functions

Another useful feature of embedding structs is that we can call parent's functions that were inherited as if they were defined on a child struct. For example, we can add a Talk(..)function on Person and use it on an instance of Singer:

type Person struct {
    ...
}

func (p Person) Talk(message string) {
    fmt.Printf("%s (a person) says \"%s\"\n", p.Name, message)
}

func main() {
    s := Singer{}
    s.Name = "John Doe"
    s.Talk("Hello, reader!")
    // John Doe (a person) says "Hello, reader!"
}

Awesome! Remember that the attributes and function are promoted to the child struct only if they are not overwritten there. If we define a Talk(..) function directly on Singer, the one from Person would never be called:

...
func (p Singer) Talk(message string) {
    fmt.Printf("Singer %s says \"%s\"\n", p.Name, message)
}
func main() {
    s := Singer{}
    s.Name = "John Doe"
    s.Talk("Hello, again!")
    // Singer John Singer says "Hello again!"
}

The trick is when a function that is promoted calls another one. For example, if we define a Type() function that would return the struct identifier on both Person and Singer, then call it within Talk function that would be promoted from the parent, we would get the Type() from the parent as well. The reason for this is that at the time of executing the function, Go does not realize we are dealing with some inheritance stuff and we don't go back to the original caller to see what the context is:

func (p Person) Type() string {
    return "PERSON"
}
func (p Person) Talk(message string) {
    fmt.Printf("%s (type=%s) says \"%s\"\n", p.Name, p.Type(), message)
}
...
func (s Singer) Type() string {
    return "SINGER"
}

func (s Singer) Sing(title string) {
    fmt.Printf("%s (type=%s) sings %s in the style of %s.\n", s.Name, s.Type(), title, s.MusicGenre)
}
...
func main() {
    s := Singer{MusicGenre: "rock"}
    s.Name = "Johny Singerra"
    s.Sing("Welcome to the forest")
    // Johny Singerra (type=SINGER) sings Welcome to the forest in the style of rock.
    s.Talk("Hello!")
    // Johny Singerra (type=PERSON) says "Hello!"
}

Hiding JSON properties

Another interesting fact is the way Go handler JSON tags that can identify attributes of a struct. For example, we can marshal Singer to JSON and see that both its own and the inherited properties end up in the document:

type Person struct {
    Name string `json:"name,omitempty"`
    DoB  string `json:"dob,omitempty"`
}
...
type Singer struct {
    Person
    MusicGenre string `json:"music_genre,omitempty"`
}
...
func (s Singer) ToJSON() (string, error) {
    bs, err := json.Marshal(s)
    if err != nil {
        return "", err
    }
    return string(bs), nil
}
...
func main() {
    s := embedding.Singer{
        Person: embedding.Person{
            Name: "John Singer",
            DoB:  "01-02-1975",
        },
        MusicGenre: "pop",
    }
    sJSON, _ := s.ToJSON()
    fmt.Println(sJSON)
    // {"name":"John Singer","dob":"01-02-1975","music_genre":"pop"}
}

It's great that both name and dob properties are there, but what if we want to hide some of those for JSON marshaler? Let's create a MusicStar struct where we'll add a nickname to the Singer, but at the same time we'd like to hide the date of birth (since we want to keep it as a secret):

type MusicStar struct {
    Singer
    Nickname string `json:"nickname,omitempty"`
    DoB      string `json:"-,omitempty"`
}

func (ms MusicStar) ToJSON() (string, error) {
    bs, err := json.Marshal(ms)
    if err != nil {
        return "", err
    }
    return string(bs), nil
}

Note that we've added a DoB field but added a - as JSON tag to indicate that this field should be removed from marshaling. Unfortunately, that doesn't work:

func main() {
    ms := embedding.MusicStar{
        Nickname: "Starry",
        Singer: embedding.Singer{
            Person: embedding.Person{
                Name: "Joe Star",
                DoB:  "01-02-1975",
            },
            MusicGenre: "pop",
        },
    }
    msJSON, _ := ms.ToJSON()
    fmt.Println(msJSON)
    // "name":"Joe Star","dob":"01-02-1975","music_genre":"pop","nickname":"Starry"}
}

That is because although Go sees our - JSON tag, it recognizes it as undefined and go deeper into embedded structs to see if there is a field that matches the name, but has some concrete tag defined. It finds one, so that is being used. We can, however, trick the language into hiding that nested DoB, by defining the same property with the same JSON tag in the top-level struct. This way the DoB from Person will never be promoted to the top level JSON object, since its top-level value is empty, therefore the empty string overwrites anything that comes from Person:

type MusicStar struct {
    Singer
    Nickname string `json:"nickname,omitempty"`
    DoB      string `json:"dob,omitempty"`
}
...
msJSON, _ := ms.ToJSON()
fmt.Println(msJSON)
// {"name":"Joe Star","music_genre":"pop","nickname":"Starry"}

As you can see, embedded structs solve some of the things we would achieve via classical inheritance, but it's necessary to understand how it works to avoid unexpected behaviors and gotchas. The full source code of these examples is available on Github.

posted @   立志做一个好的程序员  阅读(793)  评论(0编辑  收藏  举报
编辑推荐:
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 25岁的心里话
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现

不断学习创作,与自己快乐相处

点击右上角即可分享
微信分享提示