| 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() |
| } |