140 lines
3.2 KiB
Go
140 lines
3.2 KiB
Go
|
package json
|
||
|
|
||
|
import (
|
||
|
"bytes"
|
||
|
"encoding/json"
|
||
|
"fmt"
|
||
|
"io"
|
||
|
)
|
||
|
|
||
|
// DiscardUnknownField discards unknown fields from decoder body.
|
||
|
// This function is useful while deserializing json body with additional
|
||
|
// unknown information that should be discarded.
|
||
|
func DiscardUnknownField(decoder *json.Decoder) error {
|
||
|
// This deliberately does not share logic with CollectUnknownField, even
|
||
|
// though it could, because if we were to delegate to that then we'd incur
|
||
|
// extra allocations and general memory usage.
|
||
|
v, err := decoder.Token()
|
||
|
if err == io.EOF {
|
||
|
return nil
|
||
|
}
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
if _, ok := v.(json.Delim); ok {
|
||
|
for decoder.More() {
|
||
|
err = DiscardUnknownField(decoder)
|
||
|
}
|
||
|
endToken, err := decoder.Token()
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
if _, ok := endToken.(json.Delim); !ok {
|
||
|
return fmt.Errorf("invalid JSON : expected json delimiter, found %T %v",
|
||
|
endToken, endToken)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// CollectUnknownField grabs the contents of unknown fields from the decoder body
|
||
|
// and returns them as a byte slice. This is useful for skipping unknown fields without
|
||
|
// completely discarding them.
|
||
|
func CollectUnknownField(decoder *json.Decoder) ([]byte, error) {
|
||
|
result, err := collectUnknownField(decoder)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
buff := bytes.NewBuffer(nil)
|
||
|
encoder := json.NewEncoder(buff)
|
||
|
|
||
|
if err := encoder.Encode(result); err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
return buff.Bytes(), nil
|
||
|
}
|
||
|
|
||
|
func collectUnknownField(decoder *json.Decoder) (interface{}, error) {
|
||
|
// Grab the initial value. This could either be a concrete value like a string or a a
|
||
|
// delimiter.
|
||
|
token, err := decoder.Token()
|
||
|
if err == io.EOF {
|
||
|
return nil, nil
|
||
|
}
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
// If it's an array or object, we'll need to recurse.
|
||
|
delim, ok := token.(json.Delim)
|
||
|
if ok {
|
||
|
var result interface{}
|
||
|
if delim == '{' {
|
||
|
result, err = collectUnknownObject(decoder)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
} else {
|
||
|
result, err = collectUnknownArray(decoder)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Discard the closing token. decoder.Token handles checking for matching delimiters
|
||
|
if _, err := decoder.Token(); err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
return result, nil
|
||
|
}
|
||
|
|
||
|
return token, nil
|
||
|
}
|
||
|
|
||
|
func collectUnknownArray(decoder *json.Decoder) ([]interface{}, error) {
|
||
|
// We need to create an empty array here instead of a nil array, since by getting
|
||
|
// into this function at all we necessarily have seen a non-nil list.
|
||
|
array := []interface{}{}
|
||
|
|
||
|
for decoder.More() {
|
||
|
value, err := collectUnknownField(decoder)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
array = append(array, value)
|
||
|
}
|
||
|
|
||
|
return array, nil
|
||
|
}
|
||
|
|
||
|
func collectUnknownObject(decoder *json.Decoder) (map[string]interface{}, error) {
|
||
|
object := make(map[string]interface{})
|
||
|
|
||
|
for decoder.More() {
|
||
|
key, err := collectUnknownField(decoder)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
// Keys have to be strings, which is particularly important as the encoder
|
||
|
// won't except a map with interface{} keys
|
||
|
stringKey, ok := key.(string)
|
||
|
if !ok {
|
||
|
return nil, fmt.Errorf("expected string key, found %T", key)
|
||
|
}
|
||
|
|
||
|
value, err := collectUnknownField(decoder)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
object[stringKey] = value
|
||
|
}
|
||
|
|
||
|
return object, nil
|
||
|
}
|