jman

package module
v0.4.3 Latest Latest
Warning

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

Go to latest
Published: Feb 17, 2026 License: MIT Imports: 11 Imported by: 0

README

                     ██╗███╗   ███╗ █████╗ ███╗   ██╗
                     ██║████╗ ████║██╔══██╗████╗  ██║
                     ██║██╔████╔██║███████║██╔██╗ ██║
                ██   ██║██║╚██╔╝██║██╔══██║██║╚██╗██║
                ╚█████╔╝██║ ╚═╝ ██║██║  ██║██║ ╚████║
                ╚════╝  ╚═╝     ╚═╝╚═╝  ╚═╝╚═╝  ╚═══╝

jman (json manipulator)

A lightweight Go library for working with and asserting JSON in tests.
Minimal, fast, idiomatic.


Table of Contents


Context

This package is intended for testing. There are many bits of syntactical sugar that make working with it more flexible and simpler to write. It gets around much of the type safety and uses testing.T.Fatalf() to fail tests when operations don't succeed, rather than returning errors to handle. Therefore it is really not intended for production where these safety measures help ensure correct code!

However in testing, these safety measures can be hindrances. The Design Philosophy of Jman is:

  • concise
  • flexible
  • clear

Quick Start

import "github.com/akaswenwilk/jman"

func TestExample(t *testing.T) {
    expected := jman.Obj{
        "foo": "bar",
        "baz": "$ANY",
        "id": "$UUID",
    }
    actual := `{"bas":123, "foo":"bar", "id": "d47422ff-683a-4077-b3eb-e06a99bc9b55"}`

    expected.Equal(t, actual, jman.WithMatchers(
            jman.NotEmpty("$ANY"),
            jman.IsUUID("$UUID"),
        ),
    )
}

API

Basic Models

jman is built around two structs: jman.Obj and jman.Arr, corresponding to map[string]any and []any. They then have helper methods to manipulate and compare with each other.

You can create instances directly or use the New function to create them from JSON strings, byte slices, or existing instances:

// Create directly
obj := jman.Obj{"key": "value"}
arr := jman.Arr{"item1", "item2"}

// Create from JSON string
obj := jman.New[jman.Obj](t, `{"key": "value"}`)
arr := jman.New[jman.Arr](t, `["item1", "item2"]`)

// Create from byte slice
obj := jman.New[jman.Obj](t, []byte(`{"key": "value"}`))

Whenever using any of the methods to generate, edit, fetch, or compare values, they will be "normalized". Since go by default will unmarshal into specific value (I.E. any number will be unmarshalled into a float), it can get pretty annoying to ensure that you have specified the correct value in an array or object. Therefore the normalization will ensure correctness of value. So instead of having to do this:

expected := jman.Obj{
    "numberKey": float64(123),
}
actual := `{"numberKey":123}`

err := expected.Equal(actual)

you can simply write:

expected := jman.Obj{
    "numberKey": 123,
}
actual := `{"numberKey":123}`

expected.Equal(t, actual)

and the conversion will happen via normalization.

Basic Equal

the main method for comparison is the Equal method present on both jman.Arr and jman.Obj

Equal takes a testing.T as the first parameter and calls t.Fatalf() if the comparison fails, making it ideal for use in tests. It can compare against other objects/arrays, but also against strings or byte slices. No need to add any extra steps to ensure that the expected and actual are both in the same format just to compare the values!

Error Messages.

Error messages are given in dot notation, always preceded by the base character of $

Each difference between two json models is listed with its full path and a hopefully clear and concise message of the differences. This can be seen with the following test:

func TestObj_Equal_Unequal_ManyObjectErrors(t *testing.T) {
	expected := jman.Obj{
		"key1": "value1",
		"key2": jman.Obj{
			"nestedKey1": "nestedValue1",
			"nestedKey2": 42,
			"nestedKey4": "unexpectedKey", // unexpected key
		},
	}
	actual := jman.Obj{
		"key3": "value3",
		"key2": jman.Obj{
			"nestedKey1": "nestedValue2", // different value1
			"nestedKey2": "notANumber",  // different TestNewObj_UnsupportedType
			"nestedKey3": true,           // unexpected key3
		},
	}

	// This will call t.Fatalf() with the error message:
	// expected.Equal(t, actual)
	// The error message would be:
$.key1 not found in actual
$.key3 unexpected key
$.key2.nestedKey4 not found in actual
$.key2.nestedKey3 unexpected key
$.key2.nestedKey1 expected "nestedValue1" - actual "nestedValue2"
$.key2.nestedKey2 expected 42 - actual "notANumber"
}
Options

In addition to giving flexibility on types for comparison, jman also aims to give flexibility on actual comparisons. For example, if a value can be dynamic, and it's important only that it exists and not specifically what value is present:

expected := jman.Obj{
    "id": "$SOME_ID"
}
actual := `{"id":"21493933-3338-450c-8113-62a35d2c1820"}`
expected.Equal(t, actual, jman.WithMatchers(
    jman.NotEmpty("$SOME_ID")
))

jman provides several matchers that can be used out of the box:

  • NotEmpty(placeholder string) - checks that whatever is present is not a zero value or nil
  • IsUUID(placeholder string) - checks that something matches a uuid regex
  • EqualMatcher[T any](placeholder string, expected T) - checks if a value deep equals what is received, useful for tests with dynamic values that need to be matched exactly
  • Custom(placeholder string, matcherFunc MatcherFunc) - for passing in a custom matcher function

the placeholder tells what value in the expected will be checked with the corresponding function from the matcher with the value from the actual.

You can also set default matchers that apply to all comparisons using WithDefaultMatchers():

defaultMatchers := jman.Matchers{
    jman.NotEmpty("$ANY"),
    jman.IsUUID("$UUID"),
}

expected.Equal(t, actual, jman.WithDefaultMatchers(defaultMatchers))

In addition to the matchers, there is also an option to ignore array ordering:

	expected := jman.Obj{
		"items": jman.Arr{"item1", "item2", "item3"},
	}
	actual := jman.Obj{
		"items": jman.Arr{"item3", "item1", "item2"},
	}

	expected.Equal(t, actual,
		jman.WithIgnoreArrayOrder("$.items"),
	)

there can be multiple paths passed in if multiple arrays should ignore order. Each path must follow the syntax of leading with $.

Helper Methods

Additionally, both jman.Arr and jman.Obj have a set of helper methods to make json manipulation easier:

Set(t T, path string, value any) sets the value in whatever path is given. The path is separated by dots and must begin with $. It calls t.Fatalf() if the operation fails. For example:

	data := jman.Obj{
		"key1": jman.Obj{
			"nestedKey1": jman.Arr{"item1", jman.Obj{"deepKey": "deepValue"}},
		},
	}
	data.Set(t, "$.key1.nestedKey1.1.deepKey", "newDeepValue")

if a key is not present in the path, it will create a new key, otherwise it will overwrite the existing value of that key.

in arrays, it will replace the item at index, but it will not add new items.

Get(t T, path string) any retrieves a value at the specified path. It calls t.Fatalf() if the path is invalid or the value cannot be retrieved:

	data := jman.Obj{
		"key1": jman.Obj{
			"nestedKey1": jman.Arr{"item1", jman.Obj{"deepKey": "deepValue"}},
		},
	}
	value := data.Get(t, "$.key1.nestedKey1.1.deepKey")

There are also typed getter methods that call t.Fatalf() if the type conversion fails:

  • GetString(t T, path string) string
  • GetNumber(t T, path string) float64
  • GetBool(t T, path string) bool
  • GetObject(t T, path string) Obj
  • GetArray(t T, path string) Arr
JSON Marshaling Methods

Both jman.Obj and jman.Arr can marshal JSON into outputs of either a string or byte slice:

Methods that use testing.T:

  • String(t T) string - calls t.Fatalf() if marshaling fails
  • Bytes(t T) []byte - calls t.Fatalf() if marshaling fails

Methods that panic:

  • MustString() string - panics if marshaling fails
  • MustBytes() []byte - panics if marshaling fails

These are convenience helpers for testing - again, not for production code.

Documentation

Overview

Package jman provides fast, minimal helpers for building, querying and asserting JSON objects (Obj) and arrays (Arr) in tests.

Core Types

  • Obj — JSON object implemented as `map[string]any`.
  • Arr — JSON array implemented as `[]any`.

Testing Interface

  • T — interface for testing, e.g. `*testing.T`. Only Implements Fatalf() method.

Both types satisfy the JSONEqual interface and can be created:

// Literal
user := jman.Obj{"id": 1, "name": "alice"}
tags := jman.Arr{"go", "test", 42}

// Parse / normalise
user := jman.New[jman.Obj](t, `{"id":1,"name":"alice"}`)
tags := jman.New[jman.Arr](t, []byte(`["go","test",42]`))

Path-based Getters & Setters

Paths use JSON-Path–like dot syntax and must start with `$`.

id := user.GetNumber(t, "$.id")
user.Set(t, "$.settings.theme", "dark")
tags.Set(t, "$.1", "unit")

There are diffferent getters for each type expected to be returned

Setter can set any type as a value, however the value will be normalized into either: bool, string, float64, Obj, or Arr.

Deep Equality

expected := jman.Obj{
    "id":         "{{uuid}}",
    "name":       "{{nonEmpty}}",
    "roles":      jman.Arr{"admin", "editor"},
    "addresses":  jman.Arr{
        jman.Obj{"street": "High", "no": 1},
        jman.Obj{"street": "Low",  "no": 9},
    },
}

actual := `{
    "id":"9b74c989-7cdf-41fa-9a49-5290f31e59d3",
    "name":"alice",
    "roles":["editor","admin"],
    "addresses":[
      {"street":"High","no":1},
      {"street":"Low","no":9}
    ]}`

expected.Equal(t, actual,
    jman.WithIgnoreArrayOrder("$.roles", "$.addresses"),
    jman.WithDefaultMatchers(jman.Matchers{
        jman.IsUUID("{{uuid}}"),
        jman.NotEmpty("{{nonEmpty}}"),
    }),
)

Inequality Report

For each difference the path and problem is returned, e.g.:

expected not equal to actual:
$.roles expected 2 items - got 3 items
$.name expected "alice" - got "bob"
$.extra unexpected key

Matchers

Matchers allow placeholders in the *expected* JSON that are resolved at comparison time:

jman.IsUUID("{{uuid}}")          // any valid UUID
jman.NotEmpty("{{nonEmpty}}")    // non-empty string/array/object
jman.EqualMatcher("{{id}}", 99)  // equals specific value

Write your own with `jman.Custom`. A placeholder is a string that when found in the expected as a value, will find the corresponding value in the actual JSON and compare it using the matcher.

Options

  • WithIgnoreArrayOrder(paths...) — compare arrays as sets for given paths.
  • WithDefaultMatchers(ms) — register Matchers once per comparison.

Index

Constants

This section is empty.

Variables

View Source
var (
	ErrJSONRead        = errors.New("error reading JSON file")
	ErrJSONParse       = errors.New("error parsing JSON data")
	ErrNormalize       = errors.New("error normalizing JSON data")
	ErrUnsupportedType = errors.New("unsupported type for JSON data, use either string or []byte")
)

Functions

func Equal added in v0.4.3

func Equal(t T, expected, actual any, optFuncs ...optsFunc)

Equal compares two JSON values where each value can be Obj, Arr, JSON string/bytes, or any value that can be marshaled into a JSON object/array.

func New

func New[E JSONEqual](t T, data any) E

New creates a new instance of type T from the provided data. The data can be a JSON string, a byte slice, or an instance of type T. It fails the test via t.Fatalf if data cannot be parsed or normalized into type T.

func NewFromFile added in v0.4.3

func NewFromFile[E JSONEqual](t T, path string) E

NewFromFile creates a new instance of type E from a JSON file path. It fails the test via t.Fatalf if the file can't be read or JSON can't be parsed.

func WithDefaultMatchers

func WithDefaultMatchers(matchers Matchers) optsFunc

WithDefaultMatchers allows you to set the default matchers for the comparison options. Useful if defining a set of matchers that should be used in most comparisons.

func WithIgnoreArrayOrder

func WithIgnoreArrayOrder(keys ...string) optsFunc

WithIgnoreArrayOrder allows you to specify keys for which the order of array elements should be ignored during comparison. each key should be a valid JSON path, and the order of elements in arrays at those paths will not be considered during comparison. the path must start with $. For ignoring order of the base array, use "$" as the key.

func WithMatchers

func WithMatchers(matchers ...Matcher) optsFunc

WithMatchers allows you to add matchers to the comparison options.

Types

type Arr

type Arr []any

Arr represents a JSON array. It implements the Equaler interface for deep equality checks.

func (Arr) Equal

func (a Arr) Equal(t T, other any, optFuncs ...optsFunc)

Equal checks if the Arr is equal to another value, which can be either a JSON string, byte slice, or another Arr. It uses the provided options to customize the equality check, such as ignoring array order. If the actual value is not a valid JSON array, it returns an error.

func (Arr) Get

func (a Arr) Get(t T, path string) any

Get retrieves a value from the Arr at the specified path. The path is a JSONPath-like string that specifies the location of the value. If the path is invalid or the value cannot be retrieved, it calls t.Fatal

func (Arr) GetArray added in v0.4.0

func (a Arr) GetArray(t T, path string) Arr

GetArray functions like Get but attempts to convert to Arr. Fails if the value at the path is not an array.

func (Arr) GetBool added in v0.4.0

func (a Arr) GetBool(t T, path string) bool

GetBool functions like Get but attempts to convert to bool. Fails if the value at the path is not a boolean.

func (Arr) GetNumber added in v0.4.0

func (a Arr) GetNumber(t T, path string) float64

GetNumber functions like Get but attempts to convert to float64. Fails if the value at the path is not a number.

func (Arr) GetObject added in v0.4.0

func (a Arr) GetObject(t T, path string) Obj

GetObject functions like Get but attempts to convert to Obj. Fails if the value at the path is not an object.

func (Arr) GetString added in v0.4.0

func (a Arr) GetString(t T, path string) string

GetString functions like Get but attempts to convert to string. Fails if the value at the path is not a string.

func (Arr) MustBytes

func (a Arr) MustBytes(t T) []byte

Bytes returns the JSON representation of the Arr as a byte slice. It fails if there is an error during marshaling.

func (Arr) Set

func (a Arr) Set(t T, path string, value any)

Set sets a value at the specified path in the Arr. The path is a JSONPath-like string that specifies the location of the value. If the path is invalid or the value cannot be set, it fails. After setting the value, it normalizes the Arr to ensure it is valid JSON. If normalization fails, it calls Fatalf on T. If successful, it updates the Arr in place. The value can be of any type, but it will be normalized to one of the supported JSON types: bool, string, float64, Obj, or Arr.

func (Arr) String added in v0.4.0

func (a Arr) String(t T) string

String returns the JSON representation of the Arr as a string. It fails if there is an error during marshaling.

func (*Arr) UnmarshalJSON

func (a *Arr) UnmarshalJSON(data []byte) error

UnmarshalJSON implements the json.Unmarshaler interface for Arr. Any items in the array of type []any or map[string]any will be converted to Arr or Obj, and other simple types will be converted to their corresponding types (bool, string, float64).

type JSONEqual

type JSONEqual interface {
	Equal(t T, other any, optFuncs ...optsFunc)
}

JSONEqual is an interface that defines a method for comparing JSON objects. It requires the implementation to provide an Equal method that checks interface equality between two JSON objects, allowing for custom options via OptsFunc.

type Matcher

type Matcher struct {
	Placeholder string
	MatcherFunc
}

Matcher represents a matcher that can be used to validate values against certain conditions. It contains a placeholder for identification and a function that defines the matching logic. It will be checked whenever a string value is encountered in the expected value. If a matcher with the same placeholder is found, it will be used to validate the actual value.

func Custom

func Custom(placeholder string, matcherFunc MatcherFunc) Matcher

Custom creates a matcher with a custom matching function.

func EqualMatcher

func EqualMatcher[T any](placeholder string, expected T) Matcher

EqualMatcher checks if the value matches the expected value.

func IsUUID

func IsUUID(placeholder string) Matcher

IsUUID creates a matcher that checks if the value is a valid UUID against a uuid regular expression.

func NotEmpty

func NotEmpty(placeholder string) Matcher

NotEmpty creates a matcher that checks if the value is not empty. a string must not be empty, cannot be nil. booleans can be either true or false. a number can be any number, including zero. an array must not be empty, cannot be nil. an object must not be empty, cannot be nil.

type MatcherFunc

type MatcherFunc func(v any) bool

MatcherFunc is a function type that defines the matching logic. It takes a value of any type and returns true if the value matches the expected condition, false otherwise. the value passed into the MatcherFunc can be of any type, including nil and is taken from the actual value in the JSON.

type Matchers

type Matchers []Matcher

Matchers is a collection of Matcher objects.

func (Matchers) FindByPlaceholder

func (m Matchers) FindByPlaceholder(p string) (Matcher, bool)

FindByPlaceholder searches for a Matcher by its Placeholder.

type Obj

type Obj map[string]any

Obj represents a JSON object. It implements the Equaler interface for deep equality checks.

func (Obj) Bytes added in v0.4.0

func (ob Obj) Bytes(t T) []byte

Bytes returns the JSON representation of the Obj as a byte slice. It fails if the marshaling fails.

func (Obj) Equal

func (ob Obj) Equal(t T, other any, optFuncs ...optsFunc)

Equal checks if the Obj is equal to another value, which can be either a JSON string, byte slice, or another Obj.

func (Obj) Get

func (o Obj) Get(t T, path string) any

Get retrieves a value from the Obj at the specified path. The path is a JSONPath-like string that specifies the location of the value. If the path is invalid or the value cannot be retrieved, it calls t.Fatal

func (Obj) GetArray added in v0.4.0

func (o Obj) GetArray(t T, path string) Arr

GetArray functions like Get but attempts to convert to Arr. Fails if the value at the path is not an array.

func (Obj) GetBool added in v0.4.0

func (o Obj) GetBool(t T, path string) bool

GetBool functions like Get but attempts to convert to bool. Fails if the value at the path is not a boolean.

func (Obj) GetNumber added in v0.4.0

func (o Obj) GetNumber(t T, path string) float64

GetNumber functions like Get but attempts to convert to float64. Fails if the value at the path is not a number.

func (Obj) GetObject added in v0.4.0

func (o Obj) GetObject(t T, path string) Obj

GetObject functions like Get but attempts to convert to Obj. Fails if the value at the path is not an object.

func (Obj) GetString added in v0.4.0

func (o Obj) GetString(t T, path string) string

GetString functions like Get but attempts to convert to string. Fails if the value at the path is not a string.

func (Obj) MustBytes

func (ob Obj) MustBytes() []byte

MustBytes returns the JSON representation of the Obj as a byte slice. It panics if the marshaling fails, which is useful for tests where you want to ensure the JSON is valid without handling errors.

func (Obj) MustString

func (ob Obj) MustString() string

MustString returns the JSON representation of the Obj as a string. It panics if the marshaling fails, which is useful for tests where you want to

func (Obj) Set

func (o Obj) Set(t T, path string, value any)

Set sets a value at the specified path in the Obj. The path is a JSONPath-like string that specifies the location of the value. If the path is invalid or the value cannot be set, it fails. After setting the value, it normalizes the Arr to ensure it is valid JSON. If normalization fails, it calls Fatalf on T. If successful, it updates the Arr in place. The value can be of any type, but it will be normalized to one of the supported JSON types: bool, string, float64, Obj, or Arr.

func (Obj) String added in v0.4.0

func (ob Obj) String(t T) string

String returns the JSON representation of the Obj as a string. It fails if the marshaling fails.

func (*Obj) UnmarshalJSON

func (o *Obj) UnmarshalJSON(data []byte) error

UnmarshalJSON implements the json.Unmarshaler interface for Obj. Any nested fields with values of type []any or map[string]any will be converted to Arr or Obj, and other simple types will be converted to their corresponding types (bool, string, float64).

type T added in v0.4.0

type T interface {
	Fatalf(msg string, args ...any)
}

Jump to

Keyboard shortcuts

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