The out of the box JSON encoding in Go is really nice, but I need to get the output to match a certain format by adding a layer. I've figured out a way, but was hoping that there would be an easier way than the way I'm doing it.
Below is an example of how I'm doing it.
import (
"bytes"
"encoding/json"
"encoding/xml"
"fmt"
)
type Query struct {
XMLName xml.Name `xml:"http://marklogic.com/appservices/search query" json:"-"`
Format int `xml:"-" json:"-"`
Queries []interface{} `xml:",any" json:"queries"`
}
type TermQuery struct {
XMLName xml.Name `xml:"http://marklogic.com/appservices/search term-query" json:"-"`
Terms []string `xml:"http://marklogic.com/appservices/search text" json:"text"`
Weight float64 `xml:"http://marklogic.com/appservices/search weight,omitempty" json:"weight,omitempty"`
}
// use fakeQuery to avoid an infinite loop
type fakeQuery Query
//MarshalJSON for Query struct in a special way to add wraping {"query":...}
func (q Query) MarshalJSON() ([]byte, error) {
return wrapJSON(`query`, fakeQuery(q))
}
// use fakeTermQuery to avoid an infinite loop
type fakeTermQuery TermQuery
//MarshalJSON for TermQuery struct in a special way to add wraping {"term-query":...}
func (q TermQuery) MarshalJSON() ([]byte, error) {
return wrapJSON(`term-query`, fakeTermQuery(q))
}
func wrapJSON(name string, item interface{}) ([]byte, error) {
var buffer bytes.Buffer
b, err := json.Marshal(item)
buffer.Write([]byte(`{"`))
buffer.Write([]byte(name))
buffer.Write([]byte(`":`))
buffer.Write(b)
buffer.Write([]byte(`}`))
return buffer.Bytes(), err
}
I have a lot of defined structures that I would need to do this to, so I'm hoping for a better solution that won't leave me with with 100+ lines of code to just add a wrapper around the JSON object. Ideally I would like something that could peak at the XML element name defined for the XML encoder and use that to wrap the JSON.
In my case I'm using the MarshalJSON functions because these structures can be nested. If it helps I always know that the Query structure is the root structure.
To parse JSON, we use the Unmarshal() function in package encoding/json to unpack or decode the data from JSON to a struct. Syntax: func Unmarshal(data []byte, v interface{}) error. Unmarshal parses the JSON-encoded data and stores the result in the value pointed to by v.
To unmarshal a JSON array into a slice, Unmarshal resets the slice length to zero and then appends each element to the slice. As a special case, to unmarshal an empty JSON array into a slice, Unmarshal replaces the slice with a new empty slice.
Golang Unmarshal Unmarshal is the contrary of marshal. It allows you to convert byte data into the original data structure. In go, unmarshaling is handled by the json. Unmarshal() method.
Go's encoding/json package allows you to take advantage of this by defining a struct type to represent the JSON data. You can control how the data contained in the struct is translated using struct tags. In this section, you will update your program to use a struct type instead of a map type to generate your JSON data.
When I started to use Go & Json I had the same problem. I resolved it by that
func wrapJSON(name string, item interface{}) ([]byte, error) {
wrapped := map[string]interface{}{
name: item,
}
converted, err := json.Marshal(wrapped)
return converted
}
Ideally, rename your method wrapJSON
to wrap
that return an interface and after convert this interface to JSON or XML
Perhaps I am missing something, but is this what you are looking for?
I started off with the same idea as @Manawasp (using a map[string]interface{}) but decided to try to get the name from the struct tag like you asked about... here's what I came up with (*note: there may be unhandled error cases, and this may overcomplicate something that can be handled pretty easily with the other solution)
http://play.golang.org/p/qO6tDZjtXA
package main
import (
"fmt"
"reflect"
"strings"
)
import (
"encoding/json"
"encoding/xml"
"errors"
)
type Query struct {
XMLName xml.Name `xml:"http://marklogic.com/appservices/search query" json:"-"`
Field1 string
Field2 int64
}
type TermQuery struct {
XMLName xml.Name `xml:"http://marklogic.com/appservices/search term-query" json:"-"`
Field3 string
Field4 int64
}
func getXmlName(d interface{}, label string) (string, bool) {
switch reflect.TypeOf(d).Kind() {
case reflect.Struct:
v, _ := reflect.TypeOf(d).FieldByName(label)
parts := strings.Split(v.Tag.Get("xml"), " ")
return parts[1], true
}
return "", false
}
func wrapJson(item interface{}) ([]byte, error) {
if n, ok := getXmlName(item, "XMLName"); ok {
b, err := json.Marshal(map[string]interface{}{n: item})
if err != nil {
return nil, err
}
return b, nil
}
return nil, errors.New("You failed")
}
func main() {
// create a Query and encode it as {"query": {struct}}
q := Query{Field1: "hello", Field2: 42}
wrappedQ, err := wrapJson(q)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(string(wrappedQ))
// create a TermQuery and encode it as {"term-query": {struct}}
tq := TermQuery{Field3: "world", Field4: 99}
wrappedTQ, err := wrapJson(tq)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(string(wrappedTQ))
}
OUTPUT
{"query":{"Field1":"hello","Field2":42}}
{"term-query":{"Field3":"world","Field4":99}}
EDIT
Ok, here is an update now that I can see what your issue is. It might be ugly, and it might not be bullet-proof (error handling, etc)... but for my test it seems to do what you want.
http://play.golang.org/p/8MloLP3X4H
package main
import (
"fmt"
"reflect"
"strings"
)
import (
//"encoding/json"
"encoding/json"
"encoding/xml"
"errors"
)
type Query struct {
XMLName xml.Name `xml:"http://marklogic.com/appservices/search query" json:"-"`
Field1 string
Field2 int64
Queries []interface{} `xml:",any" json:"queries"`
}
type TermQuery struct {
XMLName xml.Name `xml:"http://marklogic.com/appservices/search term-query" json:"-"`
Field3 string
Field4 int64
}
func getXmlName(d interface{}, label string) (string, bool) {
switch reflect.TypeOf(d).Kind() {
case reflect.Struct:
v, _ := reflect.TypeOf(d).FieldByName(label)
parts := strings.Split(v.Tag.Get("xml"), " ")
return parts[1], true
default:
fmt.Println(reflect.TypeOf(d).Kind())
}
return "", false
}
func wrapJson(item interface{}) (map[string]interface{}, error) {
if n, ok := getXmlName(item, "XMLName"); ok {
if k := reflect.ValueOf(item).FieldByName("Queries"); k.IsValid() {
for i := 0; i < k.Len(); i++ {
b, err1 := wrapJson(k.Index(i).Interface())
if err1 != nil {
continue
}
k.Index(i).Set(reflect.ValueOf(b))
}
}
return map[string]interface{}{n: item}, nil
}
return nil, errors.New("You failed")
}
func asJson(i interface{}) []byte {
b, err := json.Marshal(i)
if err != nil {
return []byte(`{"error": "too bad"}`)
}
return b
}
func main() {
// create a TermQuery and encode it as {"term-query": {struct}}
tq := TermQuery{Field3: "world", Field4: 99}
wrappedTQ, err := wrapJson(tq)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(string(asJson(wrappedTQ)))
// create a Query and encode it as {"query": {struct}}
q := Query{
Field1: "hello",
Field2: 42,
Queries: []interface{}{
TermQuery{Field3: "world", Field4: 99},
TermQuery{Field3: "yay, it works!", Field4: 666},
Query{
Field1: "Hi",
Field2: 21,
Queries: []interface{}{
TermQuery{
Field3: "omg",
Field4: 1,
},
},
},
},
}
wrappedQ, err := wrapJson(q)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(string(asJson(wrappedQ)))
}
PRETTY-PRINTED OUTOUT
{
"query": {
"Field1": "hello",
"Field2": 42,
"queries": [
{
"term-query": {
"Field3": "world",
"Field4": 99
}
},
{
"term-query": {
"Field3": "yay, it works!",
"Field4": 666
}
},
{
"query": {
"Field1": "Hi",
"Field2": 21,
"queries": [
{
"term-query": {
"Field3": "omg",
"Field4": 1
}
}
]
}
}
]
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With