Howl

this is where i come to cry.

>

Snowflake IDs, JSON and Go

June 2, 2016

I’ve recently started making a library for the Discord API. The discord API uses snowflake for generating high ID numbers, and for preventing integer overflow in various languages they wrap Snowflake IDs into a string. This can be in a simple manner overcome by Go by passing ,string to the JSON tag:

type MyJSON struct {
	ID uint64 `json:"id,string"`
}
var m MyJSON
json.Unmarshal([]byte(`{ "id": "1956019865191951" }`), &m)
fmt.Println(m.ID)
// Output: 1956019865191951

playground

Neat, isn’t it? So now you have solved your problem with that busty uint64 being wrapped into a string, by simply placing ,string in the tag of your struct field.

But have you really?

I happened to have to unmarshal an array of these Snowflake IDs. Here’s what I tried:

var into struct{ Els []uint64 `json:"els,string"` }
err := json.Unmarshal([]byte(`{"els":["1941", "918592", "9581958129", "5819235812"]}`), &into)
if err != nil {
	panic(err)
}
fmt.Println(into)
// Error: json: cannot unmarshal string into Go value of type uint64

playground

So, how can we fix it? Well, custom types of course! Here is a trivial (and unsafe) example of a Snowflake with the methods UnmarshalJSON and MarshalJSON

package main

import (
	"fmt"
	"strconv"
	"encoding/json"
)

func main() {
	var into struct{ Els []Snowflake `json:"els,string"` }
	err := json.Unmarshal([]byte(`{"els":["1941", "918592", "9581958129", "5819235812"]}`), &into)
	if err != nil {
		panic(err)
	}
	fmt.Println(into)
}

type Snowflake uint64

func (f *Snowflake) UnmarshalJSON(data []byte) error {
	i, err := strconv.ParseUint(string(data[1:len(data)-1]), 10, 64)
	if err != nil {
		return err
	}
	*f = Snowflake(i)
	return nil
}

func (f Snowflake) MarshalJSON() ([]byte, error) {
	return []byte(`"` + strconv.FormatUint(uint64(f), 10) + `"`), nil
}

playground

As you can see, this soulution plays pretty nicely, and requires very little code. I’ve wrapped the type Snowflake into a package, with adeguate tests, documentation, examples and an actually safe implementation of the unmarshaller. It now gets down to this!

© Morgan Bazalgette 2015-2017