Smooth ordered JSON structures with anonymous members in Go

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 am trying to make the displayed JSON look correct in my case, it 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 treats the anonymous element as a real named thing, so it takes a specific type ( Anything ) and calls JSON like this:

 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 are no clear documents about the processing of anonymous members in JSON, from documents :

Anonymous structure fields are usually marshalled as if their internal exported fields were fields in the external structure, provided that the usual Go visibility rules are followed, as described in the next paragraph. An anonymous structure field with the name specified in its JSON tag is considered to have that name, not anonymous.

Handling anonymous structure fields is new in Go 1.1. Prior to transition 1.1, anonymous structure fields were ignored. To force an anonymous structure field to be ignored in the current and earlier versions, give the field a JSON tag for "-".

This does not clarify if there is a way to smooth or hint to Marshaller what I am trying to do.

I am sure that there may be, as there is a special case, a magic name that is of particular importance for renaming the root element of an XML document in an XML marshaller.

In this case, I am also not bound to the code in any way, my use case should have a function that takes interface{}, *http.Request, http.ResponseWriter , and write HATEOAS documents by wire, switching the type passed, to output a link to JSON record. (thus, access to the request, for the host, port, scheme, etc. of the request, as well as for the type itself to display the URL and known fields, etc.)

+6
source share
7 answers

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

Its essence is this; using the reflection package, we iterate over the fields of the structure that we want to serialize and map them to map[string]interface{} , now we can save the flat structure of the original structure without introducing new fields.

Caution emptor probably should have some checks against some assumptions made in this code. For example, it is assumed that MarshalHateoas always gets 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)) } } 
+2
source

Use omitempty tags and a bit of logic, so you can use one type that gives the correct output for different cases.

The trick is to know when a value is considered empty using a JSON encoder. From the / json coding documentation:

Null values: false, 0, any nil value or interface value and any array, slice, map or zero-length string.

Here is your program slightly modified to display the desired result. It skips certain fields when their values โ€‹โ€‹are "empty" - in particular, the JSON encoder will omit the int with "0" as the value and display it with zero length.

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

Copy to play.golang.org.

0
source

One approach I've worked with is using methods for types. See http://play.golang.org/p/bPWB4ryDQn for more details.

Basically, you are working with a problem from the opposite angle โ€” instead of โ€œencapsulatingโ€ the base type in the Hateoas type, you instead include the required map in each of your base types. Then implement a method for each of these base types, which is responsible for updating the link field accordingly.

This leads to the intended result and only to a conical slab with limited source code.

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

I believe that any other way, especially if you take the embed and expand approach, will require the creation of a custom marshaler ( http://golang.org/pkg/encoding/json/#Marshaler ) and most likely will require the use of a reflection package as well , especially since everything has an interface type {}.

0
source

This is pretty tricky. One thing is certain that the document refers to your sample as follows:

Interface values โ€‹โ€‹are encoded as the value contained in the interface.

therefore there is nothing more to do from the same link . "Anything", while this is anonymous, is an interface variable, so I could expect behavior in your example.

I took your code and made some changes. This example shows, but has some side effects. In this case, I had to change the names of the member IDs so that a node collision occurred. But then I also changed the json tag. If I didn't change the tag, then the code seemed to run too long and overlapping tags were omitted. ( here ).

PS: I canโ€™t say with any certainty, but I would suggest that there is a problem with the assumption. I would suggest that all that I am going to Marshal, I would like to have the opportunity of Unmarshal. Your flattener just won't do it. If you need a flatter, you may need to fork the JSON encoder and add some values โ€‹โ€‹to the tags (just like there is "omitempty", you can add "smooth".

0
source

Sorry, but I think the JSON you are trying to generate is not a valid JSON object, and therefore may be the reason JsonMarshal is not playing with you.

An object may not be consumed through JavaScript, as it contains two objects, unless you wrap the objects in an array.

 [ { "id": 123, "name": "James Dean", "_links": { "self": "http://user/123" } }, { "id": 456, "userId": 123, "_links": { "self": "http://session/456" } } ] 

Then you can use this JSON, for example:

 var user, session; user = jsonString[0]; session = jsonString[1]; 

Consider which root object names might be better considered, for example:

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

and consumed as, for example:

 var user, session; user = jsonString.user; session = jsonString.session; 

Hope this helps you.

0
source

I needed to do something similar, and tried your technical interface, and (not surprisingly) had the same problem.

I did not need to change all possible structures that need additional fields, but in the end I compromised this solution:

http://play.golang.org/p/asLFPx76jw

main package

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

What outputs:

 {"id":123,"name":"James Dean","_links":["http://user/123"]} {"id":456,"userId":123,"_links":["http://session/456"]} 

Instead of embedding the original structures in the structure with additional fields, I did the opposite.

When you look at your source code, I donโ€™t think this solution is awesome. How is this better than adding the Links property to the original structure? But for my applications, this was the best.

0
source

I like this solution if you only have cards to worry about.

 // 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 } 
0
source

Source: https://habr.com/ru/post/959279/