在Go中使用匿名成员展平编组的JSON结构

Lee*_*ley 7 json marshalling go

鉴于以下代码:( 转载于此处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))
    }
}
Run Code Online (Sandbox Code Playgroud)

我试图让渲染的JSON 在我的情况下看起来正确,这意味着:

User JSON:
{
    "id": 123,
    "name": "James Dean",
    "_links": {
        "self": "http://user/123"
    }
}
Session JSON:
{
    "id": 456,
    "userId": 123,
    "_links": {
        "self": "http://session/456"
    }
}
Run Code Online (Sandbox Code Playgroud)

遗憾的是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"
    }
}
Run Code Online (Sandbox Code Playgroud)

文档中没有关于JSON中匿名成员处理的明确文档:

匿名结构字段通常被编组,就好像它们的内部导出字段是外部结构中的字段一样,受到通常的Go可见性规则的限制,如下一段所述.在其JSON标记中给出名称的匿名结构字段被视为具有该名称,而不是匿名.

处理匿名结构字段是Go 1.1中的新功能.在Go 1.1之前,匿名结构字段被忽略.要强制忽略当前版本和早期版本中的匿名结构字段,请为该字段指定" - "的JSON标记.

这并没有说清楚是否有办法压扁,或暗示马歇尔勒我想做什么.

我确信,有一个特殊情况,可能有魔术名称,它具有重命名XML编组器中XML文档的根元素的特殊含义.

在这种情况下,我也没有以任何方式附加代码,我的用例是有一个接收interface{}, *http.Request, http.ResponseWriter和写回HATEOAS文档的函数,打开传递的类型,以推断要写回的链接进入JSON.(从而访问请求,请求主机,端口,方案等,以及类型本身以推断URL和已知字段等)

Kri*_*ard 3

工作游乐场链接:http://play.golang.org/p/_r-bQIw347

其要点是这样的;通过使用 Reflect 包,我们循环遍历想要序列化的结构体字段,并将它们映射到 amap[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))
    }
}
Run Code Online (Sandbox Code Playgroud)