我有一个具有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属性中,然后再发送它,但这很麻烦。
答案 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.Marshal
和Table
,这只会导致几行琐碎的代码。恕我直言,这样做比修改类型并使它们复杂化嵌入/别名更好,仅出于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输出将不包含Email
和Birthdate
字段
答案 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,
}
}
有几种方法可以使该功能起泡。一种简单的方法是让Table
和PlayerTop
的{{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(),
}
}