1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151
|
package cjson
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"reflect"
"sort"
"strings"
)
/*
encodeCanonicalString is a helper function to canonicalize the passed string
according to the OLPC canonical JSON specification for strings (see
http://wiki.laptop.org/go/Canonical_JSON). String canonicalization consists of
escaping backslashes ("\") and double quotes (") and wrapping the resulting
string in double quotes (").
*/
func encodeCanonicalString(s string) string {
// Escape backslashes
s = strings.ReplaceAll(s, "\\", "\\\\")
// Escape double quotes
s = strings.ReplaceAll(s, "\"", "\\\"")
// Wrap with double quotes
return fmt.Sprintf("\"%s\"", s)
}
/*
encodeCanonical is a helper function to recursively canonicalize the passed
object according to the OLPC canonical JSON specification (see
http://wiki.laptop.org/go/Canonical_JSON) and write it to the passed
*bytes.Buffer. If canonicalization fails it returns an error.
*/
func encodeCanonical(obj interface{}, result *strings.Builder) (err error) {
switch objAsserted := obj.(type) {
case string:
result.WriteString(encodeCanonicalString(objAsserted))
case bool:
if objAsserted {
result.WriteString("true")
} else {
result.WriteString("false")
}
// The wrapping `EncodeCanonical` function decodes the passed json data with
// `decoder.UseNumber` so that any numeric value is stored as `json.Number`
// (instead of the default `float64`). This allows us to assert that it is a
// non-floating point number, which are the only numbers allowed by the used
// canonicalization specification.
case json.Number:
if _, err := objAsserted.Int64(); err != nil {
panic(fmt.Sprintf("Can't canonicalize floating point number '%s'",
objAsserted))
}
result.WriteString(objAsserted.String())
case nil:
result.WriteString("null")
// Canonicalize slice
case []interface{}:
result.WriteString("[")
for i, val := range objAsserted {
if err := encodeCanonical(val, result); err != nil {
return err
}
if i < (len(objAsserted) - 1) {
result.WriteString(",")
}
}
result.WriteString("]")
case map[string]interface{}:
result.WriteString("{")
// Make a list of keys
var mapKeys []string
for key := range objAsserted {
mapKeys = append(mapKeys, key)
}
// Sort keys
sort.Strings(mapKeys)
// Canonicalize map
for i, key := range mapKeys {
if err := encodeCanonical(key, result); err != nil {
return err
}
result.WriteString(":")
if err := encodeCanonical(objAsserted[key], result); err != nil {
return err
}
if i < (len(mapKeys) - 1) {
result.WriteString(",")
}
i++
}
result.WriteString("}")
default:
// We recover in a deferred function defined above
panic(fmt.Sprintf("Can't canonicalize '%s' of type '%s'",
objAsserted, reflect.TypeOf(objAsserted)))
}
return nil
}
/*
EncodeCanonical JSON canonicalizes the passed object and returns it as a byte
slice. It uses the OLPC canonical JSON specification (see
http://wiki.laptop.org/go/Canonical_JSON). If canonicalization fails the byte
slice is nil and the second return value contains the error.
*/
func EncodeCanonical(obj interface{}) (out []byte, err error) {
// We use panic if an error occurs and recover in a deferred function,
// which is always called before returning.
// There we set the error that is returned eventually.
defer func() {
if r := recover(); r != nil {
err = errors.New(r.(string))
}
}()
// FIXME: Terrible hack to turn the passed struct into a map, converting
// the struct's variable names to the json key names defined in the struct
data, err := json.Marshal(obj)
if err != nil {
return nil, err
}
var jsonMap interface{}
dec := json.NewDecoder(bytes.NewReader(data))
dec.UseNumber()
if err := dec.Decode(&jsonMap); err != nil {
return nil, err
}
// Create a buffer and write the canonicalized JSON bytes to it
var result strings.Builder
// Allocate output result buffer with the input size.
result.Grow(len(data))
// Recursively encode the jsonmap
if err := encodeCanonical(jsonMap, &result); err != nil {
return nil, err
}
return []byte(result.String()), nil
}
|