How to unmarshal json with unknown field and key - json

From front-end I got this example of json:
{
"properties":{"unknown key": "unknown value","unknown key2": "unknown value 2"}
}
I start to parse it with map[string]interface{} but it doesn't work. Also I don't know how much this fields I can got. It can be 10 or 1.
Code:
type test struct {
p map[string]string `json:"properties"`
}
func main() {
var t test
body := `
{
"properties":{"unknown key": "unknown value","unknown key2": "unknown value 2"}
}
`
json.Unmarshal([]byte(body), &t)
fmt.Println(t.p)
}
This code always return an empty map.

You should export the field of the struct that should be Unmarshalled, like:
type test struct {
P map[string]string `json:"properties"`
}
See https://go.dev/play/p/Fp91DTlrZpw

Related

How to set optional json in nest struct

I tried to set an optional json config in nest struct, when i need this json it will appear, otherwise it will not exist.
type Test struct {
Data NestTest `json:"data"`
}
type NestTest struct {
NestData1 string `json:"data1"`
NestData2 string `json:"data2,omitempty"`
}
test := Test{
Data: NestTest{
NestData1: "something",
},
}
b, err := json.Marshal(test)
fmt.Sprintf("the test struct json string is: %s", string(b))
output:
{"data":{"data1":"something","data2":""}}
expect:
{"data":{"data1":"something"}}
All fields are optional when unmarshalling (you won't get an error if a struct field doesn't have an associated value in the JSON). When marshaling, you can use omitempty to not output a field if it contains its type's zero value:
https://pkg.go.dev/encoding/json#Marshal
var Test struct {
Data string `json:"data,omitempty" validate:"option"`
}

How to use go's json tag to unmarshal both numeric and string properties into a string value

I have the following Go struct and JSON data:
type Entry struct {
Timestamp string `json:"timestamp"`
Value string `json:"value"`
}
{
"timestamp": "2020-01-01T00:00:00.000Z",
"value": "a string" // but sometimes it's a number
}
Most of the time the value of the JSON data is of type string however sometimes it's of type number.
If it's a number the json.Unmarshal method returns an error like this:
json: cannot unmarshal number into Go struct field Entry.valueof type string
Is there an idiomatic and straightforward way to overcome such an issue in Go or I should implement a custom unmarshalling method for this case?
You could use interface{} for Entry.Value, and the encoding/json package will choose the proper type at runtime: string for a JSON string, and float64 for a JSON number:
type Entry struct {
Timestamp string `json:"timestamp"`
Value interface{} `json:"value"`
}
for _, s := range []string{
`{ "timestamp": "2020-01-01T00:00:00.000Z", "value": "a string" }`,
`{ "timestamp": "2020-01-01T00:00:00.000Z", "value": 12 }`,
} {
var e Entry
if err := json.Unmarshal([]byte(s), &e); err != nil {
panic(err)
}
fmt.Printf("%#v %T\n", e, e.Value)
}
This outputs (try it on the Go Playground):
main.Entry{Timestamp:"2020-01-01T00:00:00.000Z", Value:"a string"} string
main.Entry{Timestamp:"2020-01-01T00:00:00.000Z", Value:12} float64
Yes, then you will need a type assertion to get a typed value out from Entry.Value.
Another option is to use json.Number which may accommodate both strings and JSON numbers:
type Entry struct {
Timestamp string `json:"timestamp"`
Value json.Number `json:"value"`
}
Using this, the above example it outputs (this it on the Go Playground):
main.Entry{Timestamp:"2020-01-01T00:00:00.000Z", Value:"a string"}
main.Entry{Timestamp:"2020-01-01T00:00:00.000Z", Value:"12"}
When using json.Number, you may access its value using Number.String(), or Number.Int64() or Number.Float64() which return an error if its value is not a number.
One thing to note here: if the JSON input is a string that contains a valid number, e.g. "12", then Number.Int64() will not report error but parse it and return 12. This is a difference compared to using intefface{} (where Entry.Value will remain string).
Providing an alternative to icza's answer using a custom unmarshaller.
type Entry struct {
Timestamp string `json:"timestamp"`
Value EntryValue `json:"value"`
}
type EntryValue struct {
string
}
func (e *EntryValue) UnmarshalJSON(data []byte) error {
// Simplified check
e.string = string(bytes.Trim(data, `"`))
return nil
}
func main() {
for _, s := range []string{
`{ "timestamp": "2020-01-01T00:00:00.000Z", "value": "a string" }`,
`{ "timestamp": "2020-01-01T00:00:00.000Z", "value": 12 }`,
} {
var e Entry
if err := json.Unmarshal([]byte(s), &e); err != nil {
panic(err)
}
fmt.Printf("%#v\n", e)
}
}

cannot unmarshal array into Go struct when decoding array

I'm trying to properly decode JSON String to an object.
I defined the following structs:
type AjaxModelsList struct {
Id float64 `json:"Id"`
Name string `json:"Name"`
CarId float64 `json:"CarId"`
EngName string `json:"EngName"`
}
type AjaxModelsData struct {
ModelList []AjaxModelsList `json:"ModelList"`
}
type AjaxModels struct {
Status bool `json:"status"`
Data map[string]AjaxModelsData `json:"data"`
}
the defined object is
{
"status": true,
"data": {
"ModelList": [{
"Id": 1,
"Name": "foo",
"CarId": 1,
"EngName": "bar"
}]
}
}
and I unmarshall using the following code:
c :=AjaxModels{}
if err := json.Unmarshal(body_byte,&c); err != nil {
log.Fatalf("an error occured: %v",err)
}
and I get the following output:
an error occured: json: cannot unmarshal array into Go struct field AjaxModels.data of type main.AjaxModelsData
since I used []AjaxModelsList it's a slice so I shouldn't be getting this error. I probably missed something, but what ?
In the json the data structure is object[key]array, while in Go Data is map[key]struct.slice. Change Data to be map[key]slice instead.
E.g.
type AjaxModels struct {
Status bool `json:"status"`
Data map[string][]AjaxModelsList `json:"data"`
}
https://play.golang.org/p/Sh_vKVL-D--

Unmarshal JSON that could be a string or an object

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
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
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/

Unmarshal JSON in go with different types in a list

I have trouble unmarschaling a JSON contruct:
{
"id": 10,
"result": [
{
"bundled": true,
"type": "RM-J1100"
},
[
{
"name": "PowerOff",
"value": "AAAAAQAAAAEAAAAvAw=="
},
{
"name": "Input",
"value": "AAAAAQAAAAEAAAAlAw=="
}
]
]
}
I actually need the second slice item from the result.
My current attempt is
type Codes struct {
Id int32 `json:"id"`
Result []interface{} `json:"result"`
}
type ResultList struct {
Info InfoMap
Codes []Code
}
type InfoMap struct {
Bundled bool `json:"bundled"`
Type string `json:"type"`
}
type Code struct {
Name string `json:"name"`
Value string `json:"value"`
}
the output is like:
{10 {{false } []}}
but I also tried to use this:
type Codes struct {
Id int32 `json:"id"`
Result []interface{} `json:"result"`
}
the output is okay:
{10 [map[type:RM-J1100 bundled:true] [map[name:PowerOff value:AAAAAQAAAAEAAAAvAw==] map[name:Input value:AAAAAQAAAAEAAAAlAw==]]]}
I can also reference the Result[1] index:
[map[name:PowerOff value:AAAAAQAAAAEAAAAvAw==] map[name:Input value:AAAAAQAAAAEAAAAlAw==]]
But I fail to convert the interface type to any other Type which would match. Can anybody tell me how to do interface conversion. And what approach would be the "best".
One option would be to unmarshal the top level thing into a slice of json.RawMessage initially.
Then loop through the members, and look at the first character of each one. If it is an object, unmarshal that into your InfoMap header struct, and if it is an array, unmarshal it into a slice of the Code struct.
Or if it is predictable enough, just unmarshal the first member to the one struct and the second to a slice.
I made a playground example of this approach.
type Response struct {
ID int `json:"id"`
RawResult []json.RawMessage `json:"result"`
Header *Header `json:"-"`
Values []*Value `json:"-"`
}
type Header struct {
Bundled bool `json:"bundled"`
Type string `json:"type"`
}
type Value struct {
Name string `json:"name"`
Value string `json:"value"`
}
func main() {
//error checks ommitted
resp := &Response{}
json.Unmarshal(rawJ, resp)
resp.Header = &Header{}
json.Unmarshal(resp.RawResult[0], resp.Header)
resp.Values = []*Value{}
json.Unmarshal(resp.RawResult[1], &resp.Values)
}
(I will not point out how horrific it is this JSON struct, but as always: sXXt happens)
You can convert your struct like this, by using a cycle of JSON Marshal / Unmarshal. Code follows:
package main
import (
"encoding/json"
"log"
)
const (
inputJSON = `{
"id": 10,
"result": [
{
"bundled": true,
"type": "RM-J1100"
},
[
{
"name": "PowerOff",
"value": "AAAAAQAAAAEAAAAvAw=="
},
{
"name": "Input",
"value": "AAAAAQAAAAEAAAAlAw=="
}
]
]
}`
)
type Codes struct {
Id int32 `json:"id"`
Result [2]interface{} `json:"result"`
}
type Result struct {
Info InfoMap
Codes []Code
}
type InfoMap struct {
Bundled bool `json:"bundled"`
Type string `json:"type"`
}
type Code struct {
Name string `json:"name"`
Value string `json:"value"`
}
func main() {
newCodes := &Codes{}
err := json.Unmarshal([]byte(inputJSON), newCodes)
if err != nil {
log.Fatal(err)
}
// Prints the whole object
log.Println(newCodes)
// Prints the Result array (!)
log.Println(newCodes.Result)
if len(newCodes.Result) != 2 {
log.Fatal("Invalid Result struct")
}
// Marshal and Unmarshal data to obtain the code list
byteCodeList, _ := json.Marshal(newCodes.Result[1])
codeList := make([]Code, 0)
err = json.Unmarshal(byteCodeList, &codeList)
if err != nil {
log.Fatal("Invalid Code list")
}
// Prints the codeList
log.Println(codeList)
}
Test it on playground.