The JSON that I am at tempting to parse is very basic and looks like this.
{"id": 3, "title":"Test"}
The following is the code that I am attempting to use for creating and parsing the JSON.
package main
import (
"fmt"
"encoding/json"
)
type Config struct{
id int
title string
}
func main() {
var jsonStr = []byte(`{"id": 3, "title":"Test"}`)
var conf Config
err := json.Unmarshal(jsonStr, &conf)
if err!=nil{
fmt.Print("Error:",err)
}
fmt.Println(conf)
fmt.Println(string(jsonStr))
}
Been looking over a lot of different code examples and can't see what I'm doing wrong. When I attempt to run this, this is what I get as a return.
{0 }
{"id": 3, "title":"Test"}
I have verified that the JSON is valid, but continue to get an empty return when attempting to use json.Unmarshal. Any ideas on what I am missing so that I can get this JSON parsed?
EDIT: Looks like I can get this to work if I capitalize the titles (Id, Title). Unfortunately the return I am testing for is a return from an API which returns everything in lowercase. I need to be able to parse this JSON with lowercase titles as listed above.
Your Config struct's fields need to be exported (upper-case), but the keys in your JSON object may remain lower-case.
See here: http://play.golang.org/p/0A5tkCkSO5
Please consult the JSON package documentation, it is worth the read. While Amit already addressed the export issue I will address the following:
EDIT: Looks like I can get this to work if I capitalize the titles (Id, Title). Unfortunately the return I am testing for is a return from an API which returns everything in lowercase. I need to be able to parse this JSON with lowercase titles as listed above.
As you might imagine, the authors of encoding/json have thought of that, so again I encourage you to consult the documentation next time. The solution is this (Example on playground):
type Config struct {
Id int `json:"id"`
Title string `json:"title"`
}
Related
I have a csv response that comes from an endpoint that I don't control and I'm failing to parse its response because it has quotes. It looks something like this:
name,id,quantity,"status (active, expired)"
John,14,4,active
Bob,12,7,expired
to parse this response I have created the following struct:
type UserInfo struct {
Name string `csv:"name"`
ID string `csv:"id"`
Quantity string `csv:"quantity"`
Status string `csv:"status (active, expired)"`
}
I have tried using
Status string `csv:""status (active, expired)""`
Status string `csv:'"status (active, expired)"'`
but none seem to be helpful, I just can't access the field Status when I use gocsv.Unmarshal.
var actualResult []UserInfo
err = gocsv.Unmarshal(in, &actualResult)
for _, elem := range actualResult {
fmt.Println(elem.Status)
}
And I get nothing as as response.
https://go.dev/play/p/lje1zNO9w6E here's an example
You don't need third party package like gocsv (unless you have specific usecase) when it can be done easily with Go's builtin encoding/csv.
You just have to ignore first line/record which is csv header in your endpoint's response.
csvReader := csv.NewReader(strings.NewReader(csvString))
records, err := csvReader.ReadAll()
if err != nil {
panic(err)
}
var users []UserInfo
// Iterate over all records excluding first one i.e., header
for _, record := range records[1:] {
users = append(users, UserInfo{Name: record[0], ID: record[1], Quantity: record[2], Status: record[3]})
}
fmt.Printf("%v", users)
// Output: [{ John 14 4 active } { Bob 12 7 expired}]
Here is working example on Go Playground based on your use case and sample string.
I simply don't think gocarina/gocsv can parse a header with a quoted comma. I don't see it spelled out anywhere in the documentation that it cannot, but I did some digging and there are clear examples of commas being used in the "CSV annotations", and it looks like the author only conceived of commas in the annotations being used for the purposes of the package/API, and not as part of the column name.
If we look at sample_structs_test.go from the package, we can see commas being used in some of the following ways:
in metadata directives, like "omitempty":
type Sample struct {
Foo string `csv:"foo"`
Bar int `csv:"BAR"`
Baz string `csv:"Baz"`
...
Omit *string `csv:"Omit,omitempty"`
}
for declaring that a field in the struct can be populated from multiple, different headers:
type MultiTagSample struct {
Foo string `csv:"Baz,foo"`
Bar int `csv:"BAR"`
}
You can see this in action, here.
FWIW, the official encoding/json package has the same limitation, and they note it (emphasis added):
The encoding of each struct field can be customized by the format string stored under the "json" key in the struct field's tag. The format string gives the name of the field, possibly followed by a comma-separated list of options. The name may be empty in order to specify options without overriding the default field name.
and
The key name will be used if it's a non-empty string consisting of only Unicode letters, digits, and ASCII punctuation except quotation marks, backslash, and comma.
So, you may not be able to get what you expect/want: sorry, this may just be a limitation of having the ability to annotate your structs. If you want, you could file a bug with gocarina/gocsv.
In the meantime, you can just modify the header as it's coming in. This is example is pretty hacky, but it works: it just replaces "status (active, expired)" with "status (active expired)" and uses the comma-less version to annotate the struct.
endpointReader := strings.NewReader(sCSV)
// Fix header
var bTmp bytes.Buffer
fixer := bufio.NewReader(endpointReader)
header, _ := fixer.ReadString('\n')
header = strings.Replace(header, "\"status (active, expired)\"", "status (active expired)", -1)
bTmp.Write([]byte(header))
// Read rest of CSV
bTmp.ReadFrom(fixer)
// Turn back into a reader
reader := bytes.NewReader(bTmp.Bytes())
var actualResult []UserInfo
...
I can run that and now get:
active
expired
This question already has answers here:
Lowercase JSON key names with JSON Marshal in Go
(4 answers)
Closed 5 years ago.
What I am trying to do
I am parsing a JSON HTTP response based on this answer to a similar question. My code is able to parse the JSON without any error but is unable to read the values and store them in the provided variable.
This has been puzzling me for the last 2 hours and it might be due to a trivial reason that I am overlooking here.
CODE
type ImporterResponse struct {
results []packagemeta `json:"results"`
}
type packagemeta struct {
path string `json:"path"`
synopsis string `json:"synopsis,omitempty"`
count int `json:"import_count,omitempty`
}
func main() {
res := []byte(`{"results":[{"path":"4d63.com/randstr/lib/randstr","import_count":0,"synopsis":"Package randstr generates random strings (e.g."},{"path":"bitbucket.org/pcas/tool/mathutil","import_count":0}]}`)
fmt.Println("Decoding the JSON")
r := bytes.NewReader(res)
decoder := json.NewDecoder(r)
packageimporters := &ImporterResponse{}
err := decoder.Decode(packageimporters)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Packageimporters: %+v", packageimporters)
fmt.Println(len(packageimporters.results))
}
Link to Playground: https://play.golang.org/p/NzLl7Ujo2IJ
What I want:
How to fix this?
Why is no error message raised if JSON is not parsed properly?
P.S: I understand that this question has been asked before and there are possible solutions available but none of them work for me. Hence, I have made this post.
You need to make your struct fields exported, otherwise the json package cannot access them.
Please read JSON and go for more details, specifically this paragraph:
The json package only accesses the exported fields of struct types
(those that begin with an uppercase letter). Therefore only the the
exported fields of a struct will be present in the JSON output.
And this one for more details:
How does Unmarshal identify the fields in which to store the decoded
data? For a given JSON key "Foo", Unmarshal will look through the
destination struct's fields to find (in order of preference):
An exported field with a tag of "Foo" (see the Go spec for more on
struct tags),
An exported field named "Foo", or
An exported field
named "FOO" or "FoO" or some other case-insensitive match of "Foo".
So your struct should really be:
type Packagemeta struct {
Path string `json:"path"`
Synopsis string `json:"synopsis,omitempty"`
Count int `json:"import_count,omitempty`
}
I'm trying to decode a json response with a struct type. An instance of the object i'm trying to decode looks as follows:
{
"title": "Some Title",
"views": 344,
"profiles": {
"customField": "somevalue",
"customField2:" :somevalue"
}
}
The golang struct is the following:
type Topic struct {
Title string `json:"title"`
Views string `json:"views"`
Profiles string `json:"profiles"`
}
As you can see, the "Profiles" attribute is a string, since the profiles object is unknown, as the fields inside it can be dinamically defined.
I'm trying to decode this with:
json.NewDecoder(response.Body).Decode(result)
Where result is of type Topic, but isn't working. What type should the "Profiles" attribute be in order to correctly decoding the answer?
Thanks!
Reading the comment it's clear that profiles value could be of any type, for this reason I suggest you to declare the Profiles type as a map[string]interface{}.
Topic becomes:
type Topic struct {
Title string `json:"title"`
Views int32 `json:"views"`
Profiles map[string]interface{} `json:"profiles"`
}
Check out https://github.com/mitchellh/mapstructure
The readme has an answer you probably look for.
problem is if you have configuration or an encoding that changes slightly depending on specific fields.
Perhaps we can't populate a specific structure without first reading the "type" field from the JSON. We could always do two passes over the decoding of the JSON (reading the "type" first, and the rest later). However, it is much simpler to just decode this into a map[string]interface{} structure, read the "type" key, then use something like this library to decode it into the proper structure.
If profiles can vary you should take json.RawMessage here. Internally it is a []byte which late can be unmarshalled into other types, e.g. depending on values of the outer document.
See https://golang.org/pkg/encoding/json/#RawMessage and the examples.
The Profiles should be a struct and initiated along with the container, in this case I assume it's a Status, such as FB status or Tweet, I've made an example here https://play.golang.org/p/tG90idakLP
Remember to instantiate the new profiles inside a newly created status, before you start unmarshalling.
I have JSON from an API that I want to save to MongoDB using the mgo package. My JSON looks like this:
{
"something": "value"
"collection": [
{ "obj1": "value" }
// ... (Variable number of objects here)
]
}
To save this data I've created a new type in my Go application that looks like this:
type MyData struct {
Something string
Collection []string // This actually contains more than strings but I'll be happy if I can just get strings saved
}
mongoSess, err := mgo.Dial("localhost:27017")
if err != nil {
panic(err)
}
defer mongoSess.Close()
c := mongoSess.DB("mydatabase").C("mycollection")
insertErr := c.Insert(&MyData{something, collection})
This code works but the problem is that it isn't saving anything in my collection field which should be an array of JSON objects. Nothing at all. I get the keys in my database and they are the right type but they have no data. Here's what the Mongo output is:
{ "_id" : ObjectId("5520c535a236d8a9a215d096"), "something" : "value", "collection" : [ ] }
Can anyone spot what it is I'm doing wrong? I'm obviously new to Go and having trouble with types.
Solution
The answers here really helped me a lot. I'm at fault for not properly explaining things as the answers sent me on the right track but didn't solve the issue directly. Here's what the actual solution is.
package main
import (
"encoding/json"
"github.com/bitly/go-simplejson"
"gopkg.in/mgo.v2"
//"gopkg.in/mgo.v2/bson"
// Other packages are used as well
)
type MyData struct {
Something int
Collection []interface{}
}
func main() {
// I'm using SimpleJson for parsing JSON
collection, cerr := jsonFromExternalApi.Get("collection").Array()
if cerr != nil {
logger.Debug.Fatalln(cerr)
}
// Save response to Mongo (leaving out all the connection code)
c := mongoSess.DB("mydb").C("mycollection")
insertErr := c.Insert(&Producer{npn, licenses })
}
The issue was that SimpleJSON was returning the array of objects from my API call as a []interface{}. I was not aware I could simply declare part of a struct to be an interface so instead of just correcting what Go's compiler was telling me was wrong I was making it way harder than it should have been.
Coming from loosely typed scripting languages, stuff like this really trips me up and sometimes its hard to see the benefit but hopefully this helps someone out one day.
Looks you have the wrong data structure.
insertErr := c.Insert(&MyData{something, collection})
something => string and collection => slice
You code should be like this:
insertErr := c.Insert(&MyData{"something", []string{"obj1", "value"}})
Here is the working code.
[{Something:something Collection:[obj1 value]} {Something:something Collection:[obj1 value]} {Something:something Collection:[obj1 value]} {Something:something Collection:[obj1 value]}]
For further reference, mgo has great documentation and you can find sample code and details about running mongodb queries here.
Here you are not defining proper type to Collection in your MyData struct.
Collection []string //This is what you are defining.
But from Json you are not getting string array,you are getting map[string]interface{}
This is the reason you are not filling Mydata struct properly
Correct MyData struct will be
type MyData struct {
Something string
Collection map[string]string // This actually contains more than strings but I'll be happy if I can just get strings saved
}
I apologise in advance for a very long question. Hopefully you'll bear with me.
I'm working with the goweb library, and experimenting with the example web app.
I've been trying to modify the RESTful example code, which defines a Thing as:
type Thing struct {
Id string
Text string
}
A Thing is created by sending an HTTP Post request, with an appropriate JSON body, to http://localhost:9090/things. This is handled in the example code in the Create function, specifically the lines:
dataMap := data.(map[string]interface{})
thing := new(Thing)
thing.Id = dataMap["Id"].(string)
thing.Text = dataMap["Text"].(string)
This is all well and good, and I can run the example server (which listens on http://localhost:9090/) and the server operates as expected.
For example:
curl -X POST -H "Content-Type: application/json" -d '{"Id":"TestId","Text":"TestText"}' http://localhost:9090/things
returns with no error, and then I GET that Thing with
curl http://localhost:9090/things/TestId
and it returns
{"d":{"Id":"TestId","Text":"TestText"},"s":200}
So far, so good.
Now, I would like to modify the Thing type, and add a custom ThingText type, like so:
type ThingText struct {
Title string
Body string
}
type Thing struct {
Id string
Text ThingText
}
This in itself isn't an issue, and I can modify the Create function like so:
thing := new(Thing)
thing.Id = dataMap["Id"].(string)
thing.Text.Title = dataMap["Title"].(string)
thing.Text.Body = dataMap["Body"].(string)
and the run the previous curl POST request with the JSON set to:
{"Id":"TestId","Title":"TestTitle","Title":"TestBody"}
and it returns with no error.
Once again I can GET the Thing URL and it returns:
{"d":{"Id":"TestId","Text":{"Title":"TestTitle","Body":"TestBody"}},"s":200}
Again, so far, so good.
Now, my question:
how do I modify the Create function to allow me to POST complex JSON to it?
For example, that last returned JSON string above includes {"Id":"TestId","Text":{"Title":"TestTitle","Body":"TestBody"}}. I'd like to be able to POST that exact JSON to the endpoint and have the Thing created.
I've followed the code back, and it seems that the data variable is of type Context.RequestData() from https://github.com/stretchr/goweb/context, and the internal Map seems to be of type Object.Map from https://github.com/stretchr/stew/, described as "a map[string]interface{} with additional helpful functionality." in particular, I noticed "Supports dot syntax to set deep values."
I can't work out how I can set up the thing.Text.Title = dataMap... statement so that the correct JSON field is parsed into it. I can't seem to use anything other than string types in the dataMap, and if I try that JSON it gives an error similar to:
http: panic serving 127.0.0.1:59113: interface conversion: interface is nil, not string
Once again, sorry for the ridiculously long question. I really appreciate you reading, and any help you may have to offer. Thanks!
As the JSON package documentation and JSON and Go introduction describe, JSON data can be parsed either generically through interface{} / string maps or unmarshalling directly to the struct types.
The example code you linked to and you based your changes on seems to use the generic string-map approach, dataMap := data.(map[string]interface{}).
As your desired JSON data is an object in an object, it’s simply a map within a map.
So you should be able to
dataMap := data.(map[string]interface{})
subthingMap := dataMap["Text"].(map[string]interface{})
thing.Text.Title = subthingMap["Title"].(string)
thing.Text.Body = subthingMap["Body"].(string)
I’m not sure why that code uses casts and generic types over type-safe unmarshalling directly from JSON to struct types (abstraction I guess). Using the json packages unmarshalling to struct types would go something like
type ThingText struct {
Title string
Body string
}
type Thing struct {
Id string
Text ThingText
}
…
decoder := json.NewDecoder(body)
var thingobj Thing
for {
if err := decoder.Decode(&thingobj); err == io.EOF {
break
} else if err != nil {
log.Fatal(err)
}
fmt.Println(thingobj)
}
where body is a io.Reader - in simple/most cases from http.Response.Body.