使用 Golang 修改 Json 无需结构体

21
type Struct struct {
   Value  string `json:"value"`
   Value1 string `json:"value_one"`
   Nest   Nested `json:"nest"`
}

type Nested struct {
   Something string `json:"something"`
}

我希望能在不创建另一个结构体类型的情况下添加未在结构体定义中的元素。例如:
Struct.Extra1 = Nested{"yy"}
Struct.Nested.Extra2 = "zz"

这将导致

{
    "Value": "xx",
    "Value1": "xx",
    "Extra1": {
      "Something", "yy"
    },
    "Nest": {
      "Something": "xx",
      "Extra2": "zz"
    }
}

解决方案1: 我考虑添加omitempty来实现这一目标,但这会使结构体变得复杂。

type Struct struct {
   Value  string
   Value1 string
   Nest   Nested
   Extra1 Nested `json:"omitempty"`
}

type Nested struct {
   Something string
   Extra2 string `json:"omitempty"`
}

解决方案2:

myextras := make(map[string]interface{})
// get Struct.Nested in map[string]interface{} format
myextras = Struct.Nest
myextras["Extra2"] = "zz"

// get Struct in map[string]interface{} format
struct["Nest"] = myextras
struct["Extra1"] = Nested{"yy"}

// solves the problem with lots of type casting but doesn't support json tag naming

有没有更好的解决方案来添加嵌套元素,这些元素在struct数据类型中没有表示,并且支持json标记,可以用于输出给用户?
5个回答

21
如果有人对提供的解决方案不满意:
试试tidwall/sjson。它提供了快速编辑JSON的函数,无需定义任何结构。昨天它为我节省了很多时间:D
示例用法:
value, _ := sjson.Set(`{"name":{"last":"Anderson"}}`, "name.last", "Smith")
println(value)

// Output:
// {"name":{"last":"Smith"}}

1
好的回答。在Go中操作JSON通常非常繁琐,我们应该推广像这样有用的包。 - mukunda

10

基于这个答案:Can I use MarshalJSON to add arbitrary fields to a json encoding in golang?

你可以像这样做(演示:http://play.golang.org/p/dDiTwxhoNn):

package main

import (
    "encoding/json"
    "fmt"
    "log"
)

type Book struct {
    Title  string
    Author string

    // extra is used for additional dynamic element marshalling
    extra func() interface{}
}

type FakeBook Book

func (b *Book) SetExtra(fn func() interface{}) {
    b.extra = fn
}

func (b *Book) MarshalJSON() ([]byte, error) {
    if b.extra == nil {
        b.extra = func() interface{} { return *b }
    }

    return json.Marshal(b.extra())
}

func main() {
    ms := &Book{
        Title:  "Catch-22",
        Author: "Joseph Heller",
    }

    ms.SetExtra(func() interface{} {
        return struct {
            FakeBook
            Extra1 struct {
                Something string `json:"something"`
            } `json:"extra1"`
        }{
            FakeBook: FakeBook(*ms),
            Extra1: struct {
                Something string `json:"something"`
            }{
                Something: "yy",
            },
        }
    })

    out, err := json.MarshalIndent(ms, "", "  ")
    if err != nil {
        log.Fatalln(err)
    }
    fmt.Println(string(out))

    mb := &Book{
        Title:  "Vim-go",
        Author: "Fatih Arslan",
    }

    mb.SetExtra(func() interface{} {
        return struct {
            FakeBook
            Something string `json:"something"`
        }{
            FakeBook:  FakeBook(*mb),
            Something: "xx",
        }
    })

    out, err = json.MarshalIndent(mb, "", "  ")
    if err != nil {
        log.Fatalln(err)
    }
    fmt.Println(string(out))

    mc := &Book{
        Title:  "Another-Title",
        Author: "Fatih Arslan",
    }

    out, err = json.MarshalIndent(mc, "", "  ")
    if err != nil {
        log.Fatalln(err)
    }
    fmt.Println(string(out))
}

3
由于我的完全有效的编辑被管理员拒绝,理由是“应该是评论”(即使作为评论这很浪费空间)- 请注意,你不需要使用json:“-”来防止额外的序列化,因为它没有被导出。 - Nate Finch
1
感谢@NateFinch提供的信息,很有道理。我代表你进行编辑。 - Fatih Arslan

3

是的,有一种名为json.Raw的类型,它不是结构体而是[]byte。你可以在任何编组/解组方式中将其管理在结构体之外。

更新

使用任何方式即可动态编辑json字符串。

package main

import (
    "fmt"
    "encoding/json"
    "strings"
)

type Struct struct {
   Value  string `json:"value"`
   Value1 string `json:"value_one"`
   Nest   json.RawMessage`json:"nest"`
}

func main() {
    s := Struct{Value1: "struct string"}
    buf, _ := json.Marshal(s)
    fmt.Println(string(buf))
    s2 := strings.ReplaceAll(string(buf), "null", `{"extra2":{"anykey":3.1415926535}}`)
    fmt.Println(s2)
}

https://play.golang.org/p/lOCxJBs5iRJ


2
你能提供在json.Raw格式中添加“Extra2”的代码吗?那是棘手的部分。 - Thellimist

2

如果您不想安装额外的包,您可以使用以下代码更改或添加新值:

package main

import "fmt"
import "strings"
import "encoding/json"

func main() {
    s := []byte(`{ "level1a":"aaa", "level1b":{"level2a":"bbb", "level2b":{"level3":"aaa"} } }`)
    var j interface{}
    json.Unmarshal(s, &j)
    SetValueInJSON(j, "level1a", "new value 1a")
    SetValueInJSON(j, "level1b.level2a", "new value 2a")
    SetValueInJSON(j, "level1b.level2b.level3", "new value 3")
    SetValueInJSON(j, "level1b.level2c", "new key")
    s,_ = json.Marshal(j)
    fmt.Println(string(s))
    // result: {"level1a":"new value 1a","level1b":{"level2a":"new value 2a","level2b":{"level3":"new value 3"},"level2c":"new key"}}

}

func SetValueInJSON(iface interface{}, path string, value interface{}) interface{} {
    m := iface.(map[string]interface{})
    split := strings.Split(path, ".")
    for k, v := range m {
        if strings.EqualFold(k, split[0]) {
            if len(split) == 1 {
                m[k] = value
                return m
            }
            switch v.(type) {
            case map[string]interface{}:
                return SetValueInJSON(v, strings.Join(split[1:], "."), value)
            default:
                return m
            }
        }
    }
    // path not found -> create
    if len(split) == 1 {
        m[split[0]] = value
    } else {
        newMap := make(map[string]interface{})
        newMap[split[len(split)-1]] = value
        for i := len(split) - 2; i > 0; i-- {
            mTmp := make(map[string]interface{})
            mTmp[split[i]] = newMap
            newMap = mTmp
        }
        m[split[0]] = newMap
    }
    return m
}

这相当不错。如果嵌套对象中有一个切片,会发生什么情况?它可以解决这个确切的问题,只是我想知道是否存在更通用的解决方案/不同的场景。 - Jaspreet Singh

2

使用映射的方法是唯一明智的方式,其他任何方法(比如json.RawMessage字段)都需要额外的编组步骤。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接