Published on

Custom JSON marshal/unmarshal

Authors

Problem

Using Go we can have some freedom to use custom data type and customize the way of data will be encoded or decoded. Today we will learn about reading custom data with unique behavior.

I have json string like below, the requirement is reading price number as integer even though in json representative stored as string. How to solve this?


_1
{"price":"6550","code":"ASII"}

Solution

In simple ways we have 2 solution:

  1. Using intermediate struct to marshal/unmarshal that act as data transfer object then transform to struct with price type int64.

_11
// marshal/unmarshal json using DtoPrice
_11
type DtoTransaction struct {
_11
Price string `json:"price"`
_11
Code string `json:"code"`
_11
}
_11
_11
// then convert to this struct
_11
type Transaction struct {
_11
Price int64
_11
Code string
_11
}

  1. Custom JSON marshal/unmarshal
  • Create custom type StrInt64 with type alias int64
  • Implement MarshalJSON and UnmarshalJSON to manage read string as int64 and vise versa.

Let's digging more in the code.

  • Function MarshalJSON is quite simple we need to convert as string instead put value directly
  • Function UnmarshalJSON have 3 steps
    • Unmarshall as string
    • Convert string to int64
    • Type casting from int64 to StrInt64
intstring.go
instring_test.go

_31
package customtype
_31
_31
import (
_31
"encoding/json"
_31
"fmt"
_31
"strconv"
_31
)
_31
_31
// StrInt64 is a custom type to easily parse integers in string format
_31
type StrInt64 int64
_31
_31
// MarshalJSON encodes the value into string
_31
func (i StrInt64) MarshalJSON() ([]byte, error) {
_31
return json.Marshal(fmt.Sprintf("%d", i))
_31
}
_31
_31
// UnmarshalJSON parse encoded string data into int64
_31
func (i *StrInt64) UnmarshalJSON(data []byte) (err error) {
_31
var str string
_31
if err := json.Unmarshal(data, &str); err != nil {
_31
return err
_31
}
_31
x, err := strconv.ParseInt(str, 10, 64)
_31
if err != nil {
_31
return err
_31
}
_31
_31
*i = StrInt64(x)
_31
_31
return nil
_31
}

Prove it yourself if the implementation above is correct at https://go.dev/play/p/cnx_KdH3e2n

main.go

_49
package main
_49
_49
import (
_49
"encoding/json"
_49
"fmt"
_49
"strconv"
_49
)
_49
_49
// StrInt64 is a custom type to easily parse integers in string format
_49
type StrInt64 int64
_49
_49
// MarshalJSON encodes the value into string
_49
func (i StrInt64) MarshalJSON() ([]byte, error) {
_49
return json.Marshal(fmt.Sprintf("%d", i))
_49
}
_49
_49
// UnmarshalJSON parse encoded string data into int64
_49
func (i *StrInt64) UnmarshalJSON(data []byte) (err error) {
_49
var str string
_49
if err := json.Unmarshal(data, &str); err != nil {
_49
return err
_49
}
_49
x, err := strconv.ParseInt(str, 10, 64)
_49
if err != nil {
_49
return err
_49
}
_49
_49
*i = StrInt64(x)
_49
_49
return nil
_49
}
_49
_49
type Transaction struct {
_49
Price StrInt64 `json:"price"`
_49
Code string `json:"code"`
_49
}
_49
_49
func main() {
_49
var t Transaction
_49
json.Unmarshal([]byte(`{"price":"6550","code":"ASII"}`), &t)
_49
t.Price = t.Price * 2
_49
fmt.Println(t)
_49
s, _ := json.Marshal(t)
_49
fmt.Println(string(s))
_49
}
_49
_49
// Output
_49
// {13100 ASII}
_49
// {"price":"13100","code":"ASII"}