How to parse an inner field in a nested JSON object - json

I have a JSON object similar to this one:
{
"name": "Cain",
"parents": {
"mother" : "Eve",
"father" : "Adam"
}
}
Now I want to parse "name" and "mother" into this struct:
struct {
Name String
Mother String `json:"???"`
}
I want to specify the JSON field name with the json:... struct tag, however I don't know what to use as tag, because it is not the top object I am interested in. I found nothing about this in the encoding/json package docs nor in the popular blog post JSON and Go. I also tested mother, parents/mother and parents.mother.

You could use structs so long as your incoming data isn't too dynamic.
http://play.golang.org/p/bUZ8l6WgvL
package main
import (
"fmt"
"encoding/json"
)
type User struct {
Name string
Parents struct {
Mother string
Father string
}
}
func main() {
encoded := `{
"name": "Cain",
"parents": {
"mother": "Eve",
"father": "Adam"
}
}`
// Decode the json object
u := &User{}
err := json.Unmarshal([]byte(encoded), &u)
if err != nil {
panic(err)
}
// Print out mother and father
fmt.Printf("Mother: %s\n", u.Parents.Mother)
fmt.Printf("Father: %s\n", u.Parents.Father)
}

Unfortunately, unlike encoding/xml, the json package doesn't provide a way to access nested values. You'll want to either create a separate Parents struct or assign the type to be map[string]string. For example:
type Person struct {
Name string
Parents map[string]string
}
You could then provide a getter for mother as so:
func (p *Person) Mother() string {
return p.Parents["mother"]
}
This may or may not play into your current codebase and if refactoring the Mother field to a method call is not on the menu, then you may want to create a separate method for decoding and conforming to your current struct.

Here's some code I baked up real quick in the Go Playground
http://play.golang.org/p/PiWwpUbBqt
package main
import (
"fmt"
"encoding/json"
)
func main() {
encoded := `{
"name": "Cain",
"parents": {
"mother": "Eve"
"father": "Adam"
}
}`
// Decode the json object
var j map[string]interface{}
err := json.Unmarshal([]byte(encoded), &j)
if err != nil {
panic(err)
}
// pull out the parents object
parents := j["parents"].(map[string]interface{})
// Print out mother and father
fmt.Printf("Mother: %s\n", parents["mother"].(string))
fmt.Printf("Father: %s\n", parents["father"].(string))
}
There might be a better way. I'm looking forward to seeing the other answers. :-)

More recently, gjson supports selection of nested JSON properties.
name := gjson.Get(json, "name")
mother := gjson.Get(json, "parents.mother")

How about using an intermediary struct as the one suggested above for parsing, and then putting the relevant values in your "real" struct?
import (
"fmt"
"encoding/json"
)
type MyObject struct{
Name string
Mother string
}
type MyParseObj struct{
Name string
Parents struct {
Mother string
Father string
}
}
func main() {
encoded := `{
"name": "Cain",
"parents": {
"mother": "Eve",
"father": "Adam"
}
}`
pj := &MyParseObj{}
if err := json.Unmarshal([]byte(encoded), pj); err != nil {
return
}
final := &MyObject{Name: pj.Name, Mother: pj.Parents.Mother}
fmt.Println(final)
}

Related

JSON: Nesting a populated struct into a new struct

I have a struct like so:
type my_struct struct {
First string `json:"first"`
Second string `json:"second"`
Number int `json:"number"`
}
When I marshal that into JSON, it outputs very simple JSON as you'd expect:
var output_json []byte
output_json, _ = json.Marshal(output)
fmt.Println(string(output_json))
Result:
{"first":"my_string","second":"another_string","number":2}
All fine so far!
What I'd like to do, before marshalling that struct into JSON, is nest it inside another struct. The resulting output would be JSON that looks like this:
{
"fields": {
"first": "my_string",
"number": 2,
"second": "another_string"
},
"meta": "data"
}
How can I do that?
I think you could statically declare another struct to use:
type WrappedOutput struct {
fields my_struct
meta string
}
Then you could embed your struct before marshalling
o := WrappedOutput{fields: output}
Brand new to go so not sure if this is the easiest way to do it
The clean way to do this would be to declare 2 structs (I've done it globally below) and nest My_struct inside the higher level struct.
Then you can initialize the higher level struct before Mashalling it:
package main
import (
"encoding/json"
"fmt"
)
type My_struct struct {
First string `json:"first"`
Second string `json:"second"`
Number int `json:"number"`
}
type Big_struct struct {
Fields My_struct
Meta string
}
func main() {
output := Big_struct{
Fields: My_struct{
First: "my_string",
Second: "another_string",
Number: 2,
},
Meta: "data",
}
output_json, err := json.Marshal(output)
if err != nil {
fmt.Println(err)
}
fmt.Println(string(output_json))
}
if you don't want Big_struct you can declare an anonymous struct within your code as you need it and nest My_struct inside:
package main
import (
"encoding/json"
"fmt"
)
type My_struct struct {
First string `json:"first"`
Second string `json: "second"`
Number int `json:"number"`
}
func main() {
output := struct {
Fields My_struct
Meta string
}{
Fields: My_struct{
First: "my_string",
Second: "another_string",
Number: 2,
},
Meta: "data",
}
output_json, err := json.Marshal(output)
if err != nil {
fmt.Println(err)
}
fmt.Println(string(output_json))
}
If you don't want to use a new structure, you can do:
data := my_struct{First: "first", Second: "2", Number: 123}
result, _ := json.Marshal(&map[string]interface{}{"fields":data,"meta":"data"})
fmt.Println(string(result))
it's not clean, but it does the work.

How to unmarshal two json with same internal structure into one single golang struct?

I have two json files with following structure
{
"cast": [
{
"url": "carey-mulligan",
"name": "Carey Mulligan",
"role": "Actress"
},
{
"url": "leonardo-dicaprio",
"name": "Leonardo DiCaprio",
"role": "Actor"
},
.
.
.
]
}
and
{
"movie": [
{
"url": "carey-mulligan",
"name": "Carey Mulligan",
"role": "Actress"
},
{
"url": "leonardo-dicaprio",
"name": "Leonardo DiCaprio",
"role": "Actor"
},
.
.
.
]
}
as you can see internal structure of the json is same for cast and movie. I want to unmarshel these json file into the same golang structure. But i am not able to give two name tags (cast and movie) for same struct element. I want something like
type Detail struct {
Name string `json:"name"`
Url string `json:"url"`
Role string `json:"role"`
}
type Info struct {
Detail []Detail `json:"cast or movie"`
}
In which case Detail could parse both cast and movie.
Here is my current code
// RIMAGE project main.go
package main
import (
"encoding/json"
"fmt"
"io/ioutil"
)
const (
website = "https://data.moviebuff.com/"
)
func main() {
fmt.Println("Hello World!")
content, err := ioutil.ReadFile("data/great-getsby")
if err != nil {
panic(err)
}
var info Info
err = json.Unmarshal(content, &info)
if err != nil {
panic(err)
}
fmt.Println(info.Detail)
}
type Detail struct {
Name string `json:"name"`
Url string `json:"url"`
Role string `json:"role"`
}
type Info struct {
Detail []Detail `json:"cast" json:"movie"
}
but it only works for first tag "cast" and gives nill in case json contain the movie.
Thanks in advance.
You can use type Info map[string][]Detail instead of your struct.
Try it on the Go playground
Or you can use both types in your structure, and make method Details() which will return right one:
type Info struct {
CastDetails []Detail `json:"cast"`
MovieDetails []Detail `json:"movie"`
}
func (i Info) Details() []Detail {
if i.CastDetails == nil {
return i.MovieDetails
}
return i.CastDetails
}
Try it on the Go playground
Try anonymous field in struct:
package main
import (
"encoding/json"
"fmt"
"io/ioutil"
)
type Detail struct {
Name string `json:"name"`
Url string `json:"url"`
Role string `json:"role"`
}
type Cast struct {
Detail []Detail `json:"cast"`
}
type Movie struct {
Detail []Detail `json:"movie"`
}
type Info struct {
Cast
Movie
}
func (i *Info) getDetails() []Detail {
if len(i.Cast.Detail) > 0 {
return i.Cast.Detail
}
return i.Movie.Detail
}
func main() {
cast, _ := ioutil.ReadFile("./cast.json")
movie, _ := ioutil.ReadFile("./movie.json")
var cInfo Info
err := json.Unmarshal(cast, &cInfo)
fmt.Printf("cast: %+v\n", &cInfo)
fmt.Printf("err: %v\n", err)
fmt.Printf("details: %v\n", cInfo.getDetails())
var mInfo Info
err = json.Unmarshal(movie, &mInfo)
fmt.Printf("movie: %+v\n", &mInfo)
fmt.Printf("err: %v\n", err)
fmt.Printf("details: %v\n", mInfo.getDetails())
}
Things to note:
One more level of indirection: to access 'Details' field, you need to access either 'Cast' or 'Movie' field first in Info first.
Better provide an access function for 'Details' ('getDetail' in this example)
If you dig far enough down into encoding/json you'll get to https://github.com/golang/go/blob/master/src/encoding/json/encode.go and the following:
tag := sf.Tag.Get("json")
if tag == "-" {
continue
}
So it gets one json tag and keeps on going.
You could always go with RoninDev's solution and just copy it over when done.

Serialize a map using a specific order

I have a map that uses string for both key and value. I have an array of keys that specifies the order of the values of the map.
I want to serialize that map to a JSON, but keeping the order defined on the array.
There is a sample code here: http://play.golang.org/p/A52GTDY6Wx
I want to serialize it as:
{
"name": "John",
"age": "20"
}
But if I serialize the map directly, the keys are ordered alphabetically:
{
"age": "20",
"name": "John"
}
I can serialize it as an array of maps, thus keeping the order, however that generates a lot of undesired characters:
[
{
"name": "John"
},
{
"age": "20"
}
]
In my real code I need to serialize the results of a database query which is specified in a text file, and I need to maintain the column order. I cannot use structs because the columns are not known at compile time.
EDIT: I don't need to read the JSON later in the specified order. The generated JSON is meant to be read by people, so I just want it to be as humanly readable as possible.
I could use a custom format but JSON suits me perfectly for this.
Thanks!
You need to implement the json.Marshaler interface on a custom type. This has the advantage of playing well within other struct types.
Sorry, you're always going to have to write a little bit of JSON encoding code.
package main
import (
"bytes"
"encoding/json"
"fmt"
"log"
)
type KeyVal struct {
Key string
Val interface{}
}
// Define an ordered map
type OrderedMap []KeyVal
// Implement the json.Marshaler interface
func (omap OrderedMap) MarshalJSON() ([]byte, error) {
var buf bytes.Buffer
buf.WriteString("{")
for i, kv := range omap {
if i != 0 {
buf.WriteString(",")
}
// marshal key
key, err := json.Marshal(kv.Key)
if err != nil {
return nil, err
}
buf.Write(key)
buf.WriteString(":")
// marshal value
val, err := json.Marshal(kv.Val)
if err != nil {
return nil, err
}
buf.Write(val)
}
buf.WriteString("}")
return buf.Bytes(), nil
}
func main() {
dict := map[string]interface{}{
"orderedMap": OrderedMap{
{"name", "John"},
{"age", 20},
},
}
dump, err := json.Marshal(dict)
if err != nil {
log.Fatal(err)
}
fmt.Printf("%s\n", dump)
}
Outputs
{"orderedMap":{"name":"John","age":20}}
For that specific requirement you really don't need to use json.Marshal at all, you can simply implement your own function like this:
type OrderedMap map[string]string
func (om OrderedMap) ToJson(order ...string) string {
buf := &bytes.Buffer{}
buf.Write([]byte{'{', '\n'})
l := len(order)
for i, k := range order {
fmt.Fprintf(buf, "\t\"%s\": \"%v\"", k, om[k])
if i < l-1 {
buf.WriteByte(',')
}
buf.WriteByte('\n')
}
buf.Write([]byte{'}', '\n'})
return buf.String()
}
func main() {
om := OrderedMap{
"age": "20",
"name": "John",
}
fmt.Println(om.ToJson("name", "age"))
}
Probably the easiest solution: https://github.com/iancoleman/orderedmap
Although it might be slow as it's mentioned here
Here is a MapSlice implementation similar to what YAML v2 offers. It can do both Marshal and Unmarshal.
https://github.com/mickep76/mapslice-json

Golang issue with accessing Nested JSON Array after Unmarshalling

I'm still in the learning process of Go but am hitting a wall when it comes to JSON response arrays. Whenever I try to access a nested element of the "objects" array, Go throws (type interface {} does not support indexing)
What is going wrong and how can I avoid making this mistake in the future?
package main
import (
"encoding/json"
"fmt"
)
func main() {
payload := []byte(`{"query": "QEACOR139GID","count": 1,"objects": [{"ITEM_ID": "QEACOR139GID","PROD_CLASS_ID": "BMXCPGRIPS","AVAILABLE": 19}]}`)
var result map[string]interface{}
if err := json.Unmarshal(payload, &result); err != nil {
panic(err)
}
fmt.Println(result["objects"]["ITEM_ID"])
}
http://play.golang.org/p/duW-meEABJ
edit: Fixed link
As the error says, interface variables do not support indexing. You will need to use a type assertion to convert to the underlying type.
When decoding into an interface{} variable, the JSON module represents arrays as []interface{} slices and dictionaries as map[string]interface{} maps.
Without error checking, you could dig down into this JSON with something like:
objects := result["objects"].([]interface{})
first := objects[0].(map[string]interface{})
fmt.Println(first["ITEM_ID"])
These type assertions will panic if the types do not match. You can use the two-return form, you can check for this error. For example:
objects, ok := result["objects"].([]interface{})
if !ok {
// Handle error here
}
If the JSON follows a known format though, a better solution would be to decode into a structure. Given the data in your example, the following might do:
type Result struct {
Query string `json:"query"`
Count int `json:"count"`
Objects []struct {
ItemId string `json:"ITEM_ID"`
ProdClassId string `json:"PROD_CLASS_ID"`
Available int `json:"AVAILABLE"`
} `json:"objects"`
}
If you decode into this type, you can access the item ID as result.Objects[0].ItemId.
For who those might looking for similar solution like me, https://github.com/Jeffail/gabs provides better solution.
I provide the example here.
package main
import (
"encoding/json"
"fmt"
"github.com/Jeffail/gabs"
)
func main() {
payload := []byte(`{
"query": "QEACOR139GID",
"count": 1,
"objects": [{
"ITEM_ID": "QEACOR139GID",
"PROD_CLASS_ID": "BMXCPGRIPS",
"AVAILABLE": 19,
"Messages": [ {
"first": {
"text": "sth, 1st"
}
},
{
"second": {
"text": "sth, 2nd"
}
}
]
}]
}`)
fmt.Println("Use gabs:")
jsonParsed, _ := gabs.ParseJSON(payload)
data := jsonParsed.Path("objects").Data()
fmt.Println(" Fetch Data: ")
fmt.Println(" ", data)
children, _ := jsonParsed.Path("objects").Children()
fmt.Println(" Children Array from \"Objects\": ")
for key, child := range children {
fmt.Println(" ", key, ": ", child)
children2, _ := child.Path("Messages").Children()
fmt.Println(" Children Array from \"Messages\": ")
for key2, child2 := range children2 {
fmt.Println(" ", key2, ": ", child2)
}
}
}

golang json unmarshal part of map[string]interface{}

I have the following code to try to Unmarshal this json file, however the line json.Unmarshal([]byte(msg["restaurant"]), &restaurant) always gives an error. How can I make Unmarshal ignore the "restaurant" or pass only the "restaurant" data to the Unmarshal function?
Thanks!
{
"restaurant": {
"name": "Tickets",
"owner": {
"name": "Ferran"
}
}
}
file, e := ioutil.ReadFile("./rest_read.json")
if e != nil {
fmt.Println("file error")
os.Exit(1)
}
var data interface{}
json.Unmarshal(file, &data)
msg := data.(map[string]interface{})
log.Println(msg)
log.Println(msg["restaurant"])
log.Println(reflect.TypeOf(msg["restaurant"]))
var restaurant Restaurant
json.Unmarshal([]byte(msg["restaurant"]), &restaurant)
log.Println("RName: ", restaurant.Name)
log.Println("Name: ", restaurant.Owner.Name)
It is possible to do generic unmarshalling ala gson by decoding into an interface and then extracting a top level map from the result, e.g:
var msgMapTemplate interface{}
err := json.Unmarshal([]byte(t.ResponseBody), &msgMapTemplate)
t.AssertEqual(err, nil)
msgMap := msgMapTemplate.(map[string]interface{})
See "decoding arbitrary data" in http://blog.golang.org/json-and-go for more into.
I would propose to construct a proper model for your data. This will enable you to cleanly unmarshal your data into a Go struct.
package main
import (
"encoding/json"
"fmt"
)
type Restaurant struct {
Restaurant RestaurantData `json:"restaurant"`
}
type RestaurantData struct {
Name string `json:"name"`
Owner Owner `json:"owner"`
}
type Owner struct {
Name string `json:"name"`
}
func main() {
data := `{"restaurant":{"name":"Tickets","owner":{"name":"Ferran"}}}`
r := Restaurant{}
json.Unmarshal([]byte(data), &r)
fmt.Printf("%+v", r)
}
Unmarshalling occurs recursively, so msg["restaurant"] is no longer a json string - it is another map[string]interface{}. If you want to unmarshall directly into a Restaurant object, you will have to provide a simple wrapper object with a Restaurant member and unmarshall into that.