Parse JSON with an array in golang - json

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

Related

How to convert string to JSON in Go?

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

Unmarshal JSON with an array

I am trying to Unmarshal the following JSON object which is generated by couchDB and returns for a cURL request in Go, the cURL request code is not mentioned here as it is out of scope of this question and I have assigned it to the variable called mail in the code section.
the JSON data structure:
{
"total_rows": 4,
"offset": 0,
"rows": [{
"id": "36587e5d091a0d49f739c25c0b000c05",
"key": "36587e5d091a0d49f739c25c0b000c05",
"value": {
"rev": "1-92471472a3de492b8657d3103f5f6e0d"
}
}]
}
and here is my code to unmarshel the above JSON object,
package main
import (
"fmt"
"encoding/json"
)
type Couchdb struct {
TotalRows int `json:"total_rows"`
Offset int `json:"offset"`
Rows []struct {
ID string `json:"id"`
Key string `json:"key"`
Value struct {
Rev string `json:"rev"`
} `json:"value"`
} `json:"rows"`
}
func main() {
mail := []byte(`{"total_rows":4,"offset":0,"rows":[{"id":"36587e5d091a0d49f739c25c0b000c05","key":"36587e5d091a0d49f739c25c0b000c05","value":{"rev":"1-92471472a3de492b8657d3103f5f6e0d"}}]}`)
var s Couchdb
err := json.Unmarshal(mail, &s)
if err != nil {
panic(err)
}
//fmt.Printf("%v", s.TotalRows)
fmt.Printf("%v", s.Rows)
}
and the above code is working fine and you can access the working code here with this link in the Go Play Ground.
I need to get the 36587e5d091a0d49f739c25c0b000c05 value which is the id of rows so I am trying to do it like this
fmt.Printf("%v", s.Rows.ID)
and it returns this error
prog.go:33:25: s.Rows.ID undefined (type []struct { ID string "json:\"id\""; Key string "json:\"key\""; Value struct { Rev string "json:\"rev\"" } "json:\"value\"" } has no field or method ID)
but it works for fmt.Printf("%v", s.Rows) and it returns
[{36587e5d091a0d49f739c25c0b000c05 36587e5d091a0d49f739c25c0b000c05 {1-92471472a3de492b8657d3103f5f6e0d}}]
My ultimate goal is to get 36587e5d091a0d49f739c25c0b000c05 and assign it to GO variable but stuck getting that value using GO.
You have to call :
fmt.Println(s.Rows[0].ID)
You define Rows as slice of struct that means you should iterate Rows with for for execution of values.
for _, item := range s.Rows {
fmt.Println(item.ID)
}

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
}

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

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