Golang generic JSON marshalling - arrays

I'm working on s small server-client project based on JSON communication. But i run into issues. I'm trying to create a response struct with a generic message body. This means I have an map with a key as string and a json raw message as value. In the end the message body should work for any type (strings, integers, arrays)
package main
import (
"encoding/json"
"fmt"
)
type ServerResponse struct {
Code int `json:"code" bson:"code"`
Type string `json:"type" bson:"type"`
Body map[string]json.RawMessage `json:"body" bson:"body"`
}
func NewServerResponse() *ServerResponse {
return &ServerResponse{Body: make(map[string]json.RawMessage)}
}
func main(){
serverResponse := NewServerResponse()
serverResponse.Code = 100
serverResponse.Type = "molly"
serverResponse.Body["string"] = json.RawMessage("getIt")
serverResponse.Body["integer"] = json.RawMessage{200}
serverResponse.Body["array"] = json.RawMessage(`["a", "b", "c"]`)
if d, err := json.Marshal(&serverResponse); err != nil{
fmt.Println("Error " + err.Error())
}else{
fmt.Println(string(d))
}
}
But the output is as follow.
{
"code":100,
"type":"molly",
"body": {
"array":"WyJhIiwgImIiLCAiYyJd",
"integer":"yA==",
"string":"Z2V0SXQ="
}
}
It seems like the values are Base64 encoded and inside double quotes. Tihs should be the expected output
{
"code":100,
"type":"molly",
"body": {
"array":["a", "b", "c"],
"integer":200,
"string":"getIt"
}
}
Is this even possible? Or do I have to write a specific struct type for every response?

The raw message must be valid JSON.
Add quotes to the string to make it a valid JSON string.
serverResponse.Body["string"] = json.RawMessage("\"getIt\"")
JSON numbers are a sequence of decimal bytes. A number is not the value of a single byte as attempted in the question.
serverResponse.Body["integer"] = json.RawMessage("200")
This one works as you expected.
serverResponse.Body["array"] = json.RawMessage(`["a", "b", "c"]`)
The program in the question compiles and runs with errors. Examining those errors and fixing them leads to my suggestions above.
An alternative approach is to replace the use of json.RawMessage with interface{}:
type ServerResponse struct {
Code int `json:"code" bson:"code"`
Type string `json:"type" bson:"type"`
Body map[string]interface{} `json:"body" bson:"body"`
}
Set the response body like this:
serverResponse.Body["string"] = "getIt"
serverResponse.Body["integer"] = 200
serverResponse.Body["array"] = []string{"a", "b", "c"}
You can use json.RawMessage values:
serverResponse.Body["array"] = json.RawMessage(`["a", "b", "c"]`)
Playground example

Related

Handling Null JSON Array in Go using struct

we have struct and getting null after append struct in golang.
Find below struct with my some part of code in golang.
type XmloutRoomRate struct {
CancellationPolicy Policies `bson:"cancellationPolicy" json:"cancellationPolicy"`
}
type Policies struct {
Policies []RoomPolicies `bson:"policies" json:"policies"`
}
type RoomPolicies struct {
Amount float64 `bson:"amount" json:"amount"`
DaysBeforeCheckIn int `bson:"daysBeforeCheckIn" json:"daysBeforeCheckIn"`
}
cancelPolicyMain := Policies{}
cancelPolicy := RoomPolicies{}
if cancelAmount < 0 {
cancelPolicy.Amount = cancelAmount
cancelPolicy.DaysBeforeCheckIn = cancelDay
cancelPolicyMain.Policies = append(cancelPolicyMain.Policies, cancelPolicy)
}else{
cancelPolicyMain = agodaPolicies{}
cancelPolicyMain.Policies = append(cancelPolicyMain.Policies)
}
when data present getting proper data structure.
"cancellationPolicy": {
"policies": [
{
"amount": 5141.58,
"daysBeforeCheckIn": 5
}
]
}
But when data not available getting struct with null value.
"cancellationPolicy": {
"policies": null
}
We need my actual output with blank array [].
"cancellationPolicy": {
"policies": []
}
nil slice values are marshaled into JSON null values. This is documented at json.Marshal():
Array and slice values encode as JSON arrays, except that []byte encodes as a base64-encoded string, and a nil slice encodes as the null JSON value.
Non-nil empty slices are marshaled into empty JSON arrays. So simply initialize Policies.Policies to a non-nil empty slice, and it will be [] in the output:
cancelPolicyMain = Policies{Policies: []RoomPolicies{}}
Test code:
const cancelDay = 1
for cancelAmount := -500.0; cancelAmount <= 501; cancelAmount += 1000 {
cancelPolicyMain := Policies{}
cancelPolicy := RoomPolicies{}
if cancelAmount < 0 {
cancelPolicy.Amount = cancelAmount
cancelPolicy.DaysBeforeCheckIn = cancelDay
cancelPolicyMain.Policies = append(cancelPolicyMain.Policies, cancelPolicy)
} else {
cancelPolicyMain = Policies{Policies: []RoomPolicies{}}
cancelPolicyMain.Policies = append(cancelPolicyMain.Policies)
}
x := XmloutRoomRate{cancelPolicyMain}
if err := json.NewEncoder(os.Stdout).Encode(x); err != nil {
panic(err)
}
}
Output (try it on the Go Playground):
{"cancellationPolicy":{"policies":[{"amount":-500,"daysBeforeCheckIn":1}]}}
{"cancellationPolicy":{"policies":[]}}
In an array, the meaning of a "null" entry is clear: It means this array entry is missing.
In an object aka dictionary, there are different ways to indicate "no entry": The key might not be there. Or the key might not be there, but with an empty array as value. Or the key might be there, but with a "null" value.
You really need agreement between the provider of the data and the client processing it, what each of these mean. And since it's often hard to change what the data provider does, translate what you get into what you need.
So you have to decide what it means if "policies" does not exist as a key, or if it exists as a null value. I've seen software that wouldn't produce arrays with one element, but would provide the single element instead. So "policies": { "amount": ..., "daysBeforeCheckin": ... } would also be possible. You decide what you accept, what you treat as an array, and how you change from the form you got to the form you want.

Unmarshal JSON Array in Go

I've read a few answers here and none seem to help. I keep getting the same error:
json: cannot unmarshal array into Go value of type A
type A struct {
Arr []string
}
type MA []A
func UnmarshalJSON() (ma MA, err error) {
jsonFile, err := os.Open(aJsonFile)
// error handling
defer jsonFile.Close()
byteValue, _ := ioutil.ReadAll(jsonFile)
err = json.Unmarshal(byteValue, &ma)
if err != nil {
return ma, err
}
return ma, nil
}
The json looks like this:
[
["Name", "Another Name", "Another"],
["A", "B"],
["W", "X", "Y", "Z"],
["Foo", "Bar"]
]
I've tried various different things from top answers when searching and as previously stated, nothing has worked.
I'm still decently new to Go and having to unmarshall stuff (I'm currently unmarshalling to [][]string but I want to use structs). What am I doing wrong?
A struct has fields; fields have names; the name of the field in your struct is Arr. So the json input would need to be [{ "arr": ["list", "of", "names"]}, {"arr": ["more", "names"]}] for instance, given the example you have above.
You can, however, define an UnmarshalJSON on your type named A, like this:
func (p *A) UnmarshalJSON(data []byte) error {
var s []string
err := json.Unmarshal(data, &s)
if err != nil {
return err
}
p.Arr = s
return nil
}
This receiver function named UnmarshalJSON takes a pointer to an A object, plus some sort of valid json input. Its job is to unmarshal that json. In this case we attempt to unmarshal into an ordinary slice-of-string—the variable s—which works as long as the json itself is a valid initializer for slice-of-strings.
If the unmarshal succeeds we then set p.Arr, knowing that the array is meant just for the thing named Arr (which is in fact the only member of the structure type) and return nil (no error).
Note that the last few lines could be written as:
if err == nil {
p.Arr = s
}
return err
which is shorter, but Go conventions generally handle the error case first, rather than letting it flow through. (I actually prefer the shorter code myself, slightly, but use whatever your group likes.)
(Given an UnmarshalJSON receiver on the type, there probably should be a json marshaler on the type, too, but my minimal test-and-example on the Go playground does not have one.)
your json content should like this
[
{
"Arr": [
"a0",
"a1"
]
},
{
"Arr": [
"b0",
"b1"
]
},
{
"Arr": [
"c0",
"c1"
]
}
]

How to validate JSON when using unmarshal into generic interface?

I want to validate byte array data if it contains valid JSON using unmarsall method into interface.
package main
import (
"encoding/json"
"fmt"
)
func isJSON(s string) bool {
var js map[string]interface{}
return json.Unmarshal([]byte(s), &js) == nil
}
func main() {
var tests = []string{
`{"a":"b"}`,
`[{"a":"b"},{"a":"b"}]`,
}
for _, t := range tests {
fmt.Printf("isJSON(%s) = %v\n\n", t, isJSON(t))
}
}
Both input test parameters are valid JSON strings, but it validate based on the interface 'map[string]interface{}'
{
"a": "b"
}
[{
"a": "b"
}, {
"a": "b"
}]
I want to validate the JSON text. JSON text is a serialized object or array. Hence looking for a solution which support all valid cases for JSON text as I added test cases in playground.
How can I make this interface i.e var map[string]interface{} generic so it support both test cases of valid JSON string?
Solved: Playground Link
Don't use map[string]interface{} but simply interface{}. The second example is of type []interface{} and there are more types of valid json.
Here your working code. I added a few more cases of valid json.
playground
Here the code if you want to allow only maps and slices:
playground

Parse embedded Struct form values in GoLang

I have a struct that includes an array of another struct, eg
type Struct1 struct {
Value string
Items []Struct2
}
type Struct2 struct {
Value string
}
I am using gorilla schema to decode my Form values into Struct 1.
The values for the embedded struct, Struct 2, are not coming through.
When I look at the logs for the FormValue("Struct2") it returns '[Object object], [Object object]'
Any help would be greatly appreciated
EDIT
An example of the structure of the form,
Using AngularJS,
var data = $scope.struct1;
$http({
method: 'POST',
url:url,
data : data,
headers : {'Content-Type': 'application/x-www-form-urlencoded'},
transformRequest: function(obj) {
var str = [];
for(var p in obj)
str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p]));
return str.join("&");
}
})
.then(function successCallback(response) {
console.log(response);
}, function errorCallback(response) {
});
It's possible that you don't have the right struct design for your HTML form. You might want to post your input and/or your HTML form design.
gorilla/schema's decode expects the values input to be passed as a variable of type map[string][]string, as you can see both from the example in the documentation and from the test files in the package. Here's a simple complete script that just wraps the example from the documentation and prints the result:
package main
import(
"fmt"
"github.com/gorilla/schema"
)
type Person struct {
Name string
Phone string
}
func main() {
values := map[string][]string{
"Name": {"John"},
"Phone": {"999-999-999"},
}
person := new(Person)
decoder := schema.NewDecoder()
decoder.Decode(person, values)
fmt.Printf("Person: %v\n", person)
}
That outputs Person: &{John 999-999-999}.
That is the correct form for a map literal of type map[string][]string, as you can demonstrate by performing a declaration followed by an assignment and running it without error:
var values map[string][]string
values = map[string][]string{
"Name": {"John"},
"Phone": {"999-999-999"},
}
Now map[string][]string doesn't obviously support all the types that gorilla/schema supports, such as the type in your question: slices of structs. But the HTML form is processed such that a translation makes sense: it keeps appending indices and field names with dot separators to create the desired structure. So for the types you posted in your question, I wrote this script to decode values into the structs:
package main
import(
"fmt"
"github.com/gorilla/schema"
)
type Struct1 struct {
Value string
Items []Struct2
}
type Struct2 struct {
Value string
}
func main() {
values := map[string][]string{
"Value": {"the thing with the items"},
"Items.0.Value": {"a"},
"Items.1.Value": {"b"},
"Items.2.Value": {"c"},
}
s1 := new(Struct1)
decoder := schema.NewDecoder()
decoder.Decode(s1, values)
fmt.Printf("S1: %v\n", s1)
}
Running that outputs:
S1: &{the thing with the items [{a} {b} {c}]}
That demonstrates that decode can populate your struct design without error, if its input matches that design.
So you might try to verify that your input matches that scheme -- that it has those array-like indices and field names with the dot separator in a way that conforms to your struct design. And if it does not, that indicates that your struct design needs to be updated to fit the format of your input.
You can see examples of decode working on this type of structure in the decode_test.go file in the gorilla/schema package, such as these lines:
type Foo struct {
F01 int
F02 Bar
Bif []Baz
}
type Bar struct {
F01 string
F02 string
F03 string
F14 string
S05 string
Str string
}
type Baz struct {
F99 []string
}
func TestSimpleExample(t *testing.T) {
data := map[string][]string{
"F01": {"1"},
"F02.F01": {"S1"},
"F02.F02": {"S2"},
"F02.F03": {"S3"},
"F02.F14": {"S4"},
"F02.S05": {"S5"},
"F02.Str": {"Str"},
"Bif.0.F99": {"A", "B", "C"},
}
The Foo struct has a field named Bif of type []Baz. Baz is a struct -- so we have a slice of structs type, like in your question. Baz has a field named F99. You can see that the input is referenced with the string value "Bif.0.F99".
Use this and other examples in the test file as your guide.

How to Unmarshal the json array?

There is something wrong when I unmarshal the json array.
How do I correct it ? the code is:http://play.golang.org/p/AtU9q8Hlye
package main
import (
"encoding/json"
"fmt"
)
type Server struct {
ServerName string
ServerIP string
}
type Serverslice struct {
Name string
Servers []Server
}
func main() {
var s []Serverslice
str := `{"name":"dxh","servers":[{"serverName":"VPN0","serverIP":"127.0.0.1"},{"serverName":"Beijing_VPN","serverIP":"127.0.0.2"}],
"name":"dxh1,"servers":[{"serverName":"VPN1","serverIP":"127.0.0.1"},{"serverName":"Beijing_VPN","serverIP":"127.0.0.2"}]}`
json.Unmarshal([]byte(str), &s) //the wrong line.....................
fmt.Println(len(s))
}
First of all, you're ignoring the error return value from json.Unmarshal. You probably want something like:
if err := json.Unmarshal([]byte(str), &s); err != nil {
log.Fatalln(err)
}
With that change, we can see that your JSON data isn't valid: invalid character 's' after object key:value pair. There is a missing quote at the end of "dxh1 on the second line.
Fixing that error and rerunning the program you'll get a different error: json: cannot unmarshal object into Go value of type []main.Serverslice. There are two possible problems here:
You meant to decode into an object. In this case, just declare s as a Serverslice. Here is a version of your program that makes that change: http://play.golang.org/p/zgyr_vnn-_
Your JSON is supposed to be an array (possible, since it seems to have duplicate keys). Here's an updated version with the JSON changed to provide an array: http://play.golang.org/p/Wl6kUaivEm

Resources