英文:
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)
}
输出:
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)
}
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)
}
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)
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论