The Mystery Of JSON Conversion From Float64 To Int64
Working with JSON can sound simple & clear, you have some struct, you can change it to JSON โ A general unified language & back to your struct. Simple right? ๐
Well, yeah, but that's until you encounter some weird behavior from the `Marshal` / `Unmarshal` functions.
Problem ๐จ
It all started when I was trying to read the encoded payload from a JWT token, below is an example that demonstrates the issue
package main
import (
"encoding/json"
"fmt"
)
type User struct {
ID int64 `json:"id"`
PostIDs []int64 `json:"post_ids"`
}
func main() {
u := User{
ID: 1,
PostIDs: []int64{1, 2, 3},
}
b, err := json.Marshal(u)
if err != nil {
panic(err)
}
m := make(map[string]interface{})
if err = json.Unmarshal(b, &m); err != nil {
panic(err)
}
userID, ok := m["id"].(int64)
fmt.Printf("id: %d\nOk:%t\n", userID, ok)
fmt.Println() // spliter
postIDs, ok := m["id"].([]int64)
fmt.Printf("post_ids: %v\nOk:%t\n", postIDs, ok)
}Just marshaling and unmarshaling back out struct, so it's expected to return the same value!
Unfortunately, this didn't happen, the code above outputs
// Result id: 0 Ok:false post_ids: [] Ok:false
Debug ๐
Once I saw that output, I ๐ค the issue might be with type conversions, so I went to check what types does these interfaces have
fmt.Printf("id: %T\n", m["id"])
fmt.Printf("post_ids: %T\n", m["post_ids"])// Result
id: float64
post_ids: []interface {}
So as we can see, JSON has parsed out `int64` as `float64`, which lead to issues upon reading the data.
There is actually 2 ways to fix this issue
๐ Solution 01 (Hard way)
Use type assertions of float64, Notice that `[]interface{}` can't be mapped right away to `[]float64`, so we have to iterate each element and convert it
// Parse UserID
userID, _ := m["id"].(float64)
fmt.Printf("id: %f\n", userID)
fmt.Println() // splitter
// Parse PostIDs
postIDsArr, _ := m["post_ids"].([]interface{})
postIDs := make([]int64, len(postIDsArr))
for i, v := range postIDsArr {
// NOTICE: direct conversion to int64 won't work here!
id, _ := v.(float64)
postIDs[i] = int64(id)
}
fmt.Printf("post_ids: %v\n", postIDs)// Result id: 1.000000 post_ids: [1 2 3]
๐ Solution 02 (Easy way)
b, err = json.Marshal(m) // m = map[string]interface{}
if err != nil {
panic(err)
}
var u2 User
if err := json.Unmarshal(b, &u2); err != nil {
panic(err)
}
fmt.Println(u2.ID)
fmt.Println(u2.PostIDs)Of course, you might think, why should we even use Solution 01, isn't Solution 02 better?
Well it depends, you don't always want to create a struct to read a single attribute from a struct, so the correct answer is โ It depends!