I'm trying to parse JSON files by using dynamically created structs, but apparently I'm doing something wrong. Can somebody please tell we what am I doing wrong here:
structured := make(map[string][]reflect.StructField)
structured["Amqp1"] = []reflect.StructField{
reflect.StructField{
Name: "Test",
Type: reflect.TypeOf(""),
Tag: reflect.StructTag(`json:"test"`),
},
reflect.StructField{
Name: "Float",
Type: reflect.TypeOf(5.5),
Tag: reflect.StructTag(`json:"float"`),
},
reflect.StructField{
Name: "Connections",
Type: reflect.TypeOf([]Connection{}),
Tag: reflect.StructTag(`json:"connections"`),
},
}
sections := []reflect.StructField{}
for sect, params := range structured {
sections = append(sections,
reflect.StructField{
Name: sect,
Type: reflect.StructOf(params),
},
)
}
parsed := reflect.New(reflect.StructOf(sections)).Elem()
if err := json.Unmarshal([]byte(JSONConfigContent), &parsed); err != nil {
fmt.Printf("unable to parse data from provided configuration file: %s\n", err)
os.Exit(1)
}
https://play.golang.org/p/C2I4Pduduyg
Thanks in advance.
You want to use .Interface() to return the actual, underlying value, which should be a pointer to the concrete anonymous struct.
Note that the reflect.New function returns a reflect.Value representing a pointer to a new zero value for the specified type. The Interface method, in this case, returns that pointer as interface{} which is all you need for json.Unmarshal.
If, after unmarshaling, you need a non-pointer of the struct you can turn to reflect again and use reflect.ValueOf(parsed).Elem().Interface() to effectively dereference the pointer.
parsed := reflect.New(reflect.StructOf(sections)).Interface()
if err := json.Unmarshal([]byte(JSONConfigContent), parsed); err != nil {
fmt.Printf("unable to parse data from provided configuration file: %s\n", err)
os.Exit(1)
}
https://play.golang.org/p/Bzu1hUyKjvM
reflect.New returns a value representing a pointer.
Change line 69:
fmt.Printf(">>> %v", &parsed)
Result:
>>> <struct { Amqp1 struct { Test string "json:\"test\""; Float float64 "json:\"float\""; Connections []main.Connection "json:\"connections\"" } } Value>
I would recommend something very different. I generally avoid reflection where possible. You can accomplish what you are trying to do by simply providing structs for each type of config you expect and then do an initial "pre-unmarshalling" to determine what type of config you should actually use by name (which, in this case is a key of your JSON object):
package main
import (
"encoding/json"
"fmt"
"os"
)
//Amqp1 config struct
type Amqp1 struct {
Config struct {
Test string `json:"test"`
Float float64 `json:"float"`
Connections []Connection `json:"connections"`
} `json:"Amqp1"`
}
//Connection struct
type Connection struct {
Type string `json:"type"`
URL string `json:"url"`
}
//JSONConfigContent json
const JSONConfigContent = `{
"Amqp1": {
"test": "woobalooba",
"float": 5.5,
"connections": [
{"type": "test1", "url": "booyaka"},
{"type": "test2", "url": "foobar"}
]
}
}`
func main() {
configMap := make(map[string]interface{})
if err := json.Unmarshal([]byte(JSONConfigContent), &configMap); err != nil {
fmt.Printf("unable to parse data from provided configuration file: %s\n", err)
os.Exit(1)
}
//get config name
var configName string
for cfg := range configMap {
configName = cfg
break
}
//unmarshal appropriately
switch configName {
case "Amqp1":
var amqp1 Amqp1
if err := json.Unmarshal([]byte(JSONConfigContent), &amqp1); err != nil {
fmt.Printf("unable to parse data from provided configuration file: %s\n", err)
os.Exit(1)
}
fmt.Printf("%s >>\n", configName)
fmt.Printf("Test: %s\n", amqp1.Config.Test)
fmt.Printf("Float: %v\n", amqp1.Config.Float)
fmt.Printf("Connections: %#v\n", amqp1.Config.Connections)
default:
fmt.Printf("unknown config encountered: %s\n", configName)
os.Exit(1)
}
}
The initial "pre-unmarshalling" happens on the 2nd line of main(), to a plain map where the key is a string and the value is interface{} because you don't care. You're just doing that to get the actual type of config that it is, which is the key of the first nested object.
You can run this on playground as well.
Related
Imagine we have following Go structs:
type Config struct {
Name string `json:"name,omitempty"`
Params []Param `json:"params,omitempty"`
}
type Param struct {
Name string `json:"name,omitempty"`
Value string `json:"value,omitempty"`
}
and following json:
{
"name": "parabolic",
"subdir": "pb",
"params": [{
"name": "input",
"value": "in.csv"
}, {
"name": "output",
"value": "out.csv",
"tune": "fine"
}]
}
and we do unmarshalling:
cfg := Config{}
if err := json.Unmarshal([]byte(cfgString), &cfg); err != nil {
log.Fatalf("Error unmarshalling json: %v", err)
}
fmt.Println(cfg)
https://play.golang.org/p/HZgo0jxbQrp
Output would be {parabolic [{input in.csv} {output out.csv}]} which makes sense - unknown fields were ignored.
Question: how to find out which fields were ignored?
I.e. getIgnoredFields(cfg, cfgString) would return ["subdir", "params[1].tune"]
(There is a DisallowUnknownFields option but it's different: this option would result Unmarshal in error while question is how to still parse json without errors and find out which fields were ignored)
Not sure if that is the best way but what I did is:
If current-level type is map:
Check that all map keys are known.
It could be either if keys are struct field names or map keys.
If not known - add to list of unknown fields
Repeat recursively for value that corresponds to each key
If current level type is array:
Run recursively for each element
Code:
// ValidateUnknownFields checks that provided json
// matches provided struct. If that is not the case
// list of unknown fields is returned.
func ValidateUnknownFields(jsn []byte, strct interface{}) ([]string, error) {
var obj interface{}
err := json.Unmarshal(jsn, &obj)
if err != nil {
return nil, fmt.Errorf("error while unmarshaling json: %v", err)
}
return checkUnknownFields("", obj, reflect.ValueOf(strct)), nil
}
func checkUnknownFields(keyPref string, jsn interface{}, strct reflect.Value) []string {
var uf []string
switch concreteVal := jsn.(type) {
case map[string]interface{}:
// Iterate over map and check every value
for field, val := range concreteVal {
fullKey := fmt.Sprintf("%s.%s", keyPref, field)
subStrct := getSubStruct(field, strct)
if !subStrct.IsValid() {
uf = append(uf, fullKey[1:])
} else {
subUf := checkUnknownFields(fullKey, val, subStrct)
uf = append(uf, subUf...)
}
}
case []interface{}:
for i, val := range concreteVal {
fullKey := fmt.Sprintf("%s[%v]", keyPref, i)
subStrct := strct.Index(i)
uf = append(uf, checkUnknownFields(fullKey, val, subStrct)...)
}
}
return uf
}
Full version: https://github.com/yb172/json-unknown/blob/master/validator.go
Please read the question before flagging it.
{
"out_key": {
"some_uri": "url",
"more_than_one_key_here": {
"Ip": "127.0.0.1",
"port": "80",
}
}
}
out_key is dynamically created. There is no guessing the name of it.
Similarly, more_than_one_key_here is also dynamically created. some_uri will remain constant under out_key. In such a scenario, how do I create a struct for decoding the JSON?
In Go field names of a struct have to be known at compile time. So, in your case a struct type is not appropriate. Alternatively, you can use a map.
Let's say, that you are only interested in the IP and port values. Then you can ignore the keys of the JSON object altogether.
package main
import (
"encoding/json"
"fmt"
"log"
)
const data = `{
"out_key": {
"some_uri": "url",
"more_than_one_key_here": {
"Ip": "127.0.0.1",
"port": "80"
}
}
}`
func main() {
m := make(map[string]map[string]interface{})
err := json.Unmarshal([]byte(data), &m)
if err != nil {
log.Fatalf("failed to parse JSON: %v", err)
}
for _, value := range m {
for _, value := range value {
m, ok := value.(map[string]interface{})
if ok {
fmt.Printf("IP: %s\n", m["Ip"])
fmt.Printf("Port: %s\n", m["port"])
}
}
}
}
You could create a struct for the Ip and Port and a separate one for one of the keys.
type data struct{
Ip string
port string
}
type DynamicKey map[string]data
Then once you decode the json and have the dynamic key, let's say for example it is (rando_key) you can do this
fmt.Println(DynamicKey[rando_key].Ip, DynamicKey[rando_key].port)
This is off course for only one of the keys but it is possible to repeat the process.
When I use like &simplejson.Json{v} (v is a interface read from file and it's actual data structure is map[string]interface{}), then show this error. Details:
A json file named abcd
{
"pids": [
{
"pid": 168043,
"target_regions": [
40,
25,
43,
299,
240
]
},
{
"pid": 168044,
"target_regions": [
63,
65,
68
]
}
]
}
And the go file is
package main
import (
"fmt"
"io/ioutil"
sjson "github.com/bitly/go-simplejson"
)
type pidInfo struct {
Pid uint64 `json:"pid"`
TargetRegions []uint32 `json:"target_regions"`
}
type pidUnitInfo struct {
Pid2Info map[uint64]*pidInfo
}
func build() error {
content, _ := ioutil.ReadFile("./abcd")
json, err := sjson.NewJson(content)
if err != nil {
return err
}
newPidUnitInfo(json)
return nil
}
func newPidUnitInfo(json *sjson.Json) (*pidUnitInfo, error) {
newInfo := new(pidUnitInfo)
newInfo.buildPid2Info(json)
return nil, nil
}
func (pui *pidUnitInfo) buildPid2Info(json *sjson.Json) error {
raw, ok := json.CheckGet("pids")
if !ok {
return fmt.Errorf("not found json key %v", "pids")
}
pids, err := raw.Array()
if err != nil {
return err
}
pui.Pid2Info = make(map[uint64]*pidInfo, len(pids))
for _, v := range pids {
fmt.Println(v)
m := &sjson.Json{v}
fmt.Println(m)
}
return nil
}
func main() {
build()
}
When I execute it, show implicit assignment of unexported field 'data' in simplejson.Json literal at this line m := &sjson.Json{v}.
This line:
m := &sjson.Json{v}
Tries to create a value (and take the address) of the struct type Json from package go-simplejson. The type declaration is:
type Json struct {
data interface{}
}
It has one field: data which is unexported. That means packages other than go-simplejson cannot refer to this field. When you use a struct literal &sjson.Json{v}, it would try to initialize the Json.data field with value v which is a violation of this. You cannot do this.
The Json type is not designed for you to specify the internal data, it is designed so that the data will be the placeholder of some decoded JSON data (see the NewFromReader() and NewJson() constructor-like functions).
This data field is handled internally by the go-simplejson package, you cannot set it yourself. You may use sjson.New() to obtain a new *Json value which will initialize this data field with an empty map (map[string]interface{}). You may also use Json.Map() method which asserts that data is a map and returns it like that:
js := sjson.New()
m, err := js.Map()
if err != nil {
// Handle error
}
// Now you have a map of type map[string]interface{}
Or to populate the data inside a Json, you can use its Json.Set() method.
Let's say I have the following Go struct on the server
type account struct {
Name string
Balance int
}
I want to call json.Decode on the incoming request to parse it into an account.
var ac account
err := json.NewDecoder(r.Body).Decode(&ac)
If the client sends the following request:
{
"name": "test#example.com",
"balance": "3"
}
Decode() will return the following error:
json: cannot unmarshal string into Go value of type int
Now it's possible to parse that back into "you sent a string for Balance, but you really should have sent an integer", but it's tricky, because you don't know the field name. It also gets a lot trickier if you have a lot of fields in the request - you don't know which one failed to parse.
What's the best way to take that incoming request, in Go, and return the error message, "Balance must be a string", for any arbitrary number of integer fields on a request?
You can use a custom type with custom unmarshaling algorythm for your "Balance" field.
Now there are two possibilities:
Handle both types:
package main
import (
"encoding/json"
"fmt"
"strconv"
)
type Int int
type account struct {
Name string
Balance Int
}
func (i *Int) UnmarshalJSON(b []byte) (err error) {
var s string
err = json.Unmarshal(b, &s)
if err == nil {
var n int
n, err = strconv.Atoi(s)
if err != nil {
return
}
*i = Int(n)
return
}
var n int
err = json.Unmarshal(b, &n)
if err == nil {
*i = Int(n)
}
return
}
func main() {
for _, in := range [...]string{
`{"Name": "foo", "Balance": 42}`,
`{"Name": "foo", "Balance": "111"}`,
} {
var a account
err := json.Unmarshal([]byte(in), &a)
if err != nil {
fmt.Printf("Error decoding JSON: %v\n", err)
} else {
fmt.Printf("Decoded OK: %v\n", a)
}
}
}
Playground link.
Handle only a numeric type, and fail anything else with a sensible error:
package main
import (
"encoding/json"
"fmt"
)
type Int int
type account struct {
Name string
Balance Int
}
type FormatError struct {
Want string
Got string
Offset int64
}
func (fe *FormatError) Error() string {
return fmt.Sprintf("Invalid data format at %d: want: %s, got: %s",
fe.Offset, fe.Want, fe.Got)
}
func (i *Int) UnmarshalJSON(b []byte) (err error) {
var n int
err = json.Unmarshal(b, &n)
if err == nil {
*i = Int(n)
return
}
if ute, ok := err.(*json.UnmarshalTypeError); ok {
err = &FormatError{
Want: "number",
Got: ute.Value,
Offset: ute.Offset,
}
}
return
}
func main() {
for _, in := range [...]string{
`{"Name": "foo", "Balance": 42}`,
`{"Name": "foo", "Balance": "111"}`,
} {
var a account
err := json.Unmarshal([]byte(in), &a)
if err != nil {
fmt.Printf("Error decoding JSON: %#v\n", err)
fmt.Printf("Error decoding JSON: %v\n", err)
} else {
fmt.Printf("Decoded OK: %v\n", a)
}
}
}
Playground link.
There is a third possibility: write custom unmarshaler for the whole account type, but it requires more involved code because you'd need to actually iterate over the input JSON data using the methods of the
encoding/json.Decoder type.
After reading your
What's the best way to take that incoming request, in Go, and return the error message, "Balance must be a string", for any arbitrary number of integer fields on a request?
more carefully, I admit having a custom parser for the whole type is the only sensible possibility unless you are OK with a 3rd-party package implementing a parser supporting validation via JSON schema (I think I'd look at this first as juju is a quite established product).
A solution for this could be to use a type assertion by using a map to unmarshal the JSON data into:
type account struct {
Name string
Balance int
}
var str = `{
"name": "test#example.com",
"balance": "3"
}`
func main() {
var testing = map[string]interface{}{}
err := json.Unmarshal([]byte(str), &testing)
if err != nil {
fmt.Println(err)
}
val, ok := testing["balance"]
if !ok {
fmt.Println("missing field balance")
return
}
nv, ok := val.(float64)
if !ok {
fmt.Println("balance should be a number")
return
}
fmt.Printf("%+v\n", nv)
}
See http://play.golang.org/p/iV7Qa1RrQZ
The type assertion here is done using float64 because it is the default number type supported by Go's JSON decoder.
It should be noted that this use of interface{} is probably not worth the trouble.
The UnmarshalTypeError (https://golang.org/pkg/encoding/json/#UnmarshalFieldError) contains an Offset field that could allow retrieving the contents of the JSON data that triggered the error.
You could for example return a message of the sort:
cannot unmarshal string into Go value of type int near `"balance": "3"`
It would seem that here provides an implementation to work around this issue in Go only.
type account struct {
Name string
Balance int `json:",string"`
}
In my estimation the more correct and sustainable approach is for you to create a client library in something like JavaScript and publish it into the NPM registry for others to use (private repository would work the same way). By providing this library you can tailor the API for the consumers in a meaningful way and prevent errors creeping into your main program.
I'm playing with Go and am stumped as to why json encode and decode don't work for me
I think i copied the examples almost verbatim, but the output says both marshal and unmarshal return no data. They also don't give an error.
can anyone hint to where i'm going wrong?
my sample code: Go playground
package main
import "fmt"
import "encoding/json"
type testStruct struct {
clip string `json:"clip"`
}
func main() {
//unmarshal test
var testJson = "{\"clip\":\"test\"}"
var t testStruct
var jsonData = []byte(testJson)
err := json.Unmarshal(jsonData, &t)
if err != nil {
fmt.Printf("There was an error decoding the json. err = %s", err)
return
}
fmt.Printf("contents of decoded json is: %#v\r\n", t)
//marshal test
t.clip = "test2"
data, err := json.Marshal(&t)
if err != nil {
fmt.Printf("There was an error encoding the json. err = %s", err)
return
}
fmt.Printf("encoded json = %s\r\n", string(data))
}
output:
contents of decoded json is: main.testStruct{clip:""}
encoded json = {}
in both outputs I would have expected to see the decoded or encoded json
For example,
package main
import "fmt"
import "encoding/json"
type testStruct struct {
Clip string `json:"clip"`
}
func main() {
//unmarshal test
var testJson = "{\"clip\":\"test\"}"
var t testStruct
var jsonData = []byte(testJson)
err := json.Unmarshal(jsonData, &t)
if err != nil {
fmt.Printf("There was an error decoding the json. err = %s", err)
return
}
fmt.Printf("contents of decoded json is: %#v\r\n", t)
//marshal test
t.Clip = "test2"
data, err := json.Marshal(&t)
if err != nil {
fmt.Printf("There was an error encoding the json. err = %s", err)
return
}
fmt.Printf("encoded json = %s\r\n", string(data))
}
Output:
contents of decoded json is: main.testStruct{Clip:"test"}
encoded json = {"clip":"test2"}
Playground:
http://play.golang.org/p/3XaVougMTE
Export the struct fields.
type testStruct struct {
Clip string `json:"clip"`
}
Exported identifiers
An identifier may be exported to permit access to it from another
package. An identifier is exported if both:
the first character of the identifier's name is a Unicode upper case letter (Unicode class "Lu"); and
the identifier is declared in the package block or it is a field name or method name.
All other identifiers are not exported.
Capitalize names of structure fields
type testStruct struct {
clip string `json:"clip"` // Wrong. Lowercase - other packages can't access it
}
Change to:
type testStruct struct {
Clip string `json:"clip"`
}
In my case, my struct fields were capitalized but I was still getting the same error.
Then I noticed that the casing of my fields was different. I had to use underscores in my request.
For eg:
My request body was:
{
"method": "register",
"userInfo": {
"fullname": "Karan",
"email": "email#email.com",
"password": "random"
}
}
But my golang struct was:
type AuthRequest struct {
Method string `json:"method,omitempty"`
UserInfo UserInfo `json:"user_info,omitempty"`
}
I solved this by modifying my request body to:
{
"method": "register",
"user_info": {
"fullname": "Karan",
"email": "email#email.com",
"password": "random"
}
}