checkjson

package module
v0.0.0-...-c767585 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 18, 2021 License: MIT Imports: 8 Imported by: 1

README

Check that a JSON object's keys correspond to a struct's exported members or JSON tags.

ANNOUNCEMENTS

2021.08.18 - Merge in handling of `checkjson:"norecurse"` struct member tag.
2018.03.14 - Add ExistingJSONKeys()
2018.02.16 - Add test example of using go v1.10 (*Decoder)DisallowUnknownFields()
2017.02.13 - Handle "-" and "omitempty" JSON tags in struct definitions.
2017.02.08 - UnknownJSONKeys lists all JSON object keys that won't be decoded. 
2016.11.18 - MissingJSONKeys lists all struct members that won't be set by JSON object.

USAGE

https://godoc.org/github.com/clbanning/checkjson

	Example:
	
	data := `
	{
	   "elem1":"a simple element",
	   "elem2": {
	      "subelem":"something more complex", 
	      "notes":"take a look at this" }
	   "elem4":"extraneous" 
	}`

	type sub struct {
	   Subelem string `json:"subelem,omitempty"`
	   Another string `json:"another"`
	}
	type elem struct {
	   Elem1 string `json:"elem1"`
	   Elem2 sub    `json:"elem2"`
	   Elem3 bool   `json:"elem3"`
	}

	e := new(elem)
	result, _ := MissingJSONKeys([]byte(data), e)
	// result: [elem2.another elem3]

	result, _ = UnknownJSONKeys([]byte(data), e)
	// result: [elem2.notes elem4]
	// NOTE: using the stdlib json.Decoder with (*Decoder)DisallowUnknownFields() set
	//       will error on the first unknown key; it does not return a slice of all
	//       unknown keys - see: unknownfieldserr_test.go.

LIMITATION

This package does not support recursive struct definitions.

MOTIVATION

I make extensive use of JSON configuration files.  Sometimes the files are large or
complex and JSON keys can be prone to typos or case errors. The "encoding/json" decoder 
just ignores JSON keys that do not correspond to struct member names/tags; this can 
result in unexpected initialization errors or the failure to override defaults. 
The checkjson.Validate() function identifies JSON object keys that cannot be decoded 
to a member of the struct using the "encoding/json" package.

RELATED

There is a similar package for validating XML tags against structs in 
https://github.com/clbanning/checkxml.

Documentation

Overview

Package checkjson provides functions for checking JSON object keys against struct member names/tags to see if they will be decoded using encoding/json package.

There are several options: Validate returns an error on the first key:value pair that won't decode, and UnknownJSONKeys returns a slice of all the keys that won't be decoded.

A complementary function MissingJSONKeys provides a slice of struct members that won't be set by the JSON object using encoding/json.

NOTE: this package DOES NOT support recursive struct definitions.

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func ExistingJSONKeys

func ExistingJSONKeys(b []byte, val interface{}) ([]string, error)

ExistingJSONKeys returns a list of fields of the struct 'val' that WILL BE set by unmarshaling the JSON object. It is the complement of MissingJSONKeys. For nested structs, field labels are the dot-notation hierachical path for a JSON key. Specific struct fields can be igored when scanning the JSON object by declaring them using SetMembersToIgnore. (NOTE: JSON object keys and tags are treated as case insensitive, i.e., there is no distiction between "keylabel":"value" and "Keylabel":"value" and "keyLabel":"value".)

For embedded structs, both the field label for the embedded struct as well as the dot-notation label for that struct's fields are included in the list. Thus,

type Person struct {
   Name NameInfo
   Sex  string
}

type NameInfo struct {
   First, Middle, Last string
}

jobj := []byte(`{"name":{"first":"Jonnie","middle":"Q","last":"Public"},"sex":"unkown"}`)
p := Person{}

fields, _ := ExistingKeys(jobj, p)
fmt.Println(fields)  // prints: [Name Name.First Name.Middle Name.Last Sex]

Struct fields that have JSON tag "-" are never returned. Struct fields with the tag attribute "omitempty" will, by default NOT be returned unless the keys exist in the JSON object. If you want to know if "omitempty" struct fields are actually in the JSON object, then call IgnoreOmitEmptyTag(false) prior to using ExistingJSONKeys.

func IgnoreOmitemptyTag

func IgnoreOmitemptyTag(ok ...bool)

IgnoreOmitemptyTag determines whether a `json:",omitempty"` tag is recognized or not with respect to the JSON object. By default MissingJSONKeys will not include struct fields that are tagged with "omitempty" in the list of missing JSON keys. If the function is toggled or passed the optional argument 'false' then missing JSON keys may include those for struct fields with the 'omitempty' JSON tag.

Calling IgnoreOmitemptyTag with no arguments toggles the handling on/off. If the alternative argument is passed, then the argument value determines the "omitempty" handling behavior.

func MissingJSONKeys

func MissingJSONKeys(b []byte, val interface{}) ([]string, error)

MissingJSONKeys returns a list of fields of a struct that will NOT be set by unmarshaling the JSON object; rather, they will assume their default values. For nested structs, field labels are the dot-notation hierachical path for the missing JSON key. Specific struct fields can be igored when scanning the JSON object by declaring them using SetMembersToIgnore(). (NOTE: JSON object keys and tags are treated as case insensitive, i.e., there is no distiction between "keylabel":"value" and "Keylabel":"value" and "keyLabel":"value".)

By default keys in the JSON object that are associated with struct fields that have JSON tag "-" are ignored. If the "omitempty" attribute is included in the struct field tag they are by default also not included in the returned slice. IgnoreOmitemptyTag(false) can be called to override the handling of "omitempty" tags - this might be useful if you want to find the "omitempty" fields that are not set by decoding the JSON object.

If the struct has a member struct with `checkjson:"norecurse"` tag, then it is not scanned.

Example
// struct to which we want to decode JSON object
type test3 struct {
	Something string
	Else      string
}
type test2 struct {
	Why     string
	Not     string
	Another test3
}
type test struct {
	Ok   bool
	Why  string
	More test2
}

tv := test{}
data := []byte(`{"ok":true,"more":{"why":"again","another":{"else":"ok"}}}`)

mems, err := MissingJSONKeys(data, tv)
if err != nil {
	// handle error
}
fmt.Println("missing keys:", mems)
Output:

missing keys: [Why More.Not More.Another.Something]

func ReadJSONFile

func ReadJSONFile(file string) ([][]byte, error)

ReadJSONFile returns an array of the JSON objects in 'file'. The file can have comments outside of the JSON objects as well as comments embedded in the JSON objects if preceeded by the number, '#', symbol.

File "test.json":
	This file contains some test data for ReadJSONFile ...
	{
	  "author": "B. Dylan",
	  "title" : "Ballad of a Thin Man"  # one of my favorites
	}

Code:
	j, _ := ReadJSONFile("test.json")
	fmt.Println(string(j[0])) // prints: {"author":"B. Dylan","title":"Ballad of a Thin Man"}

func ReadJSONReader

func ReadJSONReader(r io.Reader) ([]byte, error)

ReadJSONReader returns the next JSON object from an io.Reader; it returns io.EOF if the Reader terminates. (See ReadJSONFile for notes on handling of embedded comments in JSON object.)

func ResolveJSONError

func ResolveJSONError(data []byte, err error) error

ResolveJSONError tries to augment json.Unmarshal syntax errors with the JSON context - key:... - and position when parsing stopped, if possible. (This is useful when errors occur when unmarshaling large JSON objects.)

func SetKeysToIgnore

func SetKeysToIgnore(s ...string)

SetKeysToIgnore maintains a list of JSON keys that should not be validated as exported struct fields. By default the JSON key "config" is not validated; it can be removed from the list by calling SetKeysToIgnore() with no arguments. The arguments are used as the list of keys to ignore and override the default. NOTE: keys are case insensitive - i.e., "key" == "Key" == "KEY".

A JSON object key that corresponds with a struct member that is defined with the JSON tag "-" will not be reported, since it is a valid key for the struct definiton, even if it won't be decoded by the Go stdlib.

Example
// struct to which we want to decode JSON object
type test2 struct {
	Maybe bool
}
type test struct {
	Ok  bool
	Why test2
}

tv := test{}
data := []byte(`{"ok":true, "why":{"maybe":true,"maybenot":false}, "not":"I don't know"}`)
SetKeysToIgnore("why.maybenot", "not")
defer SetKeysToIgnore("")

keys, err := UnknownJSONKeys(data, tv)
if err != nil {
	// handle error
}

fmt.Println("unknown keys:", keys)
Output:

unknown keys: []

func SetMembersToIgnore

func SetMembersToIgnore(s ...string)

SetMembersToIgnore creates a list of exported struct field names that should not be checked for as keys in the JSON object. For hierarchical struct members provide the full path for the member name using dot-notation. Calling SetMembersToIgnore with no arguments - SetMembersToIgnore() - clears the list.

Example
// struct to which we want to decode JSON object
type test3 struct {
	Something string
	Else      string
}
type test2 struct {
	Why     string
	Not     string
	Another test3
}
type test struct {
	Ok   bool
	Why  string
	More test2
}

data := []byte(`{"ok":true,"more":{"why":"again","another":{"else":"ok"}}}`)
SetMembersToIgnore("why", "more.not", "more.another.something")
defer SetMembersToIgnore()

tv := test{}
mems, err := MissingJSONKeys(data, tv)
if err != nil {
	// handle error
}
fmt.Println("missing keys:", mems)
Output:

missing keys: []

func UnknownJSONKeys

func UnknownJSONKeys(b []byte, val interface{}) ([]string, error)

UnknownJSONKeys returns a slice of the JSON object keys that will not be decoded to a member of 'val', which is of type struct. For nested JSON objects the keys are reported using dot-notation. (NOTE: JSON object keys and tags are treated as case insensitive, i.e., there is no distiction between "keylabel":"value" and "Keylabel":"value" and "keyLabel":"value".)

JSON object keys that may correspond with a struct member that is defined with the JSON tag "-" will not be included in the unknown key slice, since they are valid keys even though they won't be decoded by the Go stdlib.

NOTE: beginning with go v1.10, this is similar to setting (*Decoder)DisallowUnknownFields() for the stdlib json.Decoder; however, the stdlib stops and returns the first unknown key it encounters rather than a slice of all keys in the JSON object that will not be decoded. Also, the stdlib error message does not reference the unknown key with dot-notation; so if the error is deep in a JSON object it may be hard to locate. (NOTE: as of 3/5/19, change 145218, the stdlib now reports key using dot-notation, as here.)

Example
// struct to which we want to decode JSON object
type test2 struct {
	Maybe bool
}
type test struct {
	Ok  bool
	Why test2
}

tv := test{}
data := []byte(`{"ok":true, "why":{"maybe":true,"maybenot":false}, "not":"I don't know"}`)
defer SetKeysToIgnore("")

keys, err := UnknownJSONKeys(data, tv)
if err != nil {
	// handle error
}

fmt.Println("unknown keys:", keys)
Output:

unknown keys: [why.maybenot not]

func Validate

func Validate(b []byte, val interface{}) error

Validate scans a JSON object and returns an error when it encounters a key:value pair that will not decode to a member of the 'val' of type struct using the "encoding/json" package. (NOTE: JSON object keys and tags are treated as case insensitive, i.e., there is no distiction between "keylabel":"value" and "Keylabel":"value" and "keyLabel":"value".)

JSON object key that may correspond with a struct member that is defined with the JSON tag "-" will not be reported since it is a valid key even though it won't be decoded by the Go stdlib.

NOTE: result is similar to using (*Decoder)DisallowUnknownFields() in encoding/json lib; however, with Validate() error will provide route for for nested JSON object keys.

Types

This section is empty.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL