给出以下代码:(reproduced here at play.golang.org
。)
package main
import (
"encoding/json"
"fmt"
)
type User struct {
Id int `json:"id"`
Name string `json:"name"`
}
type Session struct {
Id int `json:"id"`
UserId int `json:"userId"`
}
type Anything interface{}
type Hateoas struct {
Anything
Links map[string]string `json:"_links"`
}
func MarshalHateoas(subject interface{}) ([]byte, error) {
h := &Hateoas{subject, make(map[string]string)}
switch s := subject.(type) {
case *User:
h.Links["self"] = fmt.Sprintf("http://user/%d", s.Id)
case *Session:
h.Links["self"] = fmt.Sprintf("http://session/%d", s.Id)
}
return json.MarshalIndent(h, "", " ")
}
func main() {
u := &User{123, "James Dean"}
s := &Session{456, 123}
json, err := MarshalHateoas(u)
if err != nil {
panic(err)
} else {
fmt.Println("User JSON:")
fmt.Println(string(json))
}
json, err = MarshalHateoas(s)
if err != nil {
panic(err)
} else {
fmt.Println("Session JSON:")
fmt.Println(string(json))
}
}
我试图让渲染的JSON看起来正确在我的情况下,这意味着:
User JSON:
{
"id": 123,
"name": "James Dean",
"_links": {
"self": "http://user/123"
}
}
Session JSON:
{
"id": 456,
"userId": 123,
"_links": {
"self": "http://session/456"
}
}
不幸的是,Go将匿名成员视为真正的命名事物,因此它采用定义的类型(Anything
)并因此命名JSON:
User JSON:
{
"Anything": {
"id": 123,
"name": "James Dean"
},
"_links": {
"self": "http://user/123"
}
}
Session JSON:
{
"Anything": {
"id": 456,
"userId": 123
},
"_links": {
"self": "http://session/456"
}
}
来自the docs的JSON中没有关于匿名成员处理的明确文档:
匿名结构字段通常被封送,就好像它们的内部导出字段是外部结构中的字段一样,这取决于下一段中描述的通常的Go可见性规则。在其JSON标记中给出名称的匿名结构字段被视为具有该名称,而不是匿名。
匿名结构字段的处理是Go 1.1中的新功能。在Go 1.1之前,匿名结构字段被忽略。要强制忽略当前版本和早期版本中的匿名结构字段,请为该字段指定“ - ”的JSON标记。
这并不能说明是否有办法让事情变得平坦,或者向马歇尔勒提示我要做什么。
我确信,有一个特殊情况,可能有魔术名称,它具有重命名XML编组器中XML文档的根元素的特殊含义。
在这种情况下,我也没有以任何方式附加代码,我的用例是拥有一个接受interface{}, *http.Request, http.ResponseWriter
的函数并在线路上写回HATEOAS文档,打开传递的类型,推断哪些链接写回JSON。 (从而访问请求,请求主机,端口,方案等,以及类型本身以推断URL和已知字段等)
答案 0 :(得分:2)
工作场所链接:http://play.golang.org/p/_r-bQIw347
它的要点是这个;通过使用反射包,我们循环遍历我们希望序列化的结构的字段并将它们映射到map[string]interface{}
我们现在可以保留原始结构的平面结构而不引入新字段。
MarshalHateoas
始终接收指向值的指针。
package main
import (
"encoding/json"
"fmt"
"reflect"
)
type User struct {
Id int `json:"id"`
Name string `json:"name"`
}
type Session struct {
Id int `json:"id"`
UserId int `json:"userId"`
}
func MarshalHateoas(subject interface{}) ([]byte, error) {
links := make(map[string]string)
out := make(map[string]interface{})
subjectValue := reflect.Indirect(reflect.ValueOf(subject))
subjectType := subjectValue.Type()
for i := 0; i < subjectType.NumField(); i++ {
field := subjectType.Field(i)
name := subjectType.Field(i).Name
out[field.Tag.Get("json")] = subjectValue.FieldByName(name).Interface()
}
switch s := subject.(type) {
case *User:
links["self"] = fmt.Sprintf("http://user/%d", s.Id)
case *Session:
links["self"] = fmt.Sprintf("http://session/%d", s.Id)
}
out["_links"] = links
return json.MarshalIndent(out, "", " ")
}
func main() {
u := &User{123, "James Dean"}
s := &Session{456, 123}
json, err := MarshalHateoas(u)
if err != nil {
panic(err)
} else {
fmt.Println("User JSON:")
fmt.Println(string(json))
}
json, err = MarshalHateoas(s)
if err != nil {
panic(err)
} else {
fmt.Println("Session JSON:")
fmt.Println(string(json))
}
}
答案 1 :(得分:0)
使用 omitempty 标签和一些逻辑,这样你就可以使用一种类型,为不同的情况产生正确的输出。
诀窍是知道JSON编码器何时将值视为空。来自encoding / json文档:
空值为false,0,任何nil指针或接口值,以及 任何长度为零的数组,切片,映射或字符串。
这是您的程序稍作修改,以产生您想要的输出。当它们的值为“空”时,它会忽略某些字段 - 具体来说,JSON编码器将省略以“0”作为值的整数并且映射为零长度。
package main
import (
"encoding/json"
"fmt"
)
type User struct {
Id int `json:"id"`
Name string `json:"name,omitempty"`
UserId int `json:"userId,omitempty"`
Links map[string]string `json:"_links,omitempty"`
}
func Marshal(u *User) ([]byte, error) {
u.Links = make(map[string]string)
if u.UserId != 0 {
u.Links["self"] = fmt.Sprintf("http://user/%d", u.UserId)
} else if u.Id != 0 {
u.Links["self"] = fmt.Sprintf("http://session/%d", u.Id)
}
return json.MarshalIndent(u, "", " ")
}
func main() {
u := &User{Id: 123, Name: "James Dean"}
s := &User{Id: 456, UserId: 123}
json, err := Marshal(u)
if err != nil {
panic(err)
} else {
fmt.Println(string(json))
}
json, err = Marshal(s)
if err != nil {
panic(err)
} else {
fmt.Println(string(json))
}
}
答案 2 :(得分:0)
我经历过的一种方法涉及在类型上使用方法。有关详细信息,请参阅http://play.golang.org/p/bPWB4ryDQn。
基本上,您从相反的角度处理问题 - 而不是将基类型“封装”为Hateoas类型,而是在每个基类型中包含所需的映射。然后,在每个基类型上实现一个方法,该方法负责相应地更新Links字段。
这会产生预期的结果,并且只有边缘源代码样板。
{
"id": 123,
"name": "James Dean",
"_links": {
"self": "http://user/123"
}
}
{
"id": 456,
"userId": 123,
"_links": {
"self": "http://session/456"
}
}
我相信除此之外的任何其他方式,特别是如果你坚持使用嵌入和扩展方法,将需要实现自定义封送器(http://golang.org/pkg/encoding/json/#Marshaler),并且可能还需要使用反射包,尤其是因为任何东西都是interface {}。
答案 3 :(得分:0)
这有点棘手。但有一点可以肯定的是,该文档通过以下方式处理您的样本:
来自同一link的接口值编码为接口
中包含的值
所以没有其他事可做。 “Anything”虽然它是匿名的,但它是一个接口变量,所以我可能会期待你样本中的行为。
我带了your code并进行了一些更改。 This示例有效,但有一些副作用。在这种情况下,我需要更改ID成员名称,以便发生nome碰撞。但后来我也改变了json标签。如果我没有更改标签,那么代码似乎需要花费太长时间才能运行,并且重复的标签都被省略了。 (here)。
PS:我不能肯定地说,但我猜这个假设存在问题。我会假设我要去Marshal的任何东西都希望能够UnMarshal。你的扁平化者不会这样做。如果你想要一个flattener你可能需要分叉JSON编码器并为标签添加一些值(就像有一个'omitempty'你可能会添加'flatten'。答案 4 :(得分:0)
该对象可能无法通过JavaScript消耗,因为它包含两个对象,除非您将对象包装在一个数组中。
[
{
"id": 123,
"name": "James Dean",
"_links": {
"self": "http://user/123"
}
},
{
"id": 456,
"userId": 123,
"_links": {
"self": "http://session/456"
}
}
]
然后你可以使用这个JSON,例如:
var user, session;
user = jsonString[0];
session = jsonString[1];
考虑给出对象根名称可能是一个更好的考虑因素,例如:
{
"user": {
"id": 123,
"name": "James Dean",
"_links": {
"self": "http://user/123"
}
},
"session": {
"id": 456,
"userId": 123,
"_links": {
"self": "http://session/456"
}
}
}
并消费为例如:
var user, session;
user = jsonString.user;
session = jsonString.session;
我希望这有助于你
答案 5 :(得分:0)
我需要做类似的事情并尝试使用嵌入式界面的技术,并且(毫不奇怪)有同样的问题。
我不想改变所有可能需要额外字段的结构,但最终我已经妥协了这个解决方案:
http://play.golang.org/p/asLFPx76jw
package main
import (
"encoding/json"
"fmt"
)
type HateoasFields struct {
Links []interface{} `json:"_links,omitempty"`
}
type User struct {
Id int `json:"id"`
Name string `json:"name"`
HateoasFields
}
type Session struct {
Id int `json:"id"`
UserId int `json:"userId"`
HateoasFields
}
func main() {
u := &User{Id: 123, Name: "James Dean"}
s := &Session{Id: 456, UserId: 123}
u.Links = []interface{}{fmt.Sprintf("http://user/%d", u.Id)}
s.Links = []interface{}{fmt.Sprintf("http://session/%d", s.Id)}
uBytes, _ := json.Marshal(u)
sBytes, _ := json.Marshal(s)
fmt.Println(string(uBytes))
fmt.Println(string(sBytes))
}
哪个输出:
{"id":123,"name":"James Dean","_links":["http://user/123"]}
{"id":456,"userId":123,"_links":["http://session/456"]}
不是将原始结构嵌入到带有附加字段的结构中,而是反过来。
在查看原始代码时,我不认为这个解决方案非常棒。这比将Links属性添加到原始结构更好吗?但对于我的应用程序需求,这是最好的事情。
答案 6 :(得分:0)
如果你只想担心地图,我喜欢这个解决方案。
// Flatten takes a map and returns a new one where nested maps are replaced
// by dot-delimited keys.
func Flatten(m map[string]interface{}) map[string]interface{} {
o := make(map[string]interface{})
for k, v := range m {
switch child := v.(type) {
case map[string]interface{}:
nm := Flatten(child)
for nk, nv := range nm {
o[k+"."+nk] = nv
}
default:
o[k] = v
}
}
return o
}