Created
July 28, 2016 20:34
-
-
Save liclac/ad436338516cc4d9ce6fe734dd7c5c9d to your computer and use it in GitHub Desktop.
Finding unhandled keys on JSON objects in Go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package jsonkeys | |
import ( | |
"fmt" | |
"reflect" | |
"strings" | |
) | |
// Returns a list of keys a JSON representation of this object would have. | |
// Works with maps (any key type; converted to strings with fmt.Sprint()), or structs (respecting | |
// `json:"..."` tags). | |
func ListKeys(in interface{}) []string { | |
v := reflect.ValueOf(in) | |
switch v.Kind() { | |
case reflect.Map: | |
keys := make([]string, 0, v.Len()) | |
for _, keyV := range v.MapKeys() { | |
key := keyV.Interface() | |
keys = append(keys, fmt.Sprint(key)) | |
} | |
return keys | |
case reflect.Struct: | |
t := v.Type() | |
numField := t.NumField() | |
keys := make([]string, 0, numField) | |
for i := 0; i < numField; i++ { | |
f := t.Field(i) | |
// PkgPath is only set for unexported fields | |
if f.PkgPath != "" { | |
continue | |
} | |
name := f.Name | |
if jsonTag := f.Tag.Get("json"); jsonTag != "" { | |
nameEnd := strings.IndexRune(jsonTag, ',') | |
if nameEnd == -1 { | |
nameEnd = len(jsonTag) | |
} | |
name = jsonTag[:nameEnd] | |
} | |
keys = append(keys, name) | |
} | |
return keys | |
} | |
return nil | |
} | |
// Convenience function to list keys from ref that are missing in obj. | |
func MissingKeys(obj, ref interface{}) []string { | |
objKeyList := ListKeys(obj) | |
objKeys := make(map[string]interface{}, len(objKeyList)) | |
for _, key := range objKeyList { | |
objKeys[key] = nil | |
} | |
var missing []string | |
for _, key := range ListKeys(ref) { | |
if _, ok := objKeys[key]; !ok { | |
missing = append(missing, key) | |
} | |
} | |
return missing | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package jsonkeys | |
import ( | |
"testing" | |
) | |
func TestListKeysNil(t *testing.T) { | |
keys := ListKeys(nil) | |
if len(keys) != 0 { | |
t.Fail() | |
} | |
} | |
func TestListKeysMap(t *testing.T) { | |
keys := ListKeys(map[string]string{ | |
"a": "", | |
"b": "", | |
}) | |
var aFound, bFound bool | |
for _, key := range keys { | |
switch key { | |
case "a": | |
aFound = true | |
case "b": | |
bFound = true | |
default: | |
t.Fatalf("Unknown key: %s", key) | |
} | |
} | |
if !aFound { | |
t.Fatal("'a' not found") | |
} | |
if !bFound { | |
t.Fatal("'b' not found") | |
} | |
} | |
func TestListKeysStruct(t *testing.T) { | |
keys := ListKeys(struct{ A, B string }{}) | |
var aFound, bFound bool | |
for _, key := range keys { | |
switch key { | |
case "A": | |
aFound = true | |
case "B": | |
bFound = true | |
default: | |
t.Fatalf("Unknown key: %s", key) | |
} | |
} | |
if !aFound { | |
t.Fatal("'A' not found") | |
} | |
if !bFound { | |
t.Fatal("'B' not found") | |
} | |
} | |
func TestListKeysStructTags(t *testing.T) { | |
keys := ListKeys(struct { | |
A string `json:"a"` | |
B string `json:"b"` | |
}{}) | |
var aFound, bFound bool | |
for _, key := range keys { | |
switch key { | |
case "a": | |
aFound = true | |
case "b": | |
bFound = true | |
default: | |
t.Fatalf("Unknown key: %s", key) | |
} | |
} | |
if !aFound { | |
t.Fatal("'a' not found") | |
} | |
if !bFound { | |
t.Fatal("'b' not found") | |
} | |
} | |
func TestListKeysStructTagsOmitempty(t *testing.T) { | |
keys := ListKeys(struct { | |
A string `json:"a,omitempty"` | |
B string `json:"b,omitempty"` | |
}{}) | |
var aFound, bFound bool | |
for _, key := range keys { | |
switch key { | |
case "a": | |
aFound = true | |
case "b": | |
bFound = true | |
default: | |
t.Fatalf("Unknown key: %s", key) | |
} | |
} | |
if !aFound { | |
t.Fatal("'a' not found") | |
} | |
if !bFound { | |
t.Fatal("'b' not found") | |
} | |
} | |
func TestListKeysStructAnonymousFields(t *testing.T) { | |
keys := ListKeys(struct{ a, b string }{}) | |
if len(keys) != 0 { | |
t.Fail() | |
} | |
} | |
func TestMissingKeys(t *testing.T) { | |
obj := map[string]string{"a": "", "b": ""} | |
ref := map[string]string{"a": "", "b": "", "c": ""} | |
missing := MissingKeys(obj, ref) | |
if len(missing) != 1 || missing[0] != "c" { | |
t.Fatalf("Wrong: %s", missing) | |
} | |
} | |
func TestMissingKeysExtras(t *testing.T) { | |
obj := map[string]string{"a": "", "b": "", "c": ""} | |
ref := map[string]string{"a": "", "b": ""} | |
missing := MissingKeys(obj, ref) | |
if len(missing) != 0 { | |
t.Fatalf("Wrong: %s", missing) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment