Create struct for complex JSON array in Golang - json

I have the following JSON array that I'm trying to convert to a struct.
[
{
"titel": "test 1",
"event": "some value",
"pair": "some value",
"condition": [
"or",
[
"contains",
"url",
"/"
]
],
"actions": [
[
"option1",
"12",
"1"
],
[
"option2",
"3",
"1"
]
]
}, {
"titel": "test 2",
"event": "some value",
"pair": "some value",
"condition": [
"or",
[
"contains",
"url",
"/"
]
],
"actions": [
[
"option1",
"12",
"1"
],
[
"option2",
"3",
"1"
]
]
}
]
This is the struct I've got so far:
type Trigger struct {
Event string `json:"event"`
Pair string `json:"pair"`
Actions [][]string `json:"actions"`
Condition []interface{} `json:"condition"`
}
type Triggers struct {
Collection []Trigger
}
However, this does not really cover the "Condition" part. Ideally id like to have a structure for that as well.

Assuming that there can be only a single condition per item in the root array, you can try a struct below. This could make using Condition clear.
https://play.golang.org/p/WxFhBjJmEN
type Trigger struct {
Event string `json:"event"`
Pair string `json:"pair"`
Actions [][]string `json:"actions"`
Condition Condition `json:"condition"`
}
type Condition []interface{}
func (c *Condition) Typ() string {
return (*c)[0].(string)
}
func (c *Condition) Val() []string {
xs := (*c)[1].([]interface{})
ys := make([]string, len(xs))
for i, x := range xs {
ys[i] = x.(string)
}
return ys
}
type Triggers struct {
Collection []Trigger
}

Related

Is there any way to unmarshal JSON into a map and preserve keys order

I need to unmarshal JSON data into the map by preserving the order of keys and values.
This is a JSON data example:
{
"31ded736-4076-4b1c-b38f-7e8d9fa78b41": {
"Name": "Requested",
"Items": [
{ "ID": "c7ac5b7f-59b0-45e3-82fb-b3b0afc05f55", "GroupName": "First task" , "NumQuestion": "10" ,"Score":"10"},
{"ID": "17acf9a1-b2c7-46c6-b975-759b9d9f538d", "GroupName": "Test1" , "NumQuestion": "20" ,"Score":"5" }
]
},
"115f7d04-3075-408a-b8ce-c6e46fe6053f": {
"Name": "To do",
"Items": [
{ "ID": "c7ac5b7f-59b0-45e3-82fb-b3b0afc05f56", "GroupName": "First task" , "NumQuestion": "5" ,"Score":"10"}
]
},
"9bcf1415-3a41-43b6-a871-8de1939a75c4": {
"Name": "In Progress",
"Items": [
{ "ID": "c7ac5b7f-59b0-45e3-82fb-b3b0afc05f57", "GroupName": "Second task" , "NumQuestion": "10" ,"Score":"5"}
]
},
"2f6c1455-6cf9-4009-b86b-de0a0d2204a1": {
"Name": "Done",
"Items": [
{ "ID": "c7ac5b7f-59b0-45e3-82fb-b3b0afc05f58", "GroupName": "Third task" , "NumQuestion": "20" , "Score":"7"}
]
}
}
This is my code:
var GroupTestListUpdate = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
type Item struct {
ID string `json:"id"`
GroupName string `json:"groupName"`
NumQuestion string `json:"numQuestion"`
Score string `json:"score"`
}
type Input struct {
Name string `json:"name"`
Items []Item `array:"item"`
}
var objmap map[string]Input
reqBody, err := ioutil.ReadAll(r.Body)
if err != nil{
http.Error(w, "can't read body", http.StatusBadRequest)
return
}
json.Unmarshal(reqBody, &objmap)
fmt.Println(objmap)
})
Output:
map[115f7d04-3075-408a-b8ce-c6e46fe6053f:{To do [{c7ac5b7f-59b0-45e3-82fb-b3b0afc05f56 First task 5 10}]} 2f6c1455-6cf9-4009-b86b-de0a0d2204a1:{Done [{c7ac5b7f-59b0-45e3-82fb-b3b0afc05f58 Third task 20 7}]} 31ded736-4076-4b1c-b38f-7e8d9fa78b41:{Requested [{c7ac5b7f-59b0-45e3-82fb-b3b0afc05f55 First task 10 10} {17acf9a1-b2c7-46c6-b975-759b9d9f538d Test1 20 5}]} 9bcf1415-3a41-43b6-a871-8de1939a75c4:{In Progress [{c7ac5b7f-59b0-45e3-82fb-b3b0afc05f57 Second task 10 5}]}]
Question:
I want to know how to preserve the order of keys and values.
I got the same output every time I tried this code, So I wonder that the order in the map is not completely random?
Yes.
You can override the UnmarshalJSON method on a custom map type that will allow you to preserve the order.
As an example, here is a library that does just that: https://github.com/iancoleman/orderedmap.

hcl, json, go : how can I iterate JSON?

Problem: I’m trying to iterate through JSON-content and present the result like key,value pairs.
I've written some code that read hcl-files, these are then decoded with hcldec.Decode, and the result is then converted to JSON. These hcl-files define source and target for the application like this:
source.hcl:
source json "namefile" {
attr firstName {
type = "varchar"
expr = "$.firstName"
length = "30"
}
attr lastName {
type = "varchar"
expr = "$.lastName"
length = "40"
}
attr gender {
type = "varchar"
expr = "$.gender"
length = "10"
}
attr age {
type = "varchar"
expr = "$.age"
length = "2"
}
}
target.hcl
target table {
cols firstName {
name=source.json.namefile.attr.firstName.expr
type=source.json.namefile.attr.firstName.type
length=source.json.namefile.attr.firstName.length
}
cols lastName {
name=source.json.namefile.attr.lastName.expr
type=source.json.namefile.attr.lastName.type
length=source.json.namefile.attr.lastName.length
}
}
The decoding is done like this:
tspec := hcldec.ObjectSpec{
"target": &hcldec.BlockMapSpec{
TypeName: "target",
LabelNames: []string{"table"},
Nested: hcldec.ObjectSpec{
"cols": &hcldec.BlockMapSpec{
TypeName: "cols",
LabelNames: []string{"name"},
Nested: &hcldec.ObjectSpec{
"name": &hcldec.AttrSpec{
Name: "name",
Type: cty.String, //cty.List(cty.String),
Required: false,
},
"type": &hcldec.AttrSpec{
Name: "type",
Type: cty.String, //cty.List(cty.String),
Required: false,
},
"length": &hcldec.AttrSpec{
Name: "length",
Type: cty.String, //cty.List(cty.String),
Required: false,
},
},
},
},
},
}
targ, _ := hcldec.Decode(body, tspec, &hcl.EvalContext{
Variables: map[string]cty.Value{
"source": val.GetAttr("source"),
},
Functions: nil,
})
j := decodeCtyToJson(targ, true)
log.Debugf("targ -j (spec): %s", string(j)) // debug info
Where the decodeCtyToJson return []byte like this:
func decodeCtyToJson(value cty.Value, pretty bool) []byte {
jsonified, err := ctyjson.Marshal(value, cty.DynamicPseudoType)
if err != nil {
log.Debugf("Error: #v", err)
return nil
}
if pretty {
return jsonPretty.Pretty(jsonified)
}
return jsonified
}
Now, when I'm trying to testprint the JSON-content I'm not getting what I'm looking for:
var result map[string]interface{}
json.Unmarshal(j, &result)
log.Debugf("result: %# v", result)
tgtfil := result["value"].(map[string]interface{})
log.Debugf("tgtfil: %v", tgtfil)
log.Debugf("len(tgtfil): %# v", len(tgtfil))
for key, value := range tgtfil {
log.Debugf("key: %# v", key)
log.Debugf("value: %# v", value)
}
I am trying to get key, value pairs. But I'm getting this (first the whole JSON pretty print as wanted, then I am trying to loop through the JSON):
DEBU[0000] targ -j (spec): {
"value": {
"target": {
"table": {
"cols": {
"firstName": {
"length": "30",
"name": "$.firstName",
"type": "varchar"
},
"lastName": {
"length": "40",
"name": "$.lastName",
"type": "varchar"
}
}
}
}
},
"type": [
"object",
{
"target": [
"map",
[
"object",
{
"cols": [
"map",
[
"object",
{
"length": "string",
"name": "string",
"type": "string"
}
]
]
}
]
]
}
]
}
DEBU[0000] result: map[string]interface {}{"type":[]interface {}{"object", map[string]interface {}{"target":[]interface {}{"map", []interface {}{"object", map[string]interface {}{"cols":[]interface {}{"map", []interface {}{"object", map[string]interface {}{"length":"string", "name":"string", "type":"string"}}}}}}}}, "value":map[string]interface {}{"target":map[string]interface {}{"table":map[string]interface {}{"cols":map[string]interface {}{"firstName":map[string]interface {}{"length":"30", "name":"$.firstName", "type":"varchar"}, "lastName":map[string]interface {}{"length":"40", "name":"$.lastName", "type":"varchar"}}}}}}
DEBU[0000] tgtfil: map[target:map[table:map[cols:map[firstName:map[length:30 name:$.firstName type:varchar] lastName:map[length:40 name:$.lastName type:varchar]]]]]
DEBU[0000] len(tgtfil): 1
DEBU[0000] key: "target"
DEBU[0000] value: map[string]interface {}{"table":map[string]interface {}{"cols":map[string]interface {}{"firstName":map[string]interface {}{"length":"30", "name":"$.firstName", "type":"varchar"}, "lastName":map[string]interface {}{"length":"40", "name":"$.lastName", "type":"varchar"}}}}
Process finished with exit code 0
My aim here is to eventually be able to iterate through alle the attributes defined in the target.hcl (length, name and type for each cols in this case). Then generate DDL-code from this information and finally implement the DDL in e.g. Presto.
But as of now I’m not able to isolate this information.
Any pointers on how to do this is appreciated.
Thanks,
/b
The solution for me was to create a struct for the target and not use the target spec.

Enumerate slice entries

If I have this output
Stuff {
"Items": [
{
"title": "test1",
"id": "1",
},
{
"title": "test",
"id": "2",
},
],
"total": 19
},
}
But want this instead:
stuff {
"Items": [
1:{
"title": "test1",
"id": "1",
},
2:{
"title": "test",
"id": "2",
},
],
"total": 19
},
}
Currently, my structs are build like this:
Stuff struct {
Items []Items `json:"items"`
Total int `json:"total"`
} `json:"stuff"`
type Items struct {
Title string `json:"title"`
ID string `json:"id"`
}
I initiate a slice by:
stuff := make([]Items, 10) // Lets say I have 10 entries
And append by:
Stuff.Items = stuff
Stuff.Total = len(Stuff.Items)
Now I am unsure on how to numerate that. So for every item entries, there should be a number, starting from 1 - 10 (In this example)
Given your Stuff and Items type declarations, here's a simple data structure and its JSON dump:
s := Stuff{Items: []Items{Items{"test1", "1"}, Items{"test2", "2"}}, Total: 10}
j, err := json.MarshalIndent(s, "", " ")
if err != nil {
log.Fatal(err)
}
fmt.Println(string(j))
JSON:
{
"items": [
{
"title": "test1",
"id": "1"
},
{
"title": "test2",
"id": "2"
}
],
"total": 10
}
To see what you want instead, there's no magic json package call to do that. You'll have to create a new data structure that reflects the structure of your desired output.
In this case a simple map will do:
m := make(map[string]Items)
for _, item := range s.Items {
m[item.ID] = item
}
And now if you JSON-dump this map, you get:
{
"1": {
"title": "test1",
"id": "1"
},
"2": {
"title": "test2",
"id": "2"
}
}
Note that I'm not wrapping it with Stuff now, because Stuff has different fields. Go is statically typed, and each struct can only contain the fields you declared it to have.

One of the elements of the array as an object

The question is how to get the "from" element? The rest is not a problem
I know that in https://github.com/json-iterator/ it can be done, but I could not figure out how it works there
Json:
{
"ab": 123456789,
"cd": [
[
4,
1234,
123456,
1000000001,
1234567890,
"text",
{
"from": "123456"
}
],
[
4,
4321,
654321,
1000000001,
9876543210,
"text",
{
"from": "654321"
}
]
]
}
Golang:
type test struct {
Ab int `json:"ab"`
Cd [][]interface{} `json:"cd"`
}
var testvar test
json.Unmarshal(Data, &testvar)
testvar.Cd[0][6]["from"].(string)
Error:
invalid operation: testvar.Cd[0][6]["from"] (type interface {} does not support indexing)
It's simple: it is a map[string]interface{} hence
m, ok := testvar.Cd[0][6].(map[string]interface{})
fmt.Println(m, ok, m["from"])
https://play.golang.org/p/XJGs_HsxMfZ

Convert struct to json array instead of json object

I apologise in advance if this question is considered too easy or whatever; this is the first time I'm writing anything in go. I have two structs (simplified for this question)
type A struct {
Content string
}
type B struct {
Element A `json:"0"`
Children []B `json:"1"`
}
I want to encode a value of type B into JSON, but instead of returning an object it should return a json array
For example:
What I get:
[
{
"0":{
"Content":"AAA"
},
"1":[
{
"0":{
"Content":"BBB"
},
"1":[
{
"0":{
"Content":"CCC"
},
"1":[]
},
{
"0":{
"Content":"DDD"
},
"1":[]
}
}
]
]
}
]
What I need:
[
{"Content": "AAA"},
[
[
{"Content": "BBB"},
[
{"Content": "CCC"},
[]
]
],
[
{"Content": "DDD"},
[]
]
]
]
I could do this by manually iterating through it, but I would hope that there's an integrated way to do it
You may do so by implementing json.Marshaler interface in B.
For example: https://play.golang.org/p/fT1WlQ5Raz
package main
import (
"encoding/json"
"fmt"
)
type A struct {
Content string
}
type B struct {
Element A
Children []B
}
// MarshalJSON implements json.Marshaler
func (b B) MarshalJSON() ([]byte, error) {
return json.Marshal([]interface{}{
b.Element,
b.Children,
})
}
func main() {
root := B{
Element: A{Content: "AAA"},
Children: []B{
B{
Element: A{Content: "BBB"},
Children: []B{
B{Element: A{Content: "CCC"}, Children: []B{}},
B{Element: A{Content: "DDD"}, Children: []B{}},
},
},
},
}
content, _ := json.MarshalIndent(root, "", " ")
fmt.Printf("%s\n", content)
}
Results:
[
{
"Content": "AAA"
},
[
[
{
"Content": "BBB"
},
[
[
{
"Content": "CCC"
},
[]
],
[
{
"Content": "DDD"
},
[]
]
]
]
]
]
i think you need a slice of interface to wrap both A and B struct into a single slice
please take a look at my snippet here https://play.golang.org/p/c0xldskKyz