Marshal Unmarshal JSON struct problems causing REST request to fail - json

I am updating the problem statement since input from nipuna, who solved the problem. The question is better described as, "why does unmarshal, then marshal of JSON result in changes to the JSON that are not compatible with a REST API request?" The reason discovered, and that I could not have discovered alone, were that the text fields sometimes needed to be included and sometimes omitted in a JSON array, and the marshal data structure did not support omitting them. The structure did use the term "omitempty" for these fields, so it could be believed this directive would omit them, however that is not sufficient. I also needed for the text fields to be pointers. The original problem description follows below. The final data structure uses both "omitempty" for the text fields and also makes the anonymous struct a pointer. (Please see the accepted answer)
I have a REST request that fails when I use Go Unmarshal / Marshal but it works on the same JSON data from a file. I have verified that the resulting []byte from reading the file is different if I take that exact []byte and roundtrip UnMarshal / Marshal it. As a result, my REST request fails. Here it is diagramatically: file json -> []byte = set A of bytes. file json -> []byte -> UnMarshal -> Marshal -> []byte = set B of bytes. Set A of bytes != set B of bytes. I would not care except that I need to modify the data in the structure of Go, then Marshal it, however the byte changes that I do not understand causes the request to fail.
Here is test code:
type SlackRequestData struct {
Blocks []struct {
BlockID string `json:"block_id"`
Elements []struct {
ActionID string `json:"action_id"`
Style string `json:"style"`
Text struct {
Text string `json:"text"`
Type string `json:"type"`
} `json:"text"`
Type string `json:"type"`
Value string `json:"value"`
} `json:"elements"`
Text struct {
Text string `json:"text"`
Type string `json:"type"`
} `json:"text"`
Type string `json:"type"`
} `json:"blocks"`
}
func MarshalFailTest(){
bytes_io, err := ioutil.ReadFile("../../slack/slack-message.json")
if err != nil {
fmt.Printf("Error: %+v\n", err) // handle err
}
new_req := SlackRequestData{}
err = json.Unmarshal(bytes_io, &new_req)
if err != nil {
fmt.Printf("Error: %+v\n", err) // handle err
}
new_json, err := json.Marshal(new_req)
if err != nil {
fmt.Printf("Error: %+v\n", err) // handle err
}
fmt.Printf("New req:\n%+v\n", new_req)
fmt.Printf("New Json:\n%+v\n", new_json)
fmt.Printf("Bytes:\n%+v\n", bytes_io)
}
Here is the json file data:
{
"blocks": [{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "Service Is Ready. Now?"
}
},
{
"type": "actions",
"block_id": "deploy_id",
"elements": [{
"type": "button",
"action_id": "yes_button",
"text": {
"type": "plain_text",
"text": "Yes"
},
"style": "danger",
"value": "yes"
},
{
"type": "button",
"action_id": "yes_toll_button",
"text": {
"type": "plain_text",
"text": "Yes To All"
},
"style": "danger",
"value": "yes"
},
{
"type": "button",
"action_id": "no_button",
"text": {
"type": "plain_text",
"text": "No"
},
"style": "primary",
"value": "no"
},
{
"type": "button",
"action_id": "no_toall_button",
"text": {
"type": "plain_text",
"text": "No To All"
},
"style": "primary",
"value": "no"
}]
}]
}
Note that I used several converters to take the file json and create the SlackRequestData struct, and the UnMarshal appears to work perfectly as the struct in the debugger has all the data in the right place. There are no errors with Marshal / UnMarshal either. So why does the []byte differ?
The data sets are large, so I'll just share the first line of each set here.
Set A:
[123 10 32 32 34 98 108 111 99 107 115 34 58 32 91 123 10 32 32 32 32 34 116 121 112 101 34 58 32 34 115 101 99 116 105 111 110 34 44 10 32 32 32 32 34
Set B:
[123 34 98 108 111 99 107 115 34 58 91 123 34 98 108 111 99 107 95 105 100 34 58 34 34 44 34 101 108 101 109 101 110 116 115 34 58 110 117 108 108 44
Here are the two sets converted from []byte to strings, as this is quite interesting too. The set A keeps whitespace, the set B has no whitespace but also the elements have been rearranged. I wonder if the struct is not correct? However I've been using this technique of using converters to create structs out of json and they have worked well to this point.
Set A:
{
"blocks": [{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "Service Is Ready. Now?"
}
},
{
"type": "actions",
"block_id": "deploy_id",
"elements": [{
"type": "button",
"action_id": "yes_button",
"text": {
"type": "plain_text",
"text": "Yes"
},
"style": "danger",
"value": "yes"
},
{
"type": "button",
"action_id": "yes_toll_button",
"text": {
"type": "plain_text",
"text": "Yes To All"
},
"style": "danger",
"value": "yes"
},
{
"type": "button",
"action_id": "no_button",
"text": {
"type": "plain_text",
"text": "No"
},
"style": "primary",
"value": "no"
},
{
"type": "button",
"action_id": "no_toall_button",
"text": {
"type": "plain_text",
"text": "No To All"
},
"style": "primary",
"value": "no"
}]
}]
}
Set B:
{"blocks":[{"block_id":"","elements":null,"text":{"text":"Service Is Ready. Now?","type":"mrkdwn"},"type":"section"},{"block_id":"deploy_id","elements":[{"action_id":"yes_button","style":"danger","text":{"text":"Yes","type":"plain_text"},"type":"button","value":"yes"},{"action_id":"yes_toll_button","style":"danger","text":{"text":"Yes To All","type":"plain_text"},"type":"button","value":"yes"},{"action_id":"no_button","style":"primary","text":{"text":"No","type":"plain_text"},"type":"button","value":"no"},{"action_id":"no_toall_button","style":"primary","text":{"text":"No To All","type":"plain_text"},"type":"button","value":"no"}],"text":{"text":"","type":""},"type":"actions"}]}
nipuna suggested I try using beautify of the JSON so I did this and there is the string version of the []byte for each set.
Set A:
{
"blocks": [{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "Service Is Ready. Now?"
}
},
{
"type": "actions",
"block_id": "deploy_id",
"elements": [{
"type": "button",
"action_id": "yes_button",
"text": {
"type": "plain_text",
"text": "Yes"
},
"style": "danger",
"value": "yes"
},
{
"type": "button",
"action_id": "yes_toll_button",
"text": {
"type": "plain_text",
"text": "Yes To All"
},
"style": "danger",
"value": "yes"
},
{
"type": "button",
"action_id": "no_button",
"text": {
"type": "plain_text",
"text": "No"
},
"style": "primary",
"value": "no"
},
{
"type": "button",
"action_id": "no_toall_button",
"text": {
"type": "plain_text",
"text": "No To All"
},
"style": "primary",
"value": "no"
}]
}]
}
Set B:
{
"blocks": [
{
"block_id": "",
"elements": null,
"text": {
"text": "Service Is Ready. Now?",
"type": "mrkdwn"
},
"type": "section"
},
{
"block_id": "deploy_id",
"elements": [
{
"action_id": "yes_button",
"style": "danger",
"text": {
"text": "Yes",
"type": "plain_text"
},
"type": "button",
"value": "yes"
},
{
"action_id": "yes_toll_button",
"style": "danger",
"text": {
"text": "Yes To All",
"type": "plain_text"
},
"type": "button",
"value": "yes"
},
{
"action_id": "no_button",
"style": "primary",
"text": {
"text": "No",
"type": "plain_text"
},
"type": "button",
"value": "no"
},
{
"action_id": "no_toall_button",
"style": "primary",
"text": {
"text": "No To All",
"type": "plain_text"
},
"type": "button",
"value": "no"
}
],
"text": {
"text": "",
"type": ""
},
"type": "actions"
}
]
}

I can see following differences and I have mention some suggestions.
Optional fields
Your data consist of array of blocks, in first block, there is only two fields text and type and in second block there is all other fields but no text. So that text is optional and it need to be omit when it is an empty struct. So you need to define your SlackRequestData with omitempty and pointer type to Text fields.
Suggested SlackRequestData struct is something like below.
type Text struct {
Type string `json:"type,omitempty"`
Text string `json:"text,omitempty"`
}
type SlackRequestData struct {
Blocks []struct {
BlockID string `json:"block_id,omitempty"`
Elements []struct {
ActionID string `json:"action_id,omitempty"`
Style string `json:"style,omitempty"`
Text *Text `json:"text,omitempty"`
Type string `json:"type,omitempty"`
Value string `json:"value,omitempty"`
} `json:"elements,omitempty"`
Text *Text `json:"text,omitempty"`
Type string `json:"type,omitempty"`
} `json:"blocks,omitempty"`
}
Marshalled Data Order
your file json data's field order and SlackRequestData's field order is different. So obviously that marshalled string and file string is different. json marshalling not guarantees that order when marshalling as mentiond in here. So if you need to compare those two, please sort those bytes in someway and compare.
Json Indent
In your file data is somewhat json beautified data and json marshal return compacted output. If you need to get same marshal output, you need to use MarshalIndent and do indent you used in file.
So if you need to compare both things, marshal with omitempty, sort both results (file byte array and json marshalled byte array). and remove whitespaces and then compare. You can get correct same result.

Related

Can I use Json to build a template and integrate it to my app to make the Slackbot more presentable?

The current template looks like this:
enter image description here
This is the code used to build this "Send secret message":
import (
"fmt"
"strings"
"github.com/google/uuid"
"github.com/pkg/errors"
"github.com/slack-go/slack"
)
// SendMessage sends a message to the receiver through slack
func SendMessage(id uuid.UUID, sender string, receiver string, description string) error {
username := strings.Split(receiver, "#")[0]
_, _, err := rtm.PostMessage(
"#"+username,
slack.MsgOptionText(fmt.Sprintf(
"*%s has shared a secret with you!* \n*Description:* %s "+
"\nView the secret at https://whisper.sky/%s",
sender,
description,
id), false),
slack.MsgOptionAsUser(true),
)
if err != nil {
return errors.Wrap(err, "sending message through slack")
}
return nil
}
Blockquote
However, on the slack Block Kit Builder they use Json, so my question is, can I edit the template to how I want in Json and import the Json into the code I have currently so it looks more like this:
enter image description here
Any help would be appreciated as I am new to this background so not sure what to change and where. Thanks.
This is the code from the block builder:
{
"blocks": [
{
"type": "header",
"text": {
"type": "plain_text",
"text": "{name} has sent you a secret!",
"emoji": true
}
},
{
"type": "divider"
},
{
"type": "section",
"fields": [
{
"type": "mrkdwn",
"text": "*Type:*\nPassword"
},
{
"type": "mrkdwn",
"text": "*For:*\nGitHub Service Account"
},
{
"type": "mrkdwn",
"text": "*Message:*\nHere's the password to the GitHub Service Account"
},
{
"type": "mrkdwn",
"text": "*Expires:*\nIn 7 Days"
}
]
},
{
"type": "divider"
},
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "*To access this secret visit http://whisper.int.discoott.sky.com/ *"
},
"accessory": {
"type": "button",
"text": {
"type": "plain_text",
"text": "Click Here",
"emoji": true
},
"value": "access_secret_button",
"url": "https://whisper.int.discoott.sky.com/",
"action_id": "button-action"
}
},
{
"type": "divider"
},
{
"type": "context",
"elements": [
{
"type": "mrkdwn",
"text": "If you did not expect this secret, you can ignore it and allow it to expire."
}
]
}
]
}
I just do not know how to implement that code into my Go file.

JSON expected EOF, got ','

I was converting my JSON file into .csv and while converting there was such mistake as "expected EOF, got ','"
{
"id": 22970,
"type": "message",
"date": "2018-11-24T21:08:21",
"edited": "1970-01-01T03:00:00",
"from": "lox",
"from_id": 731504644,
"text": "no"
},
{
"id": 22971,
"type": "message",
"date": "2018-11-24T21:08:32",
"edited": "1970-01-01T03:00:00",
"from": "Gox",
"from_id": 417024817,
"text": "ok"
}
It shows that the comma after first block must be deleted,but when i delete it next { glows red. How could i format this so it will be normally converted?
Your JSON is incorrect with or without a comma.
I believe you are trying to have an array of objects with JSON. If so, the syntax should look like this
[ // Array of objects
{ // Object 1
"id": 22970,
"type": "message",
"date": "2018-11-24T21:08:21",
"edited": "1970-01-01T03:00:00",
"from": "lox",
"from_id": 731504644,
"text": "no"
}, // Comma separating array items
{ // Object 2
"id": 22971,
"type": "message",
"date": "2018-11-24T21:08:32",
"edited": "1970-01-01T03:00:00",
"from": "Gox",
"from_id": 417024817,
"text": "ok"
}
]
I recommend using any online JSON parser to check for syntax erros in the future.

Working with deeply nested JSON in Go Lang

I have what I would consider a very messy block of JSON, and I want to read and
modify two values that are deeply nested within (denoted: I want this!) using Go.
Due to the server that I am sending it to,
I cannot change the label names. What makes it especially difficult
for me is that parents have multiple children which are also nested, and since there are so many "value" labels I don't know how to specify which "value" child thatI want to enter.
I got the values in Bash very quickly using this
jq ' .value[0].value[1].value[0].value[1].value[0].value="'"$one"'" | '\ '
.value[0].value[1].value[0].value[1].value[1].value="'"$two"'"'
I tried a format like this in Go initially, but could not get it working because of the issue where children are all named "value" and I want to go into one other than the first. Unfortunately none of those magic JSON to Go struct were able to handle all of the nesting either.
type Bar struct {
Value struct { // Value[0]
Value struct { // Value[1]
Value struct { // Value[0]
Value struct { // Value[1]
Value struct { // Value[1] or Value[2]
}}}}}}
This code converts the JSON into a more struct/map friendly form, and prints the whole thing.
var foo interface{}
err := json.Unmarshal(blob, &foo)
if err != nil {
fmt.Println("error:", err)
}
m := foo.(map[string]interface{})
// print all
fmt.Println("Loop: ", m)
for k, v := range m {
fmt.Printf("%v : %v", k, v)
}
Here is the JSON that I am storing as a variable
var blob = []byte(`{
"tag": "1",
"value": [{
"tag": "2",
"value": [{
"tag": "3",
"value": [{
"tag": "4",
"type": "x",
"value": "x"
}, {
"tag": "5",
"type": "x",
"value": "x"
}
]
}, {
"tag": "6",
"value": [{
"tag": "7",
"value": [{
"tag": "8",
"type": "x",
"value": "x"
}, {
"tag": "9",
"value": [{
"tag": "10",
"type": "x",
"value": "I want this!"
}, {
"tag": "11",
"type": "Thanks for the help mate!",
"value": "I want this!"
}
]
}
]
}
]
}, {
"tag": "12",
"type": "x",
"value": "x"
}
]
}, {
"tag": "13",
"value": [{
"tag": "14",
"type": "x",
"value": "x"
}, {
"tag": "15",
"value": [{
"tag": "16",
"value": [{
"tag": "17",
"type": "x",
"value": "x"
}
]
}
]
}
]
}
]
}`)
I would appreciate any help or advice that you could give me.
Your initial attempt didn't work because you used structs where you need a slice of struct (Value []struct{...}). But that won't work either, because in some cases the values are slices and in some cases they're strings, which encoding/json does not like. Your best bet is one of two options: raw text manipulation, which will probably be quicker but more fragile and error-prone, or decoding as in your second example into a map[string]interface{} and doing some gnarly type assertions:
var foo map[string]interface{}
err := json.Unmarshal(blob, &foo)
if err != nil {
fmt.Println("error:", err)
}
foo["value"].([]interface{})[0].(map[string]interface{})["value"].([]interface{})[1].(map[string]interface{})["value"].([]interface{})[0].(map[string]interface{})["value"].([]interface{})[1].(map[string]interface{})["value"].([]interface{})[0].(map[string]interface{})["value"]

avro runtime exception not a map when return in Json format

i have a avro schema for UKRecord, which contain a list of CMRecord(also avro schemaed):
{
"namespace": "com.uhdyi.hi.avro",
"type": "record",
"name": "UKRecord",
"fields": [
{
"name": "coupon",
"type": [
"null",
"string"
],
"default": null
},
{
"name": "cm",
"type": [
"null",
{
"type": "array",
"items": {
"type": "record",
"name": "CmRecord",
"fields": [
{
"name": "id",
"type": "string",
"default": ""
},
{
"name": "name",
"type": "string",
"default": ""
}
]
}
}
],
"default": null
}
]
}
in my java code, i create a UKRecord which has all fields populated correctly, eventually i need to return this object using a json based api, however it complained:
org.apache.avro.AvroRuntimeException: Not a map: {"type":"record","name":"CmRecord","namespace":"com.uhdyi.hi.avro","fields":[{"name":"id","type":"string","default":""},{"name":"name","type":"string","default":""}]}
the java code that write the object to json is :
ObjectWriter writer = ObjectMapper.writer();
if (obj != null) {
response.setHeader(HttpHeaders.Names.CONTENT_TYPE, "application/json; charset=UTF-8");
byte[] bytes = writer.writeValueAsBytes(obj); <-- failed here
...
}
obj is:
{"coupon": "c12345", "cm": [{"id": "1", "name": "name1"}, {"id": "2", "name": "name2"}]}
why do i get this error? please help!
Because you are using unions, Avro is uncertain how to interpret the JSON you are providing. Here's how you can change the JSON so Avro knows it's not null
{
"coupon": { "string": "c12345" },
"cm": { "array": [
{ "id": "1", "name": "name1" },
{ "id": "2", "name": "name2" }
]
}
}
I know, it's really annoying how Avro chose to handle nulls.

Schema to load json data to google big query

I have a question for the project that we are doing...
I tried to extract this JSON to Google Big Query and not able to get JSON votes Object fields from the JSON input. I tried the "record" and the "string" types in the schema.
{
"votes": {
"funny": 10,
"useful": 10,
"cool": 10
},
"user_id": "OlMjqqzWZUv2-62CSqKq_A",
"review_id": "LMy8UOKOeh0b9qrz-s1fQA",
"stars": 4,
"date": "2008-07-02",
"text": "This is what this 4-star bar is all about.",
"type": "review",
"business_id": "81IjU5L-t-QQwsE38C63hQ"
}
Also i am not able to get the tables populated from this below JSON for the categories and neighborhood JSON arrays? What should my schema be for these inputs? The docs didn't help much unfortunately in this case or maybe i am not looking at the right place..
{
"business_id": "Iu-oeVzv8ZgP18NIB0UMqg",
"full_address": "3320 S Hill St\nSouth East LA\nLos Angeles, CA 90007",
"schools": [
"University of Southern California"
],
"open": true,
"categories": [
"Medical Centers",
"Health and Medical"
],
"neighborhoods": [
"South East LA"
]
}
I am able to get the regular fields, but that's about it... Any help is appreciated!
For business it seems you want schools to be a repeated field. Your schema should be:
"schema": {
"fields": [
{
"name": "business_id",
"type": "string"
}.
{
"name": "full_address",
"type": "string"
},
{
"name": "schools",
"type": "string",
"mode": "repeated"
},
{
"name": "open",
"type": "boolean"
}
]
}
For votes it seems you want record. Your schema should be:
"schema": {
"fields": [
{
"name": "name",
"type": "string"
}.
{
"name": "votes",
"type": "record",
"fields": [
{
"name": "funny",
"type": "integer",
},
{
"name": "useful",
"type": "integer"
},
{
"name": "cool",
"type": "integer"
}
]
},
]
}
Source
I was also stuck on this problem, but the issue I faced was because one has to remember to flag the mode as repeated for the records source
Also please note that these cannot have a null value source