Skip to content

Instantly share code, notes, and snippets.

@liclac
Created July 28, 2016 20:34
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save liclac/ad436338516cc4d9ce6fe734dd7c5c9d to your computer and use it in GitHub Desktop.
Save liclac/ad436338516cc4d9ce6fe734dd7c5c9d to your computer and use it in GitHub Desktop.
Finding unhandled keys on JSON objects in Go
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
}
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