Lock object during json.Marshal in Go

I wanted to add RLock / RUnlock to the structure when it was configured on json.
The example below shows what I'm trying to do. However, this does not work, because every json.Marshal , it runs the Object.MarshalJSON method, which 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) } 

Playground example

Output:

Marshalling Object
Marshalling Object
Marshalling Object
...

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

Is there a way to call json.Marshal (or at least the json package to handle the encoding) in the struct MarshalJSON method?

Bonus Question
Why does my program not freeze? Shouldn't the structure be locked when the JSON marshal is called recursively a second time?

+4
source share
2 answers

You can type alias in a recursive call. Here he is on Play .

The alias type (JObject) does not have a specific marshal function, so it does not recurs infinitely

 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) } 
+7
source

The simple answer: Your program crashes due to infinite recursion.

You called json.Marshal(o) , which will look like MarshalJSON() in your methods, but, unfortunately, you also called json.Marshal(o) in MarshalJSON() , which ultimately leads to endless recursion of the causes and use of system of memory

It is called a general newbie mistake , because your code will lead to infinite recursion.

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

Other 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's why go is trying to impose a stack size limit as a workaround

2 Simple Solutions

  • Use a different name
  • Do not 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

main package

 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

+1
source

Source: https://habr.com/ru/post/1497557/


All Articles