隐藏JSON中的属性

时间:2019-02-18 16:32:06

标签: json go

我有一个具有2个Player的结构表,但是在发送JSON时,我需要忽略结构Player的某些属性。

我可以使用json:"-",但是总是会忽略该属性,并且仅在发送Table结构时才需要忽略它。当我在代码的其他部分发送Player时,我需要这些属性。

我有:

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"`
}

我需要:

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

    }
}

玩家来自数据库,因此属性不为空。

a)我可以做类似的事情:

myTable = new(Table)

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

由于json:"omitempty",这些属性将在JSON中被忽略,但这不是一个好主意。

b)我可以使用类似别名的结构,但是Table期望PlayerBottom的类型为Player,而不是PlayerAlias,但是我不知道该怎么做。实施它:

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

c)我尝试动态地将json:"-"添加到不需要的JSON属性中,然后再发送它,但这很麻烦。

5 个答案:

答案 0 :(得分:3)

您可以为Marshaler类型创建自定义Table。这是您必须实现的接口:

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

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

然后,您将从-中删除Player标记(因为在其他地方封送它时,您需要保留字段),并且仅在{的自定义MarshalJSON方法中忽略它{1}}。


这是一个简单的(不相关的)示例,用于为类型实现自定义封送处理,以十六进制形式编码其中一个字段:

Table

如您在这里看到的,通过仅使用所需字段构造一个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)) } 并将其传递给map即可轻松进行这种封送处理。对于您的json.MarshalTable,这只会导致几行琐碎的代码。恕我直言,这样做比修改类型并使它们复杂化嵌入/别名更好,仅出于JSON编码的目的。

答案 1 :(得分:1)

如果要表示数据的公共和私有版本-一个版本是另一个版本的超集,请尝试embedded structs。添加一个自定义JSON marshaller,您可以得到两个相同核心数据的演示。

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

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
                },  
        )   
}

答案 2 :(得分:0)

有两种方法可以实现这一目标。第一个方法是为Table类型创建一个自定义编组器。然而,这有些乏味,并且可能非常严格。恕我直言,有一种更简单的方法可以执行相同的操作:嵌入类型:

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

现在,您仍然可以访问所需的所有数据,甚至可以添加用于直接访问数据的吸气剂:

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

请注意,您不需要使用这些getter函数。 Id字段不会被覆盖,因此,如果我有一个PartialPlayer变量,则可以直接访问该值:

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

您也可以通过指定想要保留在嵌入式类型上的值(也可以不使用函数)来访问被覆盖/屏蔽的字段:

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

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

Table中使用这种类型,如下所示:

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

初始化:

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

那很好。这种方法的好处是,进出JSON的封送不需要调用您的自定义封送功能,也不需要创建/映射诸如地图或隐藏类型等中介类型。

如果要拥有另一个字段,只需将其添加到PartialPlayer类型。如果您想取消隐藏Email之类的字段,只需将其从PartialPlayer类型中删除,完成工作即可。


现在使用自定义编组工具:

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

与在此处构建类型map[string]interface{}并没有太大区别,但是通过使用类型嵌入,您不必每次在Player类型上重命名或更改字段时都更新marshaller函数。

使用这种方法,可以像现在一样使用Table类型,但是JSON输出将不包含EmailBirthdate字段

答案 3 :(得分:0)

仅其字段标记不同的类型是convertible到彼此since Go 1.8。因此,您可以为播放器定义一种或多种“视图”类型,并在编组时选择一种适合您的用例的

相对于嵌入或实现json.Marshaler的优势在于,每次向?- count(3,N). N = 12. ?- count(4,N). N = 132. ?- count(5,N). N = 6762. ?- count(6,N). N = 910480 添加新字段时,编译器都会迫使您也更新每种视图类型,即,您必须有意识地决定是否还是不要在每个视图中都包含新字段。

Player

在操场上尝试:https://play.golang.org/p/a9V2uvOJX3Y


在旁边:考虑从播放器中删除“密码”字段。密码(哈希)通常仅由很少的功能使用。需要它的函数可以接受播放器和密码作为单独的参数。这样,您就消除了意外泄露密码的风险(例如,在日志消息中)。

答案 4 :(得分:0)

自定义编组器是更改对象映射到JSON方式的好方法。但是,就您而言,我不建议这样做,以防万一您需要将整个对象映射到JSON(例如用于管理工具)。

此答案的一些关键点:

  • 所有值都在内部公开
  • 从马歇尔代码中可以清楚地看到,值将被排除,并且很容易找到排除值的代码
  • 最小化重复和新类型

我建议您简单地在结构上定义一个函数,以返回您希望公开的字段的映射。

根据您的示例:

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

有几种方法可以使该功能起泡。一种简单的方法是让TablePlayerTop的{​​{1}}结构使用映射:

PlayerBottom

将其编组为JSON将返回您想要的字段。而且,您只需编辑一个位置即可从JSON添加/删除字段。

如果您在内部使用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()} } 类型并需要从中访问播放器,那么您可能仍需要在Table上存储完整的Player结构。我只需按照上面的表格遵循Table模式,如下所示:

Public

现在,当您创建表并在内部使用它时,可以清楚地知道类型是什么,并且当您编组JSON时,很明显,您正在排除某些类型以及排除的位置。

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(),
    }
}

查看实际情况:https://play.golang.org/p/24t-B6ZuUKu