Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there an easier way to add a layer over a JSON object using Golang JSON Encoding?

Tags:

json

go

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.

like image 971
justdewit Avatar asked Apr 17 '15 02:04

justdewit


People also ask

What is JSON Unmarshal in Golang?

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.

How does JSON Unmarshal work?

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.

What is Unmarshalling in Golang?

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.

How does JSON handle data in Go?

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.


2 Answers

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

like image 70
Manawasp Avatar answered Oct 12 '22 03:10

Manawasp


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
                            }
                        }
                    ]
                }
            }
        ]
    }
}
like image 26
sberry Avatar answered Oct 12 '22 03:10

sberry