How to validate JSON when using unmarshal into generic interface? - arrays

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

Related

type interface {} does not support indexing in golang

I have such map:
Map := make(map[string]interface{})
This map is supposed to contain mapping from string to array of objects. Arrays can be of different types, like []Users or []Hosts. I populated this array:
TopologyMap["Users"] = Users_Array
TopologyMap["Hosts"] = Hosts_Array
but when I try to get an elements from it:
Map["Users"][0]
it gives an error:
(type interface {} does not support indexing)
How can I overcome it?
You have to explicitly convert your interface{} to a slice of the expected type to achieve it. Something like this:
package main
import "fmt"
type Host struct {
Name string
}
func main() {
Map := make(map[string]interface{})
Map["hosts"] = []Host{Host{"test.com"}, Host{"test2.com"}}
hm := Map["hosts"].([]Host)
fmt.Println(hm[0])
}
Playground link
First thing to be noted is the interface{} can hold any data type including function and struct or []struct. Since the error gives you :
(type interface {} does not support indexing)
It means that it holds no slice or no array values. Because you directly call the index in this case is 0 to an interface{} and you assume that the Map["Users"] is an array. But it is not. This is one of very good thing about Go it is statically type which mean all the data type is check at compiled time.
if you want to be avoid the parsing error like this:
panic: interface conversion: interface {} is []main.User, not
[]main.Host
To avoid that error while your parsing it to another type like Map["user"].([]User) just in case that another data type pass to the interface{} consider the code snippet below :
u, ok := myMap["user"].([]User)
if ok {
log.Printf("value = %+v\n", u)
}
Above code is simple and you can use it to check if the interface match to the type you are parsing.
And if you want to be more general passing the value to your interface{} at runtime you can check it first using reflect.TypeOf() please consider this code :
switch reflect.TypeOf(myMap["user"]).String() {
case "[]main.User":
log.Println("map = ", "slice of user")
logger.Debug("map = ", myMap["user"].([]User)[0])
case "[]main.Host":
log.Println("map = ", "slice of host")
logger.Debug("map = ", myMap["user"].([]Host)[0])
}
after you know what's the value of the interface{} you can confidently parse it the your specific data type in this case slice of user []User. Not that the main there is a package name you can change it to yours.
This is how I solved it for unstructured data. You have to parse to index string until you reach the end. Then you can print key value pairs.
yamlStr := `
input:
bind: 0.0.0.0:12002
interface: eth0
reaggregate: {}
versions: {}
output:
destination: owl-opsw-sw-dev-4.opsw:32001
interface: eth0
`
var obj map[string]interface{}
if err := yaml.Unmarshal([]byte(yamlStr), &obj); err != nil {
// Act on error
}
// Set nested object to map[string]
inputkv := streamObj["input"].(map[string]interface{})
for key, value := range inputkv {
// Each value is an interface{} type, that is type asserted as a string
fmt.Println(key, value.(string))
}
Result
bind 0.0.0.0:12002
interface eth0

Golang generic JSON marshalling

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

Unmarshal JSON object of strings, ints and arrays into a map

I like to unmarshal a JSON string using Decode():
var message Message
decoder := json.NewDecoder(s)
err = decoder.Decode(&message)
My data structure is
type Message map[string]interface{}
The test data is as follows:
{
"names": [
"HINDERNIS",
"TROCKNET",
"UMGEBENDEN"
],
"id":1189,
"command":"checkNames"
}
It's working fine for numbers and strings, but with the string array I get following panic:
panic: interface conversion: interface is []interface {}, not []string
this is not possible by conversion because a slice of struct != slice of interface it implements!
either you can get the elements one by one and put them into a []string like this: http://play.golang.org/p/1yqScF9yVX
or better, use the capabilities of the json package to unpack the data in your model format : http://golang.org/pkg/encoding/json/#example_Unmarshal

Serialize a mixed type JSON array in Go

I want to return a structure that looks like this:
{
results: [
["ooid1", 2.0, "Söme text"],
["ooid2", 1.3, "Åther text"],
]
}
That's an array of arrags that is string, floating point number, unicode character.
If it was Python I'd be able to:
import json
json.dumps({'results': [["ooid1", 2.0, u"Söme text"], ...])
But in Go you can't have an array (or slice) of mixed types.
I thought of using a struct like this:
type Row struct {
Ooid string
Score float64
Text rune
}
But I don't want each to become a dictionary, I want it to become an array of 3 elements each.
We can customize how an object is serialized by implementing the json.Marshaler interface. For our particular case, we seem to have a slice of Row elements that we want to encode as an array of heterogenous values. We can do so by defining a MarshalJSON function on our Row type, using an intermediate slice of interface{} to encode the mixed values.
This example demonstrates:
package main
import (
"encoding/json"
"fmt"
)
type Row struct {
Ooid string
Score float64
Text string
}
func (r *Row) MarshalJSON() ([]byte, error) {
arr := []interface{}{r.Ooid, r.Score, r.Text}
return json.Marshal(arr)
}
func main() {
rows := []Row{
{"ooid1", 2.0, "Söme text"},
{"ooid2", 1.3, "Åther text"},
}
marshalled, _ := json.Marshal(rows)
fmt.Println(string(marshalled))
}
Of course, we also might want to go the other way around, from JSON bytes back to structs. So there's a similar json.Unmarshaler interface that we can use.
func (r *Row) UnmarshalJSON(bs []byte) error {
arr := []interface{}{}
json.Unmarshal(bs, &arr)
// TODO: add error handling here.
r.Ooid = arr[0].(string)
r.Score = arr[1].(float64)
r.Text = arr[2].(string)
return nil
}
This uses a similar trick of first using an intermediate slice of interface{}, using the unmarshaler to place values into this generic container, and then plop the values back into our structure.
package main
import (
"encoding/json"
"fmt"
)
type Row struct {
Ooid string
Score float64
Text string
}
func (r *Row) UnmarshalJSON(bs []byte) error {
arr := []interface{}{}
json.Unmarshal(bs, &arr)
// TODO: add error handling here.
r.Ooid = arr[0].(string)
r.Score = arr[1].(float64)
r.Text = arr[2].(string)
return nil
}
func main() {
rows := []Row{}
text := `
[
["ooid4", 3.1415, "pi"],
["ooid5", 2.7182, "euler"]
]
`
json.Unmarshal([]byte(text), &rows)
fmt.Println(rows)
}
You can read a full example here.
Use []interface{}
type Results struct {
Rows []interface{} `json:"results"`
}
You will then have to use type assertion if you want to access the values stored in []interface{}
for _, row := range results.Rows {
switch r := row.(type) {
case string:
fmt.Println("string", r)
case float64:
fmt.Println("float64", r)
case int64:
fmt.Println("int64", r)
default:
fmt.Println("not found")
}
}
Some clumsy, but you can
type result [][]interface{}
type results struct {
Results result
}
Working example https://play.golang.org/p/IXAzZZ3Dg7

Unmarshalling top-level JSON array in Go

I'm learning Go by writing a simple http server and I need to handle some JSON responses.
With an object response, I can unmarshal it idiomatically with 2 lines of code:
structResult := Foo{}
json.Unmarshal(structBody, &structResult)
I don't know how to do the same for an array response (see the example below). Is there a way to specify (possibly via json tag) that top-level array should go into a given struct field?
package main
import "fmt"
import "encoding/json"
type Foo struct {
Id uint64 `json:"id"`
Name string `json:"name"`
}
type BaseResult struct {
Error string `json:"error"`
}
type FooResult struct {
BaseResult
Foos []Foo
}
func main() {
// Simple and works.
structBody := []byte(`{"id": 1,"name": "foo"}`)
structResult := Foo{}
json.Unmarshal(structBody, &structResult)
fmt.Printf("%#v\n", structResult)
// Doesn't work.
arrayBody := []byte(`[{"id": 1,"name": "foo"},{"id": 2,"name": "bar"},{"id": 3,"name": "foobar"}]`)
arrayResult := FooResult{}
json.Unmarshal(arrayBody, &arrayResult)
fmt.Printf("%#v\n", arrayResult)
}
I know I could make FooResult an array:
type FooResult []Foo
but then I lose the ability to specify base object which I would like to use to store error message and such. I also know that I can unmarshal into &fooResult.Foos directly, but I want the code to work with both objects and arrays.
UPDATE
Implementing UnmarshalJSON as suggested by #dyoo partially solves my problem, but I was hoping that I could use BaseResult to store parse error in case JSON has a different structure:
arrayBody := []byte(`{"error": "foo"}`)
arrayResult := FooResult{}
json.Unmarshal(arrayBody, &arrayResult)
fmt.Printf("%#v\n", arrayResult)
Of course I could implement more complex logic inside UnmarshalJSON - but isn't there a simpler way to do it?
You can implement the json.Unmarshaler interface in your FooResult, to customize exactly how it responds to unmarshaling. (Similarly, there's a json.Marshaler interface.)
Add:
func (f *FooResult) UnmarshalJSON(bs []byte) error {
return json.Unmarshal(bs, &f.Foos)
}
after which your code should otherwise work. http://play.golang.org/p/oMdoB2e-rB
You might try something like:
func (f *FooResult) UnmarshalJSON(bs []byte) error {
err1 := json.Unmarshal(bs, &f.BaseResult)
err2 := json.Unmarshal(bs, &f.Foos)
if err1 != nil && err2 != nil {
// Arbitrarily choose an error.
return err1
}
return nil
}
although even this is beginning to look dubious. Handling union type results is not quite what the json library is designed to handle automatically for you. You'll need to explicitly code the coercion logic if your JSON has dynamic type.
See: How to unmarshall an array of different types correctly? and http://blog.golang.org/json-and-go for related issues.
Just specify Foos when you Unmarshal
package main
import "fmt"
import "encoding/json"
type Foo struct {
Id uint64 `json:"id"`
Name string `json:"name"`
}
type BaseResult struct {
Error string `json:"error"`
}
type FooResult struct {
BaseResult
Foos []Foo
}
func main() {
// Simple and works.
structBody := []byte(`{"id": 1,"name": "foo"}`)
structResult := Foo{}
json.Unmarshal(structBody, &structResult)
fmt.Printf("%#v\n", structResult)
// Doesn't work.
arrayBody := []byte(`[{"id": 1,"name": "foo"},{"id": 2,"name": "bar"},{"id": 3,"name": "foobar"}]`)
arrayResult := FooResult{}
if err := json.Unmarshal(arrayBody, &arrayResult.Foos); err != nil {
arrayResult.BaseResult.Error = string(arrayBody)
}
fmt.Printf("%#v\n", arrayResult)
}

Resources