To unmarshal JSON, do your structs have to match the JSON exactly?

6,200 views
Skip to first unread message

m

unread,
Nov 25, 2010, 10:29:09 AM11/25/10
to golang-nuts
I find the idea of unmarshaling JSON to structs to be wonderful.
However in practice, I've never been successful. I think it's because
unmarshaling assumes a simple JSON structure where in reality few real
world JSON objects are simple. Here's an example:

http://query.yahooapis.com/v1/public/yql?q=select%20*%20from%20yahoo.finance.industry%20where%20id%3D%22112%22&format=json&env=store%3A%2F%2Fdatatables.org%2Falltableswithkeys&callback=

So, before I waste my time, to I have to build structs that are going
to match the JSON exactly? I have to build a top level struct, then
sub structs, even though what I want is at the bottom of the pile so
to speak?

Peter Bourgon

unread,
Nov 25, 2010, 10:44:09 AM11/25/10
to golang-nuts

You do have to represent the structure to the depth of the information
you want to extract, but you don't have to enumerate all the fields,
only the ones that contain relevant info. For example, if you wanted
only a list of company symbols, you have to have data structures that
hold the query, results, industry, company (list) and symbol, but you
can ignore created, lang, industry id/name, etc. The JSON unmarshaler
will skip stuff it doesn't find a place for.

I don't see anything wrong with this; it seems totally appropriate for
"real world JSON objects". Really I have no idea how you could even
get around this kind of requirement and still call it JSON parsing. If
you only want very particular pieces of data at the very bottom of a
deep hierarchy, consider a regex.

Brian Ketelsen

unread,
Nov 25, 2010, 10:47:14 AM11/25/10
to peter....@gmail.com, golang-nuts
Why not build a struct generator that creates the struct definition(s) based on an example?

roger peppe

unread,
Nov 25, 2010, 11:20:03 AM11/25/10
to Brian Ketelsen, peter....@gmail.com, golang-nuts
perhaps a useful addition to json would be something that
could unmarshal from a map[string]interface{} (as returned
when unmarshalling to *interface{}) to a struct.

then you could dive down into the json structure
and still get the benefits of unmarshalling to structs.

Brian Slesinsky

unread,
Nov 25, 2010, 4:08:46 PM11/25/10
to golang-nuts

On Nov 25, 7:44 am, Peter Bourgon <peterbour...@gmail.com> wrote:

> I don't see anything wrong with this; it seems totally appropriate for
> "real world JSON objects". Really I have no idea how you could even
> get around this kind of requirement and still call it JSON parsing. If
> you only want very particular pieces of data at the very bottom of a
> deep hierarchy, consider a regex.

Hmm, it might be more useful if the parser could take the JSON
equivalent of an XPath query to specify the root nodes of interest.

- Brian

Stephen J Day

unread,
Nov 25, 2010, 5:41:51 PM11/25/10
to golang-nuts
Remember, that the interface value supplied to json.Unmarshal can be
an underlying map as well:
package main

import (
"fmt"
"json"
)

func main() {
var m map[string]interface{}
json.Unmarshal([]byte("{\"foo\": 1}"), &m)
fmt.Println(m) // prints: map[foo:1]
}

Steve

On Nov 25, 7:44 am, Peter Bourgon <peterbour...@gmail.com> wrote:
> On Thu, Nov 25, 2010 at 4:29 PM, m <phillip...@gmail.com> wrote:
> > I find the idea of unmarshaling JSON to structs to be wonderful.
> > However in practice, I've never been successful.  I think it's because
> > unmarshaling assumes a simple JSON structure where in reality few real
> > world JSON objects are simple.  Here's an example:
>
> >http://query.yahooapis.com/v1/public/yql?q=select%20*%20from%20yahoo....

m

unread,
Nov 26, 2010, 5:40:54 PM11/26/10
to golang-nuts
Just flat out never works for me. I wish there were more examples of
complicated JSON unmarshaling. Using the example I showed in this
thread, I've spent much time and can't get it to work. Now I get the
error: json: cannot unmarshal object key "query" into unexported field
query of type industries.Top

My code:


type Industry struct {
Id string
Name string
Company Company
}

type Top struct {
query Query
}

type Query struct {
count string
created string
lang string
results Results
}

type Results struct {
industry Industry
}

type Company struct {
Symbol string
Name string
}

func main() {
url := "http://query.yahooapis.com/v1/public/yql" +
"?q=select%20*%20from%20yahoo.finance.in" +
"dustry%20where%20id%3D%22"+ ind.Id +
"%22&format=json&env=store%3A%2F%2Fdatat" +
"ables.org%2Falltableswithkeys&callback="
resp, _, _ := http.Get(url)

var buf []byte
buf, _ = ioutil.ReadAll(resp.Body)

var t Top
_ = json.Unmarshal(buf, &t)

println("Worked!")
}

m

unread,
Nov 26, 2010, 5:42:27 PM11/26/10
to golang-nuts
Industry struct contains []Company not Company. Typo. The error is
real.

Steven

unread,
Nov 26, 2010, 6:07:17 PM11/26/10
to m, golang-nuts

JSON can't unmarshal into private fields because they are private. It
doesn't have any special permissions. In order to get it to work, you
have to export the fields you need to unmarshal into.

m

unread,
Nov 26, 2010, 6:11:09 PM11/26/10
to golang-nuts
Thanks, I didn't understand what "export fields" meant.

On Nov 26, 6:07 pm, Steven <steven...@gmail.com> wrote:

Steven

unread,
Nov 26, 2010, 6:19:17 PM11/26/10
to m, golang-nuts
On Fri, Nov 26, 2010 at 6:11 PM, m <phill...@gmail.com> wrote:
Thanks, I didn't understand what "export fields" meant.

Do you mean before or now? Just in case, "export" means "make public" so that they are accessible from other packages. This is achieved by capitalizing their names.

m

unread,
Nov 27, 2010, 10:23:54 AM11/27/10
to golang-nuts
This returns an error of: json: cannot unmarshal object into Go value
of type []main.iIndustry

I have no idea why! Anyone willing to give it a try?




package main

import (
"http"
"io/ioutil"
"json"
"os"
)

type iIndustry struct {
Id string
Name string
}

type Sector struct {
Name string
Industry []iIndustry
}

type sResults struct {
Sector []Sector
}

type sQuery struct {
Count string
Created string
Lang string
Results sResults
}

type sTop struct {
Query sQuery
}

func GetIndustries() ([]iIndustry, os.Error) {
"?q=select%20*%20from%20yahoo.finance.se" +
"ctors&format=json&env=http%3A%2F%2Fdata" +
"tables.org%2Falltables.env&callback="

resp, _, err := http.Get(url)
if err != nil { return nil, os.NewError("Could not get industries
from Yahoo servers.") }

var buf []byte
buf, err = ioutil.ReadAll(resp.Body)
if err != nil { return nil, os.NewError("Could not parse http
response for Industries.") }

var t sTop
err = json.Unmarshal(buf, &t)
if err != nil { return nil, err }

println("Worked.")

println(len(t.Query.Results.Sector))

//println(t.Query.Results.Sector[0].Name)

os.Exit(0)

return t.Query.Results.Sector[0].Industry, nil
}

chris dollin

unread,
Nov 27, 2010, 10:43:46 AM11/27/10
to m, golang-nuts
On 27 November 2010 15:23, m <phill...@gmail.com> wrote:
> This returns an error of: json: cannot unmarshal object into Go value
> of type []main.iIndustry
>
> I have no idea why!  Anyone willing to give it a try?

Mere guesswork: []main.industry is a slice, so you should
be trying to unmarshall an array into it, not any old object.

(Should the message be more explicit about the entity it's
trying to unmarshall?)

Chris

--
Chris "allusive" Dollin

roger peppe

unread,
Nov 27, 2010, 11:09:23 AM11/27/10
to m, golang-nuts
the reason is that this JSON data is inconsistent in its encoding.
for most sectors, "industry" holds a JSON array
holding the various industries, but Conglomerates,
with only one industry has a singleton value instead of an
array holding a single value.

if it's changed to be an array, the unmarshalling works.

if this is common practice in JSON encodings, it would
be possible to change json.Unmarshal so that it is
liberal enough to unmarshal a singleton object into
an array.

as it is currently, your only option is to change the
type of Sector.Industry to interface{} and do the extraction
manually.

i still think that my earlier suggestion would be useful,
particularly when the top levels of the JSON data
are not very rigidly defined.


On 27 November 2010 15:23, m <phill...@gmail.com> wrote:

roger peppe

unread,
Nov 27, 2010, 11:10:55 AM11/27/10
to chris dollin, m, golang-nuts
On 27 November 2010 15:43, chris dollin <ehog....@googlemail.com> wrote:
> (Should the message be more explicit about the entity it's
> trying to unmarshall?)

yes!

Andrew Gerrand

unread,
Nov 28, 2010, 8:24:38 PM11/28/10
to roger peppe, m, golang-nuts
There are a few ways the json package could be more useful.

One idea I had: register a type with the json package, and then when
unmarhsalling into a value of type interface{}, json will look at its
set of registered types for one that matches the provided fields and
initialise a value of that type, instead of a map[string]interface{}.

For example:

type Foo struct {
Bar int
Baz string
}

const s = `{"Bar": 12, "Baz": "a string"}`

func main() {
json.Register(Foo{})
var v interface{}
json.Unmarshal([]byte(s), &v)
_ := v.(Foo) // this should work
}

This would make much of my code simpler. Does that seem useful to anyone else?

Andrew

jimmy frasche

unread,
Nov 28, 2010, 8:33:42 PM11/28/10
to Andrew Gerrand, roger peppe, m, golang-nuts
That seems pretty useful. Especially if you were getting a few
different kinds of reponses. You could just have one type for each of
them and do a typeswitch on v and know what kind of data you recieved
and have it nicely packaged and loaded up with helper methods. I
haven't played around with the json package yet but I have had run ins
with json data before where something like that would have saved a lot
of time and code.

Andrew Gerrand

unread,
Nov 28, 2010, 9:37:44 PM11/28/10
to jimmy frasche, roger peppe, m, golang-nuts
On 29 November 2010 10:33, jimmy frasche <soapbo...@gmail.com> wrote:
> That seems pretty useful. Especially if you were getting a few
> different kinds of reponses. You could just have one type for each of
> them and do a typeswitch on v and know what kind of data you recieved
> and have it nicely packaged and loaded up with helper methods. I
> haven't played around with the json package yet but I have had run ins
> with json data before where something like that would have saved a lot
> of time and code.

Yes, exactly.

My workaround thus far has been to create a shim object:

type Shim struct {
Foo *Foo
Bar *Bar
}

And then when generating the JSON, I put the actual data one level down:
{"Foo": { foo object contents }}
or
{"Bar": { bar object ... }}

Then it's just an if shim.Foo == nil test, but it would be much more
elegant to use a type switch.

Andrew

Reply all
Reply to author
Forward
0 new messages