douqi2804 2013-12-03 21:34
浏览 54
已采纳

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

Given the following code: (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))
    }
}

I'm attempting to have the rendered JSON look correct in my case that means something like:

User JSON:
{
    "id": 123,
    "name": "James Dean",
    "_links": {
        "self": "http://user/123"
    }
}
Session JSON:
{
    "id": 456,
    "userId": 123,
    "_links": {
        "self": "http://session/456"
    }
}

Unfortunately Go is treating the anonymous member as a real named thing, so it's taking the defined type (Anything) and naming the JSON thusly:

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

There's no clear docs on the handling of anonymous members in JSON, from the docs:

Anonymous struct fields are usually marshaled as if their inner exported fields were fields in the outer struct, subject to the usual Go visibility rules amended as described in the next paragraph. An anonymous struct field with a name given in its JSON tag is treated as having that name, rather than being anonymous.

Handling of anonymous struct fields is new in Go 1.1. Prior to Go 1.1, anonymous struct fields were ignored. To force ignoring of an anonymous struct field in both current and earlier versions, give the field a JSON tag of "-".

This doesn't make clear if there's a way to flatten out, or hint to the Marshaller what I am trying to do.

I'm certain that there might be, as there is a special case, magic name that has a special meaning to rename the root element of an XML document in the XML marshaller.

In this case, I'm also not attached to the code in any way, my use-case is to have a function that accepts interface{}, *http.Request, http.ResponseWriter and write back HATEOAS documents down the wire, switching on the type passed, to infer which links to write back into the JSON. (thus access to the request, for request host, port, scheme, etc, as well as to the type itself to infer the URL and known fields, etc)

  • 写回答

7条回答 默认 最新

  • douzhicai7148 2013-12-04 10:57
    关注

    Working playground link: http://play.golang.org/p/_r-bQIw347

    The gist of it is this; by using the reflect package we loop over the fields of the struct we wish to serialize and map them to a map[string]interface{} we can now retain the flat structure of the original struct without introducing new fields.

    Caveat emptor, there should probably be several checks against some of the assumptions made in this code. For instance it assumes that MarshalHateoas always receives pointers to values.

    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))
        }
    }
    
    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论
查看更多回答(6条)

报告相同问题?

悬赏问题

  • ¥60 Cocos creator缓动问题
  • ¥40 微信小程序 使用vant组件ActionSheet 下拉面板,内容区域滚动会触发。scroll-view自定义下拉刷!即使设置停止下拉刷新也不行。
  • ¥15 专业问题提问,7月5号2点之前
  • ¥25 使用cube ai 导入onnx模型时报错
  • ¥15 关于#微信小程序#的问题:用一个网页显示所有关联的微信小程序数据,包括每个小程序的用户访问量
  • ¥15 root的安卓12系统上,如何使apk获得root或者高级别的系统权限?
  • ¥20 关于#matlab#的问题:如果用MATLAB函数delayseq可以对分数延时,但是延时后波形较原波形有幅度上的改变
  • ¥15 使用华为ENSP软件模拟实现该实验拓扑
  • ¥15 通过程序读取主板上报税口的数据
  • ¥15 matlab修改为并行