在Go语言中,在进行json.Marshal操作期间锁定一个对象。

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

Locking an object during json.Marshal in Go

问题

我想在将结构体编组为JSON时,向其添加RLock/RUnlock。下面的示例展示了我尝试的方法。然而,它不起作用,因为每次调用json.Marshal时,都会运行Object.MarshalJSON方法,而该方法本身又调用了json.Marshal,导致无限循环。

示例:

package main

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

type Object struct {
	Name string
	Value int
	
	sync.RWMutex
}

func (o *Object) MarshalJSON() ([]byte, error) {
	o.RLock()
	defer o.RUnlock()

	fmt.Println("Marshalling object")

	return json.Marshal(o)
}

func main() {
	o := &Object{Name: "ANisus", Value: 42}
	
	j, err := json.Marshal(o)
	if err != nil {
		panic(err)
	}
	fmt.Printf("%s\n", j)
}

Playground中的示例

输出:

Marshalling Object
Marshalling Object
Marshalling Object
...

显然,我可以删除MarshalJSON方法,并在调用json.Marshal之前在main函数中调用Lock()。但是,我的问题是:

是否有办法在结构体的MarshalJSON方法中调用json.Marshal(或至少让json包处理编码)?

附加问题
为什么我的程序不会冻结?当第二次递归调用MarshalJSON时,结构体不应该被锁定吗?

英文:

I wanted to add a RLock/RUnlock to a struct when it was being marshalled into json.
The example below shows what I try to do. However, it doesn't work because every json.Marshal is called, it will run the Object.MarshalJSON method, which in itself calls json.Marshal, causing an infinite loop.

Example:

package main

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

type Object struct {
	Name string
	Value int
	
	sync.RWMutex
}

func (o *Object) MarshalJSON() ([]byte, error) {
	o.RLock()
	defer o.RUnlock()

	fmt.Println("Marshalling object")

	return json.Marshal(o)
}

func main() {
	o := &Object{Name: "ANisus", Value: 42}
	
	j, err := json.Marshal(o)
	if err != nil {
		panic(err)
	}
	fmt.Printf("%s\n", j)
}

Example in Playground

Output:

> Marshalling Object
Marshalling Object
Marshalling Object
...

Obviously, I can delete the MarshalJSON method and call Lock() inside the main function, before calling json.Marshal. But, my question is rather:

Is there any way to call json.Marshal (or at least have the json package handle the encoding) within a struct's MarshalJSON method?

Bonus question
Why doesn't my program freeze? Shouldn't the struct be locked when MarshalJSON is called recursively the second time?

答案1

得分: 13

你可以在递归调用中给类型取一个别名。这里是在Play上的示例代码。

别名类型(JObject)没有定义marshal函数,因此不会无限递归。

package main

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

type Object struct {
    Name string
    Value int

    sync.RWMutex
}

// 递归调用的类型别名
type JObject Object

func (o *Object) MarshalJSON() ([]byte, error) {
    o.RLock()
    defer o.RUnlock()

    fmt.Println("Marshalling object")
    // 这样做是因为JObject没有与之关联的MarshalJSON函数
    return json.Marshal(JObject(*o)) 
}

func main() {
    o := &Object{Name: "ANisus", Value: 42}

    j, err := json.Marshal(o)
    if err != nil {
        panic(err)
    }
    fmt.Printf("%s\n", j)
}
英文:

You can alias the type on the recursive call. Here it is on Play.

The aliased type (JObject) doesn't have the marshal function defined, so it doesn't infinitely recurse

package main

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

type Object struct {
    Name string
    Value int

    sync.RWMutex
}

//Type alias for the recursive call
type JObject Object

func (o *Object) MarshalJSON() ([]byte, error) {
    o.RLock()
    defer o.RUnlock()

    fmt.Println("Marshalling object")
    // This works because JObject doesn't have a MarshalJSON function associated with it
    return json.Marshal(JObject(*o)) 
}

func main() {
    o := &Object{Name: "ANisus", Value: 42}

    j, err := json.Marshal(o)
    if err != nil {
        panic(err)
    }
    fmt.Printf("%s\n", j)
}

答案2

得分: 2

你的程序因为无限递归而冻结。

你调用了json.Marshal(o),它会在你的方法中查找MarshalJSON(),但不幸的是你还在MarshalJSON()中调用了json.Marshal(o),这最终导致了无限递归并耗尽系统内存。

这是一个常见的初学者错误,因为你的代码会导致无限递归。

以下是使用String()的简化版本的代码:

另一个递归示例:

package main

import "fmt"

type A int

func (a A) String() string {
	return fmt.Sprintf("%v", a)
}

func main() {
	var a A
	fmt.Println("this will never print", a)
}

这就是为什么Go语言试图限制堆栈大小作为临时解决方案。

两个简单的解决方案:

  • 使用另一个名称
  • 不返回return json.Marshal(o),而是返回Item

解决方案1示例:

package main

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

type Object struct {
	Name  string
	Value int

	sync.RWMutex
}

func (o *Object) ParseJSON() ([]byte, error) {
	o.RLock()
	defer o.RUnlock()

	fmt.Println("Marshalling object")

	return json.Marshal(o)
}

func main() {
	o := &Object{Name: "ANisus", Value: 42}

	j, err := o.ParseJSON() // 这将起作用
	if err != nil {
		panic(err)
	}
	fmt.Printf("%s\n", j)

	j, err = json.Marshal(o) // 这也将起作用
	if err != nil {
		panic(err)
	}
	fmt.Printf("%s\n", j)
}

在线演示

解决方案2示例:

package main

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

type Item struct {
	Name  string
	Value int
}

type Object struct {
	item Item
	sync.RWMutex
}

func (o *Object) MarshalJSON() ([]byte, error) {
	o.RLock()
	defer o.RUnlock()

	fmt.Println("Marshalling object")
	return json.Marshal(o.item)
}

func main() {
	o := &Object{item: Item{Name: "ANisus", Value: 42}}

	j, err := json.Marshal(o)
	if err != nil {
		panic(err)
	}
	fmt.Printf("%s\n", j)
}

在线演示

英文:

Simple Answer :
Your program freezes because of Infinite recursion.

You called json.Marshal(o) which would look MarshalJSON() in your methods but unfortunately you also called json.Marshal(o) in MarshalJSON() which ultimately leads to infinite cause recursion and using up the systems memory

Its called common rookie mistake because your code would lead to infinite recursion.

Here is a simpler version of your code using String()

Another recursion Example:

package main

import "fmt"

type A int

func (a A) String() string {
	return fmt.Sprintf("%v", a)
}

func main() {
	var a A
	fmt.Println("this will never print", a)
}

That is why go is trying to impose stack size limit as a temporal solution

2 Simple Solutions

  • Use another name
  • Don't return return json.Marshal(o) but Item

Solution 1 Example

package main

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

type Object struct {
	Name  string
	Value int

	sync.RWMutex
}

func (o *Object) ParseJSON() ([]byte, error) {
	o.RLock()
	defer o.RUnlock()

	fmt.Println("Marshalling object")

	return json.Marshal(o)
}

func main() {
	o := &Object{Name: "ANisus", Value: 42}

	j, err := o.ParseJSON() // THis would work
	if err != nil {
		panic(err)
	}
	fmt.Printf("%s\n", j)

	j, err = json.Marshal(o) // this would work
	if err != nil {
		panic(err)
	}
	fmt.Printf("%s\n", j)
}

Live Demo

Solution 2 Example

package main

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

type Item struct {
	Name  string
	Value int

}
type Object struct {
	item Item
	sync.RWMutex
}

func (o *Object) MarshalJSON() ([]byte, error) {
	o.RLock()
	defer o.RUnlock()

	fmt.Println("Marshalling object")
	return json.Marshal(o.item)
}

func main() {
	o := &Object{item : Item{Name: "ANisus", Value: 42}}

	j, err := json.Marshal(o)
	if err != nil {
		panic(err)
	}
	fmt.Printf("%s\n", j)
}

Live Demo

huangapple
  • 本文由 发表于 2013年8月17日 17:30:32
  • 转载请务必保留本文链接:https://go.coder-hub.com/18287242.html
匿名

发表评论

匿名网友

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

确定