How to convert string to JSON in Go? - json

Let’s say I have this string:
`
{
"testCode": 0,
"replyTest": "OK",
"data": {
"001": {
"fields": {
"name": "arben",
"fav_color": "blue",
"address": "PH",
}
},
"002": {
"fields": {
"name": "john",
"fav_color": "black",
"address": "PH",
}
},
}
}
`
How to convert this string to JSON where data is in form of list in order for me to loop this list in a process?

Whenever you have json property names that aren't known upfront, or they don't lend themselves very well to be represented as fields of a struct then you're left more or less with one option, a map. So first you need to unmarshal the "data" property into a map.
However maps in Go are implemented as unordered groups of elements indexed by a set of unique keys. So basically there's no way to ensure the order of a map in Go, and therefore you'll have to transfer the data from the map into something that can be ordered, like a slice for example.
After you've got your data in the slice you can use the standard sort package to sort the slice in the order you want.
You can start by declaring the types you need:
type DataItem struct {
Key string `json:"-"`
Fields DataFields `json:"fields"`
}
type DataFields struct {
Name string `json:"name"`
FavColor string `json:"fav_color"`
Address string `json:"address"`
}
Then unmarshal the json
var obj struct {
TestCode int `json:"testCode"`
ReplyTest string `json:"replyTest"`
Data map[string]*DataItem `json:"data"`
}
if err := json.Unmarshal(data, &obj); err != nil {
panic(err)
}
Transfer the contents of the map into a slice
items := []*DataItem{}
for key, item := range obj.Data {
item.Key = key // keep track of the key because it will be used to order the contents of the slice
items = append(items, item)
}
Finally sort the slice
sort.Slice(items, func(i, j int) bool {
return items[i].Key < items[j].Key
})
https://play.golang.com/p/D2u46veOQwD

Related

Parse JSON with an array in golang

I would like to retrieve all IDs and also the index value.
{
"results": [
{
"ID": "3b72"
},
{
"ID": "d953b1"
},
{
"ID": "8eac"
},
{
"ID": "00b4c"
},
{
"ID": "22360"
}
],
"index": 10
}
I tried the following but no success ( I am quite new in golang):
var result map[string]interface{}
json.Unmarshal([]byte(body), &result)
ids:= result["results"].(map[string]interface{})
for key, value := range ids{
fmt.Println(key, value.(string))
}
As per your json string, few things need to be fixed. First, the result["results"] property need to be casted as []interface{} because results hold an array data.
ids := result["results"].([]interface{})
Then, on each iteration of ids, variable value (whose type is interface{}) need to be casted into map[string]interface{}, so we will be able to access ID property to get it's value.
for index, value := range ids {
valueMap := value.(map[string]interface{})
fmt.Println(index, valueMap["ID"])
}
Working example: https://play.golang.org/p/Pxq6u7q-NAK
As per #Andrejs' answer, if your JSON structure is fixed, then it would be better to use well defined struct on the result (see #danopz's answer). It has better performance and consume less effort interact with it's child.
You should probably define a struct for the body and unmarshal into this:
var result struct {
Results []struct{
ID string `json:"ID"`
} `json:"results"`
Index int `json:"index"`
}
err := json.Unmarshal(body, &result)
if err != nil {
// do something, result may be empty
return
}
for i, result := range result.Results {
fmt.Println(i, result.ID)
}
Then you would have access to the values e.g. by calling result.Index or result.Results[0].ID.
Working example: https://play.golang.org/p/j1WpEolQlXV

How to show empty object instead of empty struct or nil in Go json marshal

I need to show json's empty object {} when do json.Marshal() for a struct pointer. I can only output either null value or empty struct value.
If the person key is filled with &Person{} or new(Person), it will show empty struct like below:
{
"data": {
"person": {
"name": "",
"age": 0
},
"created_date": "2009-11-10T23:00:00Z"
}
}
And if we don't initialize it at all, it will show null.
{
"data": {
"person": null,
"created_date": "2009-11-10T23:00:00Z"
}
}
I want to show "person": {}. Is it possible?
Go Playground for the complete code: https://play.golang.org/p/tT15G2ESPVc
Option A, use the omitempty tag option on all of the Person's fields and make sure the response's field is allocated before marshaling.
type Person struct {
Name string `json:"name,omitempty"`
Age int `json:"age,omitempty"`
}
// ...
resp.Person = new(Person)
https://play.golang.org/p/o3jWdru_8bC
Option B, use a non-pointer wrapper type that embeds the Person pointer type.
type PersonJSON struct {
*Person
}
type Response struct {
Person PersonJSON `json:"person"`
CreatedDate time.Time `json:"created_date"`
}
https://play.golang.org/p/EKQc7uf1_Vk
Option C, have the Reponse type implement the json.Marshaler interface.
func (r *Response) MarshalJSON() ([]byte, error) {
type tmp Response
resp := (*tmp)(r)
var data struct {
Wrapper struct {
*Person
} `json:"person"`
*tmp
}
data.Wrapper.Person = resp.Person
data.tmp = resp
return json.Marshal(data)
}
https://play.golang.org/p/1qkSCWZ225j
There may be other options...
In Go, an empty struct by definition assigns zero values to field elements. Eg: for int 0, "" for string, etc.
For your case, simply comparing to null would work out. Or, you could define an emptyPerson as:
var BAD_AGE = -1
emptyPerson := &Person{"", BAD_AGE} // BAD_AGE indicates no person
if person[age] == BAD_AGE {
// handle case for emptyPerson}

Golang map containing both string and integer

I am trying to create a JSON string from a map using JSON.Marshal() in golang. However, the int values are being displayed as strings surrounded by double quotes.
My code is outputting:
{ "age":
{
"$gt":"22",
"$lt":"20"
},
"location":
{
"$eq":"london"
},
"name":{
"$eq":"fred"
}
}
instead of
{ "age":
{
"$gt":22,
"$lt":20
},
"location":
{
"$eq":"london"
},
"name":{
"$eq":"fred"
}
}
I am using:
var output_map = map[string]map[string]string{}
//Populate map here
output_json, err := json.Marshal(output_map)
if err!= nil {
fmt.Println("Error encoding JSON")
}
fmt.Println(output_json)
My understanding is that JSON.Marshal() will print the integers correctly if they are supplied but my map won't contain integers. I could change my map to map[string]map[string]int{} but then it wouldn't contain the string values for 'name' and 'location'.
The ultimate problem is that I need the map to contain both int and string values. Some sort of map[string]map[string]{}.
How can I achieve this? Thank you in advance.
Harry
If you cannot describe your data with a properly typed struct then consider using a map with values of type interface{} (essentially any type):
output_map := map[string]map[string]interface{}{}
For example:
output_map := map[string]map[string]interface{}{
"age": {
"$gt": 18,
},
"location": {
"eq": "London",
},
}
bytes, err := json.MarshalIndent(&output_map, "", " ")
if err != nil {
panic(err)
}
// {
// "age": {
// "$gt": 18
// },
// "location": {
// "eq": "London"
// }
// }
Of course, using the interface{} type is not a best-practice; however, it's sometimes the only way to accomplish certain things.
We can declare variable for mix mapping with integer,string as below.
var variable_name = map[string]interface{}{}
e.g.
var variable_name = map[string]interface{}{
"name": c.String("name"),
"age": c.Int("age"),
}
I agree with maerics,
map[string]interface{} would be the way to go, if you have to avoid structs.
From your content I assume, that you are dealing with mongodb-queries.
So maybe the following code helps.
If you are going to query mongodb from go I would recommend the mgo-driver mgo.v2.
It implements a custom type bson.M which is the same as mentioned above, but works also for querying the db.
Beside this, it makes the code more readable.
Sample:
package main
import (
"fmt"
"gopkg.in/mgo.v2/bson"
"encoding/json"
)
func main() {
bsonMap := bson.M{
"age": bson.M{
"$gt": 22,
"$lt": 20,
},
"location": bson.M{"$eq": "london",},
"name": bson.M{"$eq": "fred"},
}
bytes, err := json.Marshal(bsonMap)
if err != nil {
panic(err)
}
fmt.Println(string(bytes))
}
Your code works as expected: map[string]map[string]string{} contains only string values, so json.Marshal(interface{}) return json string with marshaled string values.
I think you should use struct instead of map. Just create a struct, something like this:
type Age struct {
Gt int `json:"gt"`
Lt int `json:"lt"`
}
type Person struct {
Age Age `json:"age"`
Location string `json:"location"`
Name string `json:"name"`
}
And just marshal it with standart json.Marshal(interface{}) method.
(in this code json tags isn't required, but you can use it if you want to change names of fields in serialized json string)
I create test.go file with following code in main():
person := Person{
Age{
22,
20,
},
"London",
"Fred",
}
serialized, e := json.Marshal(person)
if e != nil {
fmt.Println(e)
return
}
fmt.Println(string(serialized))
and it returns this:
{
"age":
{
"gt":22,
"lt":20
},
"location":"London",
"name":"Fred"
}
P.S. I can't recommend you to use map[string]map[string]interface{} because in this case you need to have custom Unmarshaller (to fill up your map back). With structs you can unmarshal it by this way:
deserialized := Person{}
e = json.Unmarshal(serialized, &deserialized)
if e != nil {
fmt.Println("can't deserialize:", e)
return
}

Unmarshal JSON that could be a string or an object

I have a third party service that is returning JSON where one field contains a collection of data. Here is an example of the structure it returns.
{
"title": "Afghanistan",
"slug": "afghanistan",
"fields": {
"fieldOne": "",
"fieldTwo": {
"new1": {
"type": "contentBlock",,
"fields": {
"richTextBlick": "<p>This is the travel advice.<\/p>"
}
}
},
"fieldThree": {
"new1": {
"type": "introBlock",
"fields": {
"text": "This is a title"
"richText": "<p>This is the current travel summary for Afganistan.<\/p>"
}
},
"new2": {
"type": "contentBlock",
"fields": {
"richText": "<p>It has a second block of content!<\/p>"
}
}
},
"fieldfour": "country"
}
}
Each of the "field" entries could be either a string or another object. I'd like to decode these into something like the structs below.
type EntryVersion struct {
Slug string `json:"slug"`
Fields map[string][]EntryVersionBlock `json:"fields"`
}
type EntryVersionBlock struct {
Type string `json:"type"`
Fields map[string]string `json:"fields"`
}
If the field value is simply a string, I would wrap it in an EntryVersionBlock with the type of "Text" and a single entry in the Fields map.
Any ideas how can I do this in an efficient manner? I may have to do it several hundred times in an extreme edge-case.
Thanks
Your structure is a little bit off from the json's. Fields in EntryVersion is an object not an array so making it a slice is not what you want. I would recommend you change it to this:
type EntryVersion struct {
Slug string `json:"slug"`
Fields map[string]EntryVersionBlock `json:"fields"`
}
The EntryVersionBlock does not have a "type" field in the corresponding json, it has entries with field names "new1", "new2", etc. So I would recommend you add a 3rd type Entry into which you unmarshal those fields.
type Entry struct {
Type string `json:"type"`
Fields map[string]string `json:"fields"`
}
And you would update your EntryVersionBlock to look something like this:
type EntryVersionBlock struct {
Value string `json:"-"`
Fields map[string]Entry `json:"fields"`
}
And to deal with your original problem you can have the EntryVersionBlock type implement the json.Unmarshaler interface, check the first byte in the data passed to the UnmarshalJSON method and if it's a double quote it's a string and if it's a curly opening brace it's an object. Something like this:
func (evb *EntryVersionBlock) UnmarshalJSON(data []byte) error {
switch data[0] {
case '"':
if err := json.Unmarshal(data, &evb.Value); err != nil {
return err
}
case '{':
evb.Fields = make(map[string]Entry)
if err := json.Unmarshal(data, &evb.Fields); err != nil {
return err
}
}
return nil
}
Playground: https://play.golang.org/p/IsTXI5202m
You can use GJson library to unmarshal your JSON to map, then iterate on this map and do the conversion that you need using your structs and based on fields type (map or string).
Update: Example using this method with similar case
http://blog.serverbooter.com/post/parsing-nested-json-in-go/

How can I parse JSON in GoLang if nested content uses dynamic keys?

I received JSON below from a client API but i am struggling to get nested JSON content. How can I parse it if the inner keys are dynamic?
const jsonStream = `
{
"items": {
"bvu62fu6dq": {
"name": "john",
"age": 23,
"xyz": "weu33s"
},
"iaxdw23fq": {
"name": "kelly",
"age": 21,
"xyz": "weu33s"
}
}
}`
This is what i have tried below by looping the map to extract the value of name and age from above JSON string; but it returns map with nil value as a result.
goplaygound
type Person struct {
Name string `json:"name"`
Age int `json:"age"`
}
type Item struct {
Contact struct {
Info map[string]Person
} `json:"items"`
}
func main() {
dec := json.NewDecoder(strings.NewReader(jsonStream))
for {
var item Item
if err := dec.Decode(&item); err == io.EOF {
break
} else if err != nil {
log.Fatal(err)
}
fmt.Printf("%s\n", item.Contact.Info["bvu62fu6dq"].Name)
}
}
Try this instead, looks like you just have your structure set up incorrectly:
http://play.golang.org/p/VRKbv-GVQB
You need to parse the entire json string, which is an object that contains a single element named items. items then contains a map of string -> Person objects.
If you only want to extract name and age from each person, you do it by grabbing data.Items["bvu62fu6dq"].Name.
If you want dynamic keys inside the Person, you'll need to do map[string]interface{} instead of Person in order to capture the dynamic keys again. It would look something like:
type Data struct {
Items map[string]map[string]interface{} `json:"items"`
}
...
fmt.Printf("%v\n", data.Items["bvu62fu6dq"]["name"]
fmt.Printf("%v\n", data.Items["bvu62fu6dq"]["age"]
fmt.Printf("%v\n", data.Items["bvu62fu6dq"]["xyz"]