简体   繁体   English

隐藏JSON中的属性

[英]Hide properties from a JSON

I have a struct Table with 2 Players, but I need to ignore some properties from the struct Player when I send JSON. 我有一个包含2个玩家的结构表,但是当我发送JSON时,我需要忽略struct Player一些属性。

I could use json:"-" , but then the property will be ignored ALWAYS, and I need to ignore it only when I send the Table struct. 我可以使用json:"-" ,但随后属性将被忽略,我只需要在发送Table结构时忽略它。 I need those properties when I send the Player in other parts of the code. 当我在代码的其他部分发送Player时,我需要这些属性。

I have: 我有:

type Player struct {
    Id            Int64   `json:"id"`
    Username      string  `json:"username,omitempty"`
    Password      string          `json:"-,omitempty"`
    Email         string          `json:"email,omitempty"`
    Birthdate     time.Time       `json:"birthdate,omitempty"`
    Avatar        string  `json:avatar,omitempty"`
}

type Table struct {
    Id           int       `json:"id"`
    PlayerTop    Player      `json:"playerTop"`
    PlayerBottom Player      `json:"playerBottom"`
}

I need: 我需要:

{
    "Table": {
        "id": 1,
        "playerBottom": {
            "id": 1,
            "username": "peter",
            "avatar": "avatar.png"
        },
        "playerTop": {
            "id": 1,
            "username": "peter",
            "avatar": "avatar.png"
        }

    }
}

The players come from the database, so the properties aren't empty. 玩家来自数据库,因此属性不为空。

a) I could do something like: a)我可以这样做:

myTable = new(Table)

myTable.PlayerBottom.Email = ""
myTable.PlayerBottom.Birthdate = ""
myTable.PlayerTop.Email = ""
myTable.PlayerTop.Birthdate = ""

so those properties will be ignored in the JSON, thanks to json:"omitempty" , but this is a bad idea. 所以这些属性将在JSON中被忽略,感谢json:"omitempty" ,但这是一个坏主意。

b) I could use something like an alias struct but Table is expecting that PlayerBottom is of type Player not PlayerAlias , but I don't know how to implement it: b)我可以使用类似别名结构的东西但是Table期望PlayerBottomPlayer而不是PlayerAlias类型,但我不知道如何实现它:

type PlayerAlias struct {
    Id            Int64   `json:"id"`
    Username      string  `json:"username,omitempty"`
    Avatar        string  `json:avatar,omitempty"`
}

c) I tried to add dynamically json:"-" to the properties that I don't want from the JSON before to send it, but it was a mess. c)我试图在发送它之前动态地将json:"-"添加到我不希望从JSON中获取的属性,但它是一团糟。

You could create a custom Marshaler for Table types. 您可以为Table类型创建自定义Marshaler This is the interface you have to implement: 这是您必须实现的界面:

https://golang.org/pkg/encoding/json/#Marshaler https://golang.org/pkg/encoding/json/#Marshaler

type Marshaler interface {
        MarshalJSON() ([]byte, error)
}

Then you'd remove the - tag from Player (because when you marshal it elsewhere you need to preserve the fields) and only ignore it in the custom MarshalJSON method of Table . 然后你要从Player删除-标签(因为当你在别处编组它时你需要保留字段)并且只在Table的自定义MarshalJSON方法中忽略它。


Here's a simple (unrelated) example of implementing custom marshaling for a type, encoding one of the fields in hex: 这是一个为类型实现自定义编组的简单(无关)示例,以十六进制编码其中一个字段:

type Account struct {
    Id   int32
    Name string
}

func (a Account) MarshalJSON() ([]byte, error) {
    m := map[string]string{
        "id":   fmt.Sprintf("0x%08x", a.Id),
        "name": a.Name,
    }
    return json.Marshal(m)
}

func main() {
    joe := Account{Id: 123, Name: "Joe"}
    fmt.Println(joe)

    s, _ := json.Marshal(joe)
    fmt.Println(string(s))
}

As you can see here, such marshaling is easy to do by constructing a map with just the fields you need and passing it to json.Marshal . 正如你在这里看到的那样,只需用你需要的字段构建一个map并将它传递给json.Marshal就可以轻松完成这样的编组。 For your Table and Player this will result in just a few lines of trivial code. 对于你的TablePlayer这将导致几行简单的代码。 IMHO it's better to do this than to modify the types and complicate them with embeddings/aliases, just for the sake of JSON encoding. 恕我直言,最好这样做,而不是修改类型并使用嵌入/别名复杂化,只是为了JSON编码。

If you want to represent a public and private version of data - and one version is a superset of the other, try embedded structs . 如果要表示公共和私有版本的数据 - 并且一个版本是另一个版本的超集,请尝试嵌入式结构 Adding a custom JSON marshaller and you can get two presentations of the same core data. 添加自定义JSON编组器,您可以获得相同核心数据的两个演示文稿。

Database JSON: {"Id":12345,"PlayerTop":{"id":456,"username":"Peter","avatar":"peter.png","password":"Secr3t","birthdate":"0001-01-01T00:00:00Z"},"PlayerBottom":{"id":890,"username":"Paul","avatar":"paul.png","password":"abc123","birthdate":"0001-01-01T00:00:00Z"}}

Public JSON: {"id":12345,"playerTop":{"id":456,"username":"Peter","avatar":"peter.png"},"playerBottom":{"id":890,"username":"Paul","avatar":"paul.png"}}

Run in playground : 操场上跑步

// public info
type PublicPlayer struct {
        Id       int64  `json:"id"`
        Username string `json:"username,omitempty"`
        Avatar   string `json:"avatar,omitempty"`
}

// private info
type Player struct {
        PublicPlayer // embed public info

        Password  string    `json:"password,omitempty"`
        Email     string    `json:"email,omitempty"`
        Birthdate time.Time `json:"birthdate,omitempty"`
}

type Table struct {
    Id           int    `json:"id"`
    PlayerTop    Player `json:"playerTop"`
    PlayerBottom Player `json:"playerBottom"`
}

// derivative type, so we can add a custom marshaller
type PublicTable Table

func (t PublicTable) MarshalJSON() ([]byte, error) {
        return json.Marshal(
                // anonymous struct definition
                struct {
                        Id     int          `json:"id"`
                        Top    PublicPlayer `json:"playerTop"`
                        Bottom PublicPlayer `json:"playerBottom"`
                }{  
                        t.Id,
                        t.PlayerTop.PublicPlayer,    // only export public data
                        t.PlayerBottom.PublicPlayer, // only export public data
                },  
        )   
}

There's a couple of ways you can achieve this. 有几种方法可以实现这一目标。 The first would be to create a custom marshaller for the Table type. 第一种是为Table类型创建自定义编组器。 This is, however somewhat tedious, and can be quite restrictive. 然而,这有点单调乏味,而且可能非常严格。 There is, IMHO, an easier way to do the same thing: embed types: 有恕我直言,更容易做同样的事情:嵌入类型:

type PartialPlayer struct {
     Player // embed the entire type
     Email string `json:"-"` // override fields and add the tag to exclude them
     Birthdate string `json:"-"`
}

Now you can still access all data you want, and you could even add getters for indirect data access: 现在您仍然可以访问所需的所有数据,甚至可以为间接数据访问添加getter:

func (pp PartialPlayer) GetEmail() string {
    if pp.Email == "" {
        return pp.Player.Email // get embedded Email value
    }
    return pp.Email // add override value
}

Note that you don't need to use these getter functions. 请注意,您不需要使用这些getter函数。 The Id field is not overridden, so if I have a PartialPlayer variable, I can access the value directly: Id字段没有被覆盖,所以如果我有一个PartialPlayer变量,我可以直接访问该值:

pp := PartialPlayer{
    Player: playerVar,
}
fmt.Printf("Player ID: %v\n", pp.Id) // still works

You can access overridden/masked fields by specifying you want the value held on the embedded type, without a function, too: 您可以通过指定希望嵌入类型上保留的值而不使用函数来访问重写/屏蔽字段:

fmt.Printf("Email on partial: '%s', but I can see '%s'\n", pp.Email, pp.Player.Email)

The latter will print Email on partial: '', but I can see 'foo@bar.com' . 后者将打印Email on partial: '', but I can see 'foo@bar.com'

Use this type in Table like this: Table使用此类型,如下所示:

type Table struct {
    Id           int            `json:"id"`
    PlayerTop    PartialPlayer  `json:"playerTop"`
    PlayerBottom PartialPlayer  `json:"playerBottom"`
}

Initialise: 初始化:

tbl := Table{
    Id: 213,
    PlayerTop: PartialPlayer{
        Player: playerVar,
    },
    PlayerBottom: PartialPlayer{
        Player: player2Var,
    },
}

That works just fine. 这很好用。 The benefit of this approach is that marshalling to and from JSON doesn't require a call to your custom marshaller functions, and creating/mapping intermediary types like maps or hidden types etc... 这种方法的好处是,在JSON之间进行编组不需要调用自定义编组函数,也不需要创建/映射中间类型,如地图或隐藏类型等...

Should you want to hade another field, just add it to the PartialPlayer type. 如果您想要另外一个字段,只需将其添加到PartialPlayer类型即可。 Should you want to unhide a field like Email , just remove it from the PartialPlayer type, job done. 如果您想取消隐藏像Email这样的字段,只需将其从PartialPlayer类型中删除,即可完成工作。


Now for an approach with a custom marshaller: 现在使用自定义编组器的方法:

type Table struct {
    Id           int    `json:"id"`
    PlayerTop    Player `json:"playerTop"`
    PlayerBottom Player `json:"playerBottom"`
}

type marshalTable {
    Table
    // assuming the PartialPlayer type above
    PlayerTop    PartialPlayer `json:"playerTop"`
    PlayerBottom PartialPlayer `json:"playerBottom"`
}

func (t Table) MarshalJSON() ([]byte, error) {
    mt := marshalTable{
        Table:        t,
        PlayerTop:    PartialPlayer{
            Player: t.PlayerTop,
        },
        PlayerBottom: PartialPlayer{
            Player: t.PlayerBottom,
        },
    }
    return json.Marshal(mt)
}

It's not too different from building a type map[string]interface{} here, but by using type embedding, you don't have to update the marshaller function every time a field is renamed or changed on the Player type. 它与在此处构建类型map[string]interface{}没有什么不同,但是通过使用类型嵌入,每次在Player类型上重命名或更改字段时,都不必更新marshaller函数。

Using this approach, your Table type can be used in the exact same way as you're doing right now, but the JSON output will not include the Email and Birthdate fields. 使用此方法,您的Table类型可以使用与您现在正在执行的完全相同的方式,但JSON输出将不包括EmailBirthdate字段。

Types that only differ in their field tags are convertible to one another since Go 1.8 . 自Go 1.8开始 ,只有字段标签不同的类型才能相互转换 So you can define one or more "view" types for players and pick one that fits your use case when marshaling. 因此,您可以为玩家定义一个或多个“视图”类型,并在编组时选择一个适合您的用例。

The advantage over embedding or implementing json.Marshaler is that every time you add a new field to Player the compiler forces you to update every view type as well, ie you have to make a conscious decision whether or not to include the new field in each view. 嵌入或实现json.Marshaler的优势在于,每次向Player添加新字段时,编译器都会强制您更新每个视图类型,即您必须有意识地决定是否在每个视图中包含新字段。视图。

package main

import (
    "encoding/json"
    "fmt"
    "time"
)

type Player struct {
    Id        int64     `json:"id"`
    Username  string    `json:"username,omitempty"`
    Password  string    `json:"-,omitempty"`
    Email     string    `json:"email,omitempty"`
    Birthdate time.Time `json:"birthdate,omitempty"`
    Avatar    string    `json:"avatar,omitempty"`
}

// PlayerSummary has the same underlying type as Player, but omits some fields 
// in the JSON representation.
type PlayerSummary struct {
    Id        int64     `json:"id"`
    Username  string    `json:"username,omitempty"`
    Password  string    `json:"-"`
    Email     string    `json:"-"`
    Birthdate time.Time `json:"-"`
    Avatar    string    `json:"avatar,omitempty"`
}

type Table struct {
    Id           int           `json:"id"`
    PlayerTop    PlayerSummary `json:"playerTop"`
    PlayerBottom PlayerSummary `json:"playerBottom"`
}

func main() {
    p1 := Player{
        Id:        1,
        Username:  "Alice",
        Email:     "alice@example.com",
        Birthdate: time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC),
        Avatar:    "https://www.gravatar.com/avatar/c160f8cc69a4f0bf2b0362752353d060",
    }
    p2 := Player{
        Id:        2,
        Username:  "Bob",
        Email:     "bob@example.com",
        Birthdate: time.Date(1998, 6, 1, 0, 0, 0, 0, time.UTC),
        Avatar:    "https://www.gravatar.com/avatar/4b9bb80620f03eb3719e0a061c14283d",
    }

    b, _ := json.MarshalIndent(Table{
        Id:           0,
        PlayerTop:    PlayerSummary(p1), // marshal p1 as PlayerSummary
        PlayerBottom: PlayerSummary(p2), // marshal p2 as PlayerSummary
    }, "", "  ")

    fmt.Println(string(b))
}

// Output:
// {
//   "id": 0,
//   "playerTop": {
//     "id": 1,
//     "username": "Alice",
//     "avatar": "https://www.gravatar.com/avatar/c160f8cc69a4f0bf2b0362752353d060"
//   },
//   "playerBottom": {
//     "id": 2,
//     "username": "Bob",
//     "avatar": "https://www.gravatar.com/avatar/4b9bb80620f03eb3719e0a061c14283d"
//   }
// }

Try it on the Playground: https://play.golang.org/p/a9V2uvOJX3Y 在Playground尝试: https//play.golang.org/p/a9V2uvOJX3Y


Aside: Consider removing the Password field from Player. 旁白:考虑从播放器中删除密码字段。 The password (hash) is typically only used by very few functions. 密码(哈希)通常仅由极少数函数使用。 Functions that do need it can accept the player and password as separate arguments. 需要它的函数可以接受播放器和密码作为单独的参数。 That way you eliminate the risk of accidentally leaking the password (in log messages, for instance). 这样就可以消除意外泄露密码的风险(例如,在日志消息中)。

The custom marshaller is a great way to change how your object is mapped to JSON. 自定义编组程序是更改对象映射到JSON的方式的好方法。 In you case however, I would not suggest this, in case you ever need to map your entire object to JSON at some other point (ie for an admin tool). 但是,在你的情况下,如果你需要在某个其他方面将整个对象映射到JSON(例如管理工具),我不建议这样做。

Some key points of this answer: 这个答案的一些关键点:

  • All values are exposed internally 所有值都在内部公开
  • From the marshall code it's clear that values will be excluded and it's easy to get to the code that excludes values 从马歇尔代码中可以清楚地看出,值将被排除,并且很容易找到排除值的代码
  • Minimize repetition and new types 尽量减少重复和新类型

I would suggest simply defining a function on your struct the returns a map of the fields you wish to expose. 我建议简单地在你的struct上定义一个函数,返回你想要公开的字段的映射。

From your example: 从你的例子:

type Player struct {
    Id        int64     `json:"id"`
    Username  string    `json:"username,omitempty"`
    Password  string    `json:"-,omitempty"`
    Email     string    `json:"email,omitempty"`
    Birthdate time.Time `json:"birthdate,omitempty"`
    Avatar    string    `json:"avatar,omitempty"`
}

func (p Player) PublicInfo() map[string]interface{} {
    return map[string]interface{}{
        "id":       p.Id,
        "username": p.Username,
        "avatar":   p.Avatar,
    }
}

There are several ways you can bubble the use of this function up. 有几种方法可以冒泡使用此功能。 One simple way is to have the Table struct use maps for PlayerTop and PlayerBottom : 一种简单的方法是让Table结构使用PlayerTopPlayerBottom映射:

type Table struct {
    Id           int                         `json:"id"`
    PlayerTop    map[string]interface{}      `json:"playerTop"`
    PlayerBottom map[string]interface{}      `json:"playerBottom"`
}

func NewTable(id int, playerTop, playerBottom Player) Table {
    return Table{Id: id, 
                 PlayerTop: playerTop.PublicInfo(), 
                 PlayerBottom: playerBottom.PublicInfo()}
}

Marshalling this to JSON will return the fields you want. 将此编组为JSON将返回所需的字段。 And you only need to edit one place to add/remove fields from the JSON. 而且您只需编辑一个位置即可添加/删除JSON中的字段。

In case you use the Table type internally and need to access the players from it, then you may still need to store the full Player struct on the Table . 如果您在内部使用Table类型并需要从中访问播放器,那么您可能仍需要在Table上存储完整的Player结构。 I would simply follow the Public pattern from above with table like so: 我只需按照上面的Public模式使用表格如下:

type Table struct {
    Id           int    `json:"id"`
    PlayerTop    Player `json:"playerTop"`
    PlayerBottom Player `json:"playerBottom"`
}

func (t Table) PublicInfo() map[string]interface{} {
    return map[string]interface{}{
        "id":           t.Id,
        "playerTop":    t.PlayerTop.PublicInfo(),
        "playerBottom": t.PlayerBottom.PublicInfo(),
    }
}

Now when you create a table and use it internally its clear what the types are, and when you marshall the JSON it's clear that you are excluding some types and where that exclusion is taking place. 现在,当你创建一个表并在内部使用它时,它清楚了什么类型,当你编组JSON时,很明显你排除了一些类型和排除的位置。

func main() {
    p1 := Player{Id: 1, Username: "peter", Avatar: "avatar.png", Email: "PRIVATE"}
    p2 := Player{Id: 1, Username: "peter", Avatar: "avatar.png", Email: "PRIVATE"}
    t := Table{Id: 1, PlayerTop: p1, PlayerBottom: p2}
    admin, _ :=  json.Marshal(t)
    public, _ := json.Marshal(t.PublicInfo())
    fmt.Println(fmt.Sprintf("For admins: %s", string(admin)))
    fmt.Println(fmt.Sprintf("For public: %s", string(public)))
}
/*
Output: 
For admins: {"id":1,"playerTop":{"id":1,"username":"peter","email":"PRIVATE","birthdate":"0001-01-01T00:00:00Z","avatar":"avatar.png"},"playerBottom":{"id":1,"username":"peter","email":"PRIVATE","birthdate":"0001-01-01T00:00:00Z","avatar":"avatar.png"}}
For public: {"id":1,"playerBottom":{"avatar":"avatar.png","id":1,"username":"peter"},"playerTop":{"avatar":"avatar.png","id":1,"username":"peter"}}
*/

See it in action: https://play.golang.org/p/24t-B6ZuUKu 请参阅以下内容: https//play.golang.org/p/24t-B6ZuUKu

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM