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?
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)
}
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
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With