How To Correctly Serialize JSON String In Golang

json is one of the most wildly used Go packages. It is simple and, what is more important, very intuitive. So what could be easier than marshalling a string with JSON and unmarshalling it to struct? If you believe (as I did) that the issue is trivial and json.Marshal does the job, read on.

What's wrong with json.Marshal?

It's easier to demonstrate on example. Let's write a simple program which serializes JSON string to bytes and deserializes the bytes into matching struct:

package main

import (  
    "encoding/json"
    "fmt"
)

type Person struct {  
    FirstName string `json:"firstName"`
    LastName  string `json:"lastName"`
}

func main() {  
    in := `{"firstName":"John","lastName":"Dow"}`

    bytes, err := json.Marshal(in)
    if err != nil {
        panic(err)
    }

    var p Person
    err = json.Unmarshal(bytes, &p)
    if err != nil {
        panic(err)
    }

    fmt.Printf("%+v", p)
}

When you run the program it will panic:

panic: json: cannot unmarshal string into Go value of type main.Person

goroutine 1 [running]:  
panic(0x1064c0, 0xc820014240)  
    /usr/local/go/src/runtime/panic.go:481 +0x3e6
main.main()  
    /Users/yury/projects/go/src/exp/main.go:24 +0x194

This is unexpected. Let's make sure that JSON string matches Person struct:

package main

import (  
    "encoding/json"
    "fmt"
)

type Person struct {  
    FirstName string `json:"firstName"`
    LastName  string `json:"lastName"`
}

func main() {  
    bytes, err := json.Marshal(Person{
        FirstName: "John",
        LastName:  "Dow",
    })
    if err != nil {
        panic(err)
    }

    fmt.Println(string(bytes))
}

Which outputs:

{"firstName":"John","lastName":"Dow"}

Serialized struct is identical to our JSON string.

What's going on?

Let's compare serialized JSON string with result of struct serialization:

package main

import (  
    "encoding/json"
    "fmt"
)

type Person struct {  
    FirstName string `json:"firstName"`
    LastName  string `json:"lastName"`
}

func main() {  
    bytes, err := json.Marshal(Person{
        FirstName: "John",
        LastName:  "Dow",
    })
    if err != nil {
        panic(err)
    }

    fmt.Println(string(bytes))

    in := `{"firstName":"John","lastName":"Dow"}`

    bytes, err = json.Marshal(in)
    if err != nil {
        panic(err)
    }

    fmt.Println(string(bytes))
}

The output reveals the truth:

{"firstName":"John","lastName":"Dow"}
"{\"firstName\":\"John\",\"lastName\":\"Dow\"}"

Can you see escaped double quotes? This is the crux of the problem. json.Marshal escapes string while serializing it.

Skipping escaping

json package includes a solution for the issue. It has RawMessage type, which Marshalls and Unmarshals without escaping. So if you need to serialize JSON string which you need to deserialize later into struct, do it with json.RawMessage:

package main

import (  
    "encoding/json"
    "fmt"
)

type Person struct {  
    FirstName string `json:"firstName"`
    LastName  string `json:"lastName"`
}

func main() {  
    in := `{"firstName":"John","lastName":"Dow"}`

    rawIn := json.RawMessage(in)
    bytes, err := rawIn.MarshalJSON()
    if err != nil {
        panic(err)
    }

    var p Person
    err = json.Unmarshal(bytes, &p)
    if err != nil {
        panic(err)
    }

    fmt.Printf("%+v", p)
}

Output as expected:

{FirstName:John LastName:Dow}
Is there a simpler way?

Yes. json.RawMessage is just an alias to []byte. So what you really need is to convert string into []byte:

package main

import (  
    "encoding/json"
    "fmt"
)

type Person struct {  
    FirstName string `json:"firstName"`
    LastName  string `json:"lastName"`
}

func main() {  
    in := `{"firstName":"John","lastName":"Dow"}`
    bytes := []byte(in)

    var p Person
    err := json.Unmarshal(bytes, &p)
    if err != nil {
        panic(err)
    }

    fmt.Printf("%+v", p)
}

Output is the same:

{FirstName:John LastName:Dow}
comments powered by Disqus