blob: d21230ebc140a3985f863bdf7c75d61102f621c1 [file] [log] [blame]
package jsonutils
import (
"bytes"
"encoding/json"
"sort"
"strconv"
"time"
)
// Number is an int64 which may be unmarshaled from a JSON string.
type Number int64
// UnmarshalJSON parses data as an integer, whether data is a number or string.
func (n *Number) UnmarshalJSON(data []byte) error {
data = bytes.Trim(data, `"`)
num, err := strconv.ParseInt(string(data), 0, 64)
if err == nil {
*n = Number(num)
}
return err
}
// Time is a convenience type used for unmarshaling a time.Time from a JSON-
// encoded timestamp in microseconds.
type Time time.Time
// MarshalJSON encodes a time.Time as a JSON number of microseconds.
func (t *Time) MarshalJSON() ([]byte, error) {
ts := (*time.Time)(t).UnixNano() / int64(time.Microsecond)
return json.Marshal(ts)
}
// UnmarshalJSON parses a time.Time from a JSON number of microseconds.
func (t *Time) UnmarshalJSON(data []byte) error {
var timeN Number
if err := timeN.UnmarshalJSON(data); err != nil {
return err
}
*t = Time(time.Unix(0, int64(timeN)*int64(time.Microsecond)).UTC())
return nil
}
// MarshalStringMap turns the given string map into a []byte slice that is the JSON encoded version
// of that map. It will produce the same output as json.Marshal. This includes the keys being sorted
// lexicographically. Unlike json.Marshal, it does not return an error because the errors
// json.Marshal could return (e.g. for cyclic data) do not apply.
func MarshalStringMap(m map[string]string) (data []byte) {
if len(m) == 0 {
// This behavior matches json.Marshal
if m == nil {
return []byte("null")
}
return []byte("{}")
}
keyValues := make([]string, 0, len(m))
byteCount := 0
var buf bytes.Buffer
for k, v := range m {
buf.WriteRune('"')
buf.WriteString(k)
buf.WriteString(`":"`)
buf.WriteString(v)
buf.WriteRune('"')
keyValues = append(keyValues, buf.String())
byteCount += buf.Len()
buf.Reset()
}
// sort for determinism and to match the default impl
// We go with insertion sort unless there are a lot of values.
if len(keyValues) <= 30 {
for i := 0; i < len(keyValues); i++ {
for j := i; j > 0 && keyValues[j] < keyValues[j-1]; j-- {
keyValues[j], keyValues[j-1] = keyValues[j-1], keyValues[j]
}
}
} else {
sort.Strings(keyValues)
}
var result bytes.Buffer
// Need to account for an open and closed curly brace, and n-1 commas
result.Grow(2 + byteCount + len(keyValues) - 1)
result.WriteRune('{')
for i := range keyValues {
if i != 0 {
result.WriteRune(',')
}
result.WriteString(keyValues[i])
}
result.WriteRune('}')
return result.Bytes()
}