Reputation: 384
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
Upvotes: 0
Views: 1043
Reputation: 38223
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
Upvotes: 4
Reputation: 6584
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/
Upvotes: 0