SuperJSONOf

func SuperJSONOf(expectedJSON interface{}, params ...interface{}) TestDeep

SuperJSONOf operator allows to compare the JSON representation of data against expectedJSON. Unlike JSON operator, marshaled data must be a JSON object/map (aka {…}). expectedJSON can be a:

  • string containing JSON data like {"fullname":"Bob","age":42}
  • string containing a JSON filename, ending with “.json” (its content is ioutil.ReadFile before unmarshaling)
  • []byte containing JSON data
  • io.Reader stream containing JSON data (is ioutil.ReadAll before unmarshaling)

JSON data contained in expectedJSON must be a JSON object/map (aka {…}) too. During a match, each expected entry should match in the compared map. But some entries in the compared map may not be expected.

type MyStruct struct {
  Name string `json:"name"`
  Age  int    `json:"age"`
  City string `json:"city"`
}
got := MyStruct{
  Name: "Bob",
  Age:  42,
  City: "TestCity",
}
td.Cmp(t, got, td.SuperJSONOf(`{"name": "Bob", "age": 42}`))  // succeeds
td.Cmp(t, got, td.SuperJSONOf(`{"name": "Bob", "zip": 666}`)) // fails, miss "zip"

expectedJSON JSON value can contain placeholders. The params are for any placeholder parameters in expectedJSON. params can contain TestDeep operators as well as raw values. A placeholder can be numeric like $2 or named like $name and always references an item in params.

Numeric placeholders reference the n’th “operators” item (starting at 1). Named placeholders are used with Tag operator as follows:

td.Cmp(t, gotValue,
  SuperJSONOf(`{"fullname": $name, "age": $2, "gender": $3}`,
    td.Tag("name", td.HasPrefix("Foo")), // matches $1 and $name
    td.Between(41, 43),                  // matches only $2
    "male"))                             // matches only $3

Note that placeholders can be double-quoted as in:

td.Cmp(t, gotValue,
  td.SuperJSONOf(`{"fullname": "$name", "age": "$2", "gender": "$3"}`,
    td.Tag("name", td.HasPrefix("Foo")), // matches $1 and $name
    td.Between(41, 43),                  // matches only $2
    "male"))                             // matches only $3

It makes no difference whatever the underlying type of the replaced item is (= double quoting a placeholder matching a number is not a problem). It is just a matter of taste, double-quoting placeholders can be preferred when the JSON data has to conform to the JSON specification, like when used in a “.json” file.

SuperJSONOf does its best to convert back the JSON corresponding to a placeholder to the type of the placeholder or, if the placeholder is an operator, to the type behind the operator. Allowing to do things like:

td.Cmp(t, gotValue,
  td.SuperJSONOf(`{"foo":$1}`, []int{1, 2, 3, 4}))
td.Cmp(t, gotValue,
  td.SuperJSONOf(`{"foo":$1}`, []interface{}{1, 2, td.Between(2, 4), 4}))
td.Cmp(t, gotValue,
  td.SuperJSONOf(`{"foo":$1}`, td.Between(27, 32)))

Of course, it does this conversion only if the expected type can be guessed. In the case the conversion cannot occur, data is compared as is, in its freshly unmarshaled JSON form (so as bool, float64, string, []interface{}, map[string]interface{} or simply nil).

Note expectedJSON can be a []byte, JSON filename or io.Reader:

td.Cmp(t, gotValue, td.SuperJSONOf("file.json", td.Between(12, 34)))
td.Cmp(t, gotValue, td.SuperJSONOf([]byte(`[1, $1, 3]`), td.Between(12, 34)))
td.Cmp(t, gotValue, td.SuperJSONOf(osFile, td.Between(12, 34)))

A JSON filename ends with “.json”.

To avoid a legit “$” string prefix causes a bad placeholder error, just double it to escape it. Note it is only needed when the “$” is the first character of a string:

td.Cmp(t, gotValue,
  td.SuperJSONOf(`{"fullname": "$name", "details": "$$info", "age": $2}`,
    td.Tag("name", td.HasPrefix("Foo")), // matches $1 and $name
    td.Between(41, 43)))                 // matches only $2

For the “details” key, the raw value “$info” is expected, no placeholders are involved here.

Note that Lax mode is automatically enabled by SuperJSONOf operator to simplify numeric tests.

Comments can be embedded in JSON data:

td.Cmp(t, gotValue,
  td.SuperJSONOf(`
{
  // A guy properties:
  "fullname": "$name",  // The full name of the guy
  "details":  "$$info", // Literally "$info", thanks to "$" escape
  "age":      $2        /* The age of the guy:
                           - placeholder unquoted, but could be without
                             any change
                           - to demonstrate a multi-lines comment */
}`,
    td.Tag("name", td.HasPrefix("Foo")), // matches $1 and $name
    td.Between(41, 43)))                 // matches only $2

Comments, like in go, have 2 forms. To quote the Go language specification:

  • line comments start with the character sequence // and stop at the end of the line.
  • multi-lines comments start with the character sequence /* and stop with the first subsequent character sequence */.

Most operators can be directly embedded in SuperJSONOf without requiring any placeholder.

td.Cmp(t, gotValue,
  td.SuperJSONOf(`
{
  "fullname": HasPrefix("Foo"),
  "age":      Between(41, 43),
  "details":  SuperMapOf({
    "address": NotEmpty(),
    "car":     Any("Peugeot", "Tesla", "Jeep") // any of these
  })
}`))

Placeholders can be used anywhere, even in operators parameters as in:

td.Cmp(t, gotValue, td.SuperJSONOf(`{"fullname": HasPrefix($1)}`, "Zip"))

A few notes about operators embedding:

Operators taking no parameters can also be directly embedded in JSON data using $^OperatorName or “$^OperatorName” notation. They are named shortcut operators (they predate the above operators embedding but they subsist for compatibility):

td.Cmp(t, gotValue, td.SuperJSONOf(`{"id": $1}`, td.NotZero()))

can be written as:

td.Cmp(t, gotValue, td.SuperJSONOf(`{"id": $^NotZero}`))

or

td.Cmp(t, gotValue, td.SuperJSONOf(`{"id": "$^NotZero"}`))

As for placeholders, there is no differences between $^NotZero and “$^NotZero”.

The allowed shortcut operators follow:

TypeBehind method returns the map[string]interface{} type.

See also SuperJSONOf godoc.

Examples

Basic example
Placeholders example
File example

CmpSuperJSONOf shortcut

func CmpSuperJSONOf(t TestingT, got, expectedJSON interface{}, params []interface{}, args ...interface{}) bool

CmpSuperJSONOf is a shortcut for:

td.Cmp(t, got, td.SuperJSONOf(expectedJSON, params...), args...)

See above for details.

Returns true if the test is OK, false if it fails.

args… are optional and allow to name the test. This name is used in case of failure to qualify the test. If len(args) > 1 and the first item of args is a string and contains a ‘%’ rune then fmt.Fprintf is used to compose the name, else args are passed to fmt.Fprint. Do not forget it is the name of the test, not the reason of a potential failure.

See also CmpSuperJSONOf godoc.

Examples

Basic example
Placeholders example
File example

T.SuperJSONOf shortcut

func (t *T) SuperJSONOf(got, expectedJSON interface{}, params []interface{}, args ...interface{}) bool

SuperJSONOf is a shortcut for:

t.Cmp(got, td.SuperJSONOf(expectedJSON, params...), args...)

See above for details.

Returns true if the test is OK, false if it fails.

args… are optional and allow to name the test. This name is used in case of failure to qualify the test. If len(args) > 1 and the first item of args is a string and contains a ‘%’ rune then fmt.Fprintf is used to compose the name, else args are passed to fmt.Fprint. Do not forget it is the name of the test, not the reason of a potential failure.

See also T.SuperJSONOf godoc.

Examples

Basic example
Placeholders example
File example