Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Locking an object during json.Marshal in Go

Tags:

json

go

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?

like image 208
ANisus Avatar asked Aug 17 '13 09:08

ANisus


2 Answers

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)
}
like image 164
David Budworth Avatar answered Oct 22 '22 12:10

David Budworth


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

like image 1
Baba Avatar answered Oct 22 '22 12:10

Baba