In Go I usually unmarshal my JSON into a struct and read values from the struct.. it works very well.
This time around I am only concerned with a certain element of a JSON object and because the overall JSON object is very large, I don't want to have to create a struct.
Is there a way in Go so that I can lookup values in the JSON object using keys or iterate arrays as per usual.
Considering the following JSON how could I pull out the title field only.
{
"title": "Found a bug",
"body": "I'm having a problem with this.",
"assignee": "octocat",
"milestone": 1,
"labels": [
"bug"
]
}
Dont declare fields you dont want.
https://play.golang.org/p/cQeMkUCyFy
package main
import (
"fmt"
"encoding/json"
)
type Struct struct {
Title string `json:"title"`
}
func main() {
test := `{
"title": "Found a bug",
"body": "I'm having a problem with this.",
"assignee": "octocat",
"milestone": 1,
"labels": [
"bug"
]
}`
var s Struct
json.Unmarshal([]byte(test), &s)
fmt.Printf("%#v", s)
}
Or if you want to completely get rid of structs:
var i interface{}
json.Unmarshal([]byte(test), &i)
fmt.Printf("%#v\n", i)
fmt.Println(i.(map[string]interface{})["title"].(string))
Or. It will swallow all conversion errors.
m := make(map[string]string)
json.Unmarshal([]byte(test), interface{}(&m))
fmt.Printf("%#v\n", m)
fmt.Println(m["title"])
To extend Darigaaz's answer, you can also use an anonymous struct declared within the parsing function. This avoids having to have package-level type declarations litter the code for single-use cases.
https://play.golang.org/p/MkOo1KNVbs
package main
import (
"encoding/json"
"fmt"
)
func main() {
test := `{
"title": "Found a bug",
"body": "I'm having a problem with this.",
"assignee": "octocat",
"milestone": 1,
"labels": [
"bug"
]
}`
var s struct {
Title string `json:"title"`
}
json.Unmarshal([]byte(test), &s)
fmt.Printf("%#v", s)
}
Check out go-simplejson
Example:
js, err := simplejson.NewJson([]byte(`{
"test": {
"string_array": ["asdf", "ghjk", "zxcv"],
"string_array_null": ["abc", null, "efg"],
"array": [1, "2", 3],
"arraywithsubs": [{"subkeyone": 1},
{"subkeytwo": 2, "subkeythree": 3}],
"int": 10,
"float": 5.150,
"string": "simplejson",
"bool": true,
"sub_obj": {"a": 1}
}
}`))
if _, ok = js.CheckGet("test"); !ok {
// Missing test struct
}
aws := js.Get("test").Get("arraywithsubs")
aws.GetIndex(0)
package main
import (
"encoding/json"
"fmt"
)
type Seller struct {
Name string
ShareName string
Holdings int
Quantity int
PerShare float64
}
type Buyer struct {
Name string
ShareName string
Holdings int
Quantity int
PerShare float64
}
func validateTransaction(seller Seller,buyer Buyer) bool{
var isallowTransact bool =false
if (seller.Quantity >=buyer.Quantity &&seller.PerShare == buyer.PerShare && seller.ShareName ==buyer.ShareName){
isallowTransact=true;
}
return isallowTransact
}
func executeTransaction(seller Seller,buyer Buyer) {
seller.Holdings -=seller.Quantity;
buyer.Holdings +=seller.Quantity;
fmt.Printf("seller current holding : %d, \n buyyer current holding: %d",seller.Holdings,buyer.Holdings)
}
func main() {
sellerJson :=`{"name":"pavan","ShareName":"TCS","holdings":100,"quantity":30,"perShare":11.11}`
buyerJson :=`{"name":"Raju","ShareName":"TCS","holdings":0,"quantity":30,"perShare":14.11}`
var seller Seller
var buyer Buyer
json.Unmarshal([]byte(sellerJson ), &seller)
json.Unmarshal([]byte(buyerJson ), &buyer)
//fmt.Printf("seller name : %s, shares of firm: %s,total holdings: %d, want to sell: %d,unit cost: %f", seller.Name , seller.ShareName,seller.Holdings , seller.Quantity,seller.PerShare )
var isallowExecute bool =false
isallowExecute =validateTransaction(seller,buyer)
if(isallowExecute){
executeTransaction(seller,buyer)
}else{
fmt.Print("\n seems buyer quotes are not matching with seller so we are not able to perform transacrion ,Please check and try again");
}
fmt.Println("\n Happy Trading...!!");
}
Update: This answer is wrong; I'm leaving it as an example of what not to do.
You should look it up with a regex then (pseudocode):
String s = {json source};
int i = s.indexOf("\"title\": \"")
s.substring(i,s.indexOf("\"",i))
more on substrings in Java
Related
With a json file that looks something like this, where someone can only have one social but it could be different depending on the person, how would I unmarshal this into a struct.
[
{
"name": "Bob",
"age": 14,
"occupation": "Builder",
"social": {
"facebook": "Bob_the_builder"
},
{
"name": "Alice",
"age": 14,
"occupation": "Builder",
"social": {
"twitter": "Alice_the_builder"
}
]
My current struct variables looks like this.
type User struct {
Name String 'json:"name"'
Age int 'json:"age"'
Occupation String 'json:"occupation"'
Social Social 'json:"social"'
}
type Social struct {
Facebook String 'json:"facebook"'
Twitter String 'json:"twitter"'
}
Is this the correct way to unmarshall the json or will it fail as the 'Social' struct wants two different strings one for Facebook and one for Twitter. Is there a better way to unmarshall a json like this?
The example you provided should work, it will just leave one of your strings empty.
If you want to replace Social with 2 different structs, you can make Social an interface which both new structs implement. Then use json.RawMessage to delay the unmarshalling of the json in social. This is the example given in the godocs:
import (
"encoding/json"
"fmt"
"log"
)
func main() {
type Color struct {
Space string
Point json.RawMessage // delay parsing until we know the color space
}
type RGB struct {
R uint8
G uint8
B uint8
}
type YCbCr struct {
Y uint8
Cb int8
Cr int8
}
var j = []byte(`[
{"Space": "YCbCr", "Point": {"Y": 255, "Cb": 0, "Cr": -10}},
{"Space": "RGB", "Point": {"R": 98, "G": 218, "B": 255}}
]`)
var colors []Color
err := json.Unmarshal(j, &colors)
if err != nil {
log.Fatalln("error:", err)
}
for _, c := range colors {
var dst interface{}
switch c.Space {
case "RGB":
dst = new(RGB)
case "YCbCr":
dst = new(YCbCr)
}
err := json.Unmarshal(c.Point, dst)
if err != nil {
log.Fatalln("error:", err)
}
fmt.Println(c.Space, dst)
}
}
In this example they use a string to indicate as which struct to unmarshall Point. In your case you may need to first unmarshall social as map[string]interface{} and determine which type should be used based on the fields. Or add a socialType field to the User struct.
Part of the JSON I'm trying to unmarshal has an array that can contain either strings or ints. How would I go about parsing this?
{
"id": "abc",
"values": [1,2,3]
},
{
"id": "def",
"values": ["elephant", "tomato", "arrow"]
},
{
//etc...
}
I tried the following:
type Thing struct {
ID string `json:"id"`
Values []string `json:"values,string,omitempty"`
}
Get the following error:
panic: json: cannot unmarshal array into Go struct field Thing.values of type string
You can set the type of the field Values to []interface{}, but you might have to use some type assertions.
Here is a link to a playground where I use the data you provided: https://play.golang.org/p/ahePgggr8o1
I will paste the code here anyways:
package main
import (
"fmt"
"encoding/json"
)
type Thing struct {
ID string `json:"id"`
Values []interface{} `json:"values,omitempty"`
}
func print(values []interface{}) {
for _, v := range values{
switch value := v.(type){
case string:
fmt.Println("String", value)
case int:
fmt.Println("Integer", value)
case float64:
fmt.Println("Floats", value)
default:
fmt.Printf("I don't know about type %T!\n", v)
}
}
}
func main() {
var t1, t2 Thing
data := []byte("{ \"id\": \"abc\", \"values\": [1,2,3]}")
data2 := []byte("{ \"id\": \"def\", \"values\": [\"elephant\", \"tomato\", \"arrow\"]}")
_ = json.Unmarshal(data, &t1)
_ = json.Unmarshal(data2, &t2)
print(t1.Values)
print(t2.Values)
}
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
}
I have JSON data which looks like this:
[
{
"globalTradeID": 64201000,
"tradeID": 549285,
"date": "2016-11-11 23:51:58",
"type": "buy",
"rate": "10.33999779",
"amount": "0.02176472",
"total": "0.22504715"
},
{
"globalTradeID": 64200631,
"tradeID": 549284,
"date": "2016-11-11 23:48:39",
"type": "buy",
"rate": "10.33999822",
"amount": "0.18211700",
"total": "1.88308945"
}...
]
I've tried to unmarshall this JSON by defining a type:
type TradeHistoryResponse []TradeHistoryInfo
type TradeHistoryInfo struct {
GlobalTradeID int64 `json:"globalTradeID"`
TradeID int64 `json:"tradeID"`
Date string `json:"date"`
Type string `json:"type"`
Rate float64 `json:"rate,string"`
Amount float64 `json:"amount,string"`
Total float64 `json:"total,string"`
}
And then pulling the JSON data as so:
//Read response
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("[Poloniex] (doRequest) Failed to parse response for query to %s! (%s)", reqURL, err.Error())
}
//Convert JSON to struct
var THR TradeHistoryResponse
err = json.Unmarshal(body, &THR)
if err != nil {
return nil, fmt.Errorf("[Poloniex] (doRequest) Failed to convert response into JSON for query to %s! (%s)", reqURL, err.Error())
}
I get the following error:
(json: cannot unmarshal object into Go value of type poloniex.TradeHistoryResponse)
My best guess why the Unmarshall doesn't work is because the array is key-less?
Would love to have some clarification on the matter on how I might best get this working.
I didn't think unmarhsalling an array in a JSON like you intended actually works but to the props of encoding/json it does. Example code:
package main
import (
"encoding/json"
"fmt"
"log"
)
var mjs string = "[" +
"{" +
`"globalTradeID": 64201000,
"tradeID": 549285,
"date": "2016-11-11 23:51:58",
"type": "buy",
"rate": "10.33999779",
"amount": "0.02176472",
"total": "0.22504715"
},
{
"globalTradeID": 64200631,
"tradeID": 549284,
"date": "2016-11-11 23:48:39",
"type": "buy",
"rate": "10.33999822",
"amount": "0.18211700",
"total": "1.88308945"
}
]`
type TradeHistoryResponse []TradeHistoryInfo
type TradeHistoryInfo struct {
GlobalTradeID int64 `json:"globalTradeID"`
TradeID int64 `json:"tradeID"`
Date string `json:"date"`
Type string `json:"type"`
Rate float64 `json:"rate,string"`
Amount float64 `json:"amount,string"`
Total float64 `json:"total,string"`
}
func main() {
//Convert JSON to struct
var THR TradeHistoryResponse
err := json.Unmarshal([]byte(mjs), &THR)
if err != nil {
log.Fatal(err)
}
for _, v := range THR {
fmt.Printf("%+v", v)
fmt.Printf("\n")
}
}
This prints out all the values as expected. So it doesn't have a problem converting the json values to float/int either (which would have been my second guess).
Are you sure you aren't modifying the Body in any way ? And that the example you give cover all edge cases ?
Could you add:
fmt.Println(string(body))
to the second error block and log the error it give here.
Also what version of go are you using ? I wouldn't exclude the possibility of encoding/json to have changed between versions. In the end, if this is indeed the case the easy fix would be taking your response as a string, removing all whitespaces and splitting at "}'{" as in:
fmtBody := strings.Replace(string(body), "\n", "", -1)
fmtBody = strings.Replace(string(fmtBody), "\w", "", -1)
fmtBody = strings.Replace(string(fmtBody), "[", "", -1)
fmtBody = strings.Replace(string(fmtBody), "]", "", -1)
var goArrOfYourJsonsObj []String = strings.Split(fmtBody, "},{")
for k, _ := range goArrOfYourJsonsObj {
goArrOfYourJsonsObj[k] += "}"
}
And now you have your JSON objects neatly separated into a go array of types String which, can be used in Unmarshall as []byte(val).
TradeHistoryResponse is a type no a variable, you have to create a var of type TradeHistoryResponse, like this:
var th TradeHistoryResponse
//Convert JSON to struct
err = json.Unmarshal(body, &th)
// ...
fmt.Printf("%+v\n", th)
Following the Go by Example: JSON tutorial, I see how to work with a basic JSON string:
package main
import (
"encoding/json"
"fmt"
)
type Response struct {
Page int `json:"page"`
Fruits []string `json:"fruits"`
}
func main() {
str := `{"page": 1, "fruits": ["apple", "peach"]}`
res := Response{}
json.Unmarshal([]byte(str), &res)
fmt.Println(res.Page)
fmt.Println(res.Fruits)
}
// the output looks good here:
// 1
// [apple peach]
I would like to add some complexity to the str data object that I am decoding.
Namely, I would like to add a key with a slice of maps as its value:
"activities": [{"name": "running"}, {"name", "swimming"}]
My script now looks like below example, however, for the life of me, I can not figure out what the correct syntax is in the Response struct in order to get at the values in activities. I know that this syntax isn't correct: Activities []string ... but can not hack my way towards a solution that captures the data I want to display.
package main
import (
"encoding/json"
"fmt"
)
type Response struct {
Page int `json:"page"`
Fruits []string `json:"fruits"`
Activities []string `json:"activities"`
}
func main() {
str := `{"page": 1, "fruits": ["apple", "peach"], "activities": [{"name": "running"}, {"name", "swimming"}]}`
res := Response{}
json.Unmarshal([]byte(str), &res)
fmt.Println(res.Page)
fmt.Println(res.Fruits)
fmt.Println(res.Activities)
}
// the script basically craps out here and returns:
// 0
// []
// []
Thanks for any help!
Use []map[string]string:
type Response struct {
Page int `json:"page"`
Fruits []string `json:"fruits"`
Activities []map[string]string `json:"activities"`
}
playground example
Always check and handle errors.
The example JSON has a syntax error which is corrected in the playground example.
I know this is an old one, but i've recently written utility for generating exact go type from json input and in similar case you could give it a spin: https://github.com/m-zajac/json2go
For this particular example it generates from json:
{"page": 1, "fruits": ["apple", "peach"], "activities": [{"name": "running"}, {"name": "swimming"}]}
go type:
type Object struct {
Activities []struct {
Name string `json:"name"`
} `json:"activities"`
Fruits []string `json:"fruits"`
Page int `json:"page"`
}