使用Golang JSON编码,有没有更简单的方法在JSON对象上添加一个层级?

huangapple go评论75阅读模式
英文:

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

问题

Go语言中的开箱即用的JSON编码非常好,但我需要通过添加一层来使输出与特定格式匹配。我已经找到了一种方法,但希望有比我目前使用的方法更简单的方式。

以下是我目前的做法的示例代码:

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"`
}

// 使用fakeQuery来避免无限循环
type fakeQuery Query

// 以特殊方式为Query结构体编写MarshalJSON,添加包装{"query":...}
func (q Query) MarshalJSON() ([]byte, error) {
	return wrapJSON("query", fakeQuery(q))
}

// 使用fakeTermQuery来避免无限循环
type fakeTermQuery TermQuery

// 以特殊方式为TermQuery结构体编写MarshalJSON,添加包装{"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
}

我有很多定义的结构体需要进行这样的操作,所以我希望有一个更好的解决方案,不需要我写100多行代码来给JSON对象添加一个包装器。理想情况下,我希望能够查看为XML编码器定义的XML元素名称,并使用它来包装JSON。

在我的情况下,我使用MarshalJSON函数是因为这些结构体可以嵌套。如果有帮助的话,我总是知道Query结构体是根结构体。

英文:

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.

答案1

得分: 6

当我开始使用Go和Json时,我遇到了同样的问题。我通过以下方式解决了它:

func wrapJSON(name string, item interface{}) ([]byte, error) {
    wrapped := map[string]interface{}{
       name: item,
    }
    converted, err := json.Marshal(wrapped)
    return converted
}

理想情况下,将你的方法wrapJSON重命名为wrap,返回一个接口,然后将该接口转换为JSON或XML。

英文:

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

答案2

得分: 2

也许我漏掉了一些东西,但这是你要找的吗?

我开始使用与@Manawasp相同的想法(使用map[string]interface{}),但决定尝试从结构标签中获取名称,就像你所询问的那样...这是我想出的(注意:可能存在未处理的错误情况,并且这可能会过于复杂化,可以使用其他解决方案轻松处理)。

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))

}

输出

{"query":{"Field1":"hello","Field2":42}}
{"term-query":{"Field3":"world","Field4":99}}

编辑
好的,现在我知道你的问题是什么了。这可能有些丑陋,而且可能不是完全可靠的(错误处理等)...但是对于我的测试来说,它似乎做到了你想要的。

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
	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)))

}

漂亮打印的输出

{
"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
}
}
]
}
}
]
}
}
英文:

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 (
&quot;fmt&quot;
&quot;reflect&quot;
&quot;strings&quot;
)
import (
&quot;encoding/json&quot;
&quot;encoding/xml&quot;
&quot;errors&quot;
)
type Query struct {
XMLName xml.Name `xml:&quot;http://marklogic.com/appservices/search query&quot; json:&quot;-&quot;`
Field1  string
Field2  int64
}
type TermQuery struct {
XMLName xml.Name `xml:&quot;http://marklogic.com/appservices/search term-query&quot; json:&quot;-&quot;`
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(&quot;xml&quot;), &quot; &quot;)
return parts[1], true
}
return &quot;&quot;, false
}
func wrapJson(item interface{}) ([]byte, error) {
if n, ok := getXmlName(item, &quot;XMLName&quot;); ok {
b, err := json.Marshal(map[string]interface{}{n: item})
if err != nil {
return nil, err
}
return b, nil
}
return nil, errors.New(&quot;You failed&quot;)
}
func main() {
// create a Query and encode it as {&quot;query&quot;: {struct}}
q := Query{Field1: &quot;hello&quot;, Field2: 42}
wrappedQ, err := wrapJson(q)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(string(wrappedQ))
// create a TermQuery and encode it as {&quot;term-query&quot;: {struct}}
tq := TermQuery{Field3: &quot;world&quot;, Field4: 99}
wrappedTQ, err := wrapJson(tq)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(string(wrappedTQ))
}

OUTPUT

{&quot;query&quot;:{&quot;Field1&quot;:&quot;hello&quot;,&quot;Field2&quot;:42}}
{&quot;term-query&quot;:{&quot;Field3&quot;:&quot;world&quot;,&quot;Field4&quot;: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 (
&quot;fmt&quot;
&quot;reflect&quot;
&quot;strings&quot;
)
import (
//&quot;encoding/json&quot;
&quot;encoding/json&quot;
&quot;encoding/xml&quot;
&quot;errors&quot;
)
type Query struct {
XMLName xml.Name `xml:&quot;http://marklogic.com/appservices/search query&quot; json:&quot;-&quot;`
Field1  string
Field2  int64
Queries []interface{} `xml:&quot;,any&quot; json:&quot;queries&quot;`
}
type TermQuery struct {
XMLName xml.Name `xml:&quot;http://marklogic.com/appservices/search term-query&quot; json:&quot;-&quot;`
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(&quot;xml&quot;), &quot; &quot;)
return parts[1], true
default:
fmt.Println(reflect.TypeOf(d).Kind())
}
return &quot;&quot;, false
}
func wrapJson(item interface{}) (map[string]interface{}, error) {
if n, ok := getXmlName(item, &quot;XMLName&quot;); ok {
if k := reflect.ValueOf(item).FieldByName(&quot;Queries&quot;); k.IsValid() {
for i := 0; i &lt; 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(&quot;You failed&quot;)
}
func asJson(i interface{}) []byte {
b, err := json.Marshal(i)
if err != nil {
return []byte(`{&quot;error&quot;: &quot;too bad&quot;}`)
}
return b
}
func main() {
// create a TermQuery and encode it as {&quot;term-query&quot;: {struct}}
tq := TermQuery{Field3: &quot;world&quot;, 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 {&quot;query&quot;: {struct}}
q := Query{
Field1: &quot;hello&quot;, 
Field2: 42, 
Queries: []interface{}{
TermQuery{Field3: &quot;world&quot;, Field4: 99},
TermQuery{Field3: &quot;yay, it works!&quot;, Field4: 666},
Query{
Field1: &quot;Hi&quot;,
Field2: 21,
Queries: []interface{}{
TermQuery{
Field3: &quot;omg&quot;,
Field4: 1,
},
},
},
},
}
wrappedQ, err := wrapJson(q)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(string(asJson(wrappedQ)))
}

PRETTY-PRINTED OUTOUT

{
&quot;query&quot;: {
&quot;Field1&quot;: &quot;hello&quot;,
&quot;Field2&quot;: 42,
&quot;queries&quot;: [
{
&quot;term-query&quot;: {
&quot;Field3&quot;: &quot;world&quot;,
&quot;Field4&quot;: 99
}
},
{
&quot;term-query&quot;: {
&quot;Field3&quot;: &quot;yay, it works!&quot;,
&quot;Field4&quot;: 666
}
},
{
&quot;query&quot;: {
&quot;Field1&quot;: &quot;Hi&quot;,
&quot;Field2&quot;: 21,
&quot;queries&quot;: [
{
&quot;term-query&quot;: {
&quot;Field3&quot;: &quot;omg&quot;,
&quot;Field4&quot;: 1
}
}
]
}
}
]
}
}

答案3

得分: 0

在你的JSON Marshaler中,你可以定义一个或多个(匿名)结构体,并添加所需的JSON标签进行包装。

由于你已经定义了JSON Marshaler,原始结构中定义的任何标签都可能被你的JSON Marshaler实现覆盖。

请参见Golang Playground

type Book struct {
	Title     string
	Author    string
	Language  string
	Publisher string
}

func (b Book) MarshalJSON() ([]byte, error) {
	type BookDetailJSON struct {
		Name      string `json:"Title"`
		Author    string
		Language  string `json:",omitempty"`
		Publisher string `json:"-"`
	}
	type BookJSON struct {
		Book BookDetailJSON `json:"Novel"`
	}

	return json.Marshal(BookJSON{BookDetailJSON{
		Name:      b.Title,
		Author:    b.Author,
		Language:  b.Language,
		Publisher: b.Publisher,
	}})
}
英文:

Inside your JSON Marshaler you can define one more more (anonymous) structs with the various JSON tags and add the desired wrapping.

Since you have already defined the JSON Marshaler any tags defined in the original struct might be overriden by your JSON Marshaler implementation.

See Golang Playground.

type Book struct {
	Title     string
	Author    string
	Language  string
	Publisher string
}

func (b Book) MarshalJSON() ([]byte, error) {
	type BookDetailJSON struct {
		Name      string `json:&quot;Title&quot;`
		Author    string
		Language  string `json:&quot;,omitempty&quot;`
		Publisher string `json:&quot;-&quot;`
	}
	type BookJSON struct {
		Book BookDetailJSON `json:&quot;Novel&quot;`
	}

	return json.Marshal(BookJSON{BookDetailJSON{
		Name:      b.Title,
		Author:    b.Author,
		Language:  b.Language,
		Publisher: b.Publisher,
	}})
}

答案4

得分: -1

类型 MultiMatch 结构体 {
查询 字符串 json:"query"
字段 []字符串 json:"fields"
}

func (m *MultiMatch) MarshalJSON() ([]字节, 错误) {
w := map[字符串]interface{}{}
w["multi_match"] = *m
返回 json.Marshal(w)
}

英文:
type MultiMatch struct {
Query              string   `json:&quot;query&quot;`
Fields             []string `json:&quot;fields&quot;`
}
func (m *MultiMatch) MarshalJSON() ([]byte, error) {
w := map[string]interface{}{}
w[&quot;multi_match&quot;] = *m
return json.Marshal(w)
}

huangapple
  • 本文由 发表于 2015年4月17日 10:15:40
  • 转载请务必保留本文链接:https://go.coder-hub.com/29689092.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定