jsonlogic

package module
v0.3.1 Latest Latest
Warning

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

Go to latest
Published: Apr 22, 2021 License: Apache-2.0 Imports: 9 Imported by: 0

README

Go Reference Go Report Card

jsonlogic

This package implements jsonlogic in Go. Please see godoc for details.

Documentation

Overview

Package jsonlogic is a Go implementation of JsonLogic as described at http://jsonlogic.com. All example content for all operations, as given on the web site, work as expected. The test suite provided at https://jsonlogic.com/tests.json passes unaltered.

var data lookup assumes the passed-in data structure is akin to that provided by a raw json.Unmarshal. Only the following types are supported:

primitives of type string, float64 or bool
map[string]interface{} // where interface{} is a compatible type
[]interface{} // where interface{} is a compatible type

We cannot currently query Go native structs, or maps/slices of other native Go types.

Incompatibilities may exist in support for JavaScript type coercion. Any incompatibilities found should be reported as bugs.

Example
fizzbuzz := `{ "if": [
								{"==": [ { "%": [ { "var": "i" }, 15 ] }, 0]},
								"fizzbuzz",

								{"==": [ { "%": [ { "var": "i" }, 3 ] }, 0]},
								"fizz",

								{"==": [ { "%": [ { "var": "i" }, 5 ] }, 0]},
								"buzz",

								{ "var": "i" }
							 ]}`

data := map[string]interface{}{"i": float64(20)}

var c Clause
err := json.Unmarshal([]byte(fizzbuzz), &c)
if err != nil {
	log.Fatalf("unmarshal failed, %v", err)
}

cf, err := Compile(&c)
if err != nil {
	log.Fatalf("compile failed, %v", err)
}

ctx := context.Background()
fmt.Println(cf(ctx, data))
Output:

buzz

Index

Examples

Constants

This section is empty.

Variables

View Source
var DefaultOps = OpsSet{
	// contains filtered or unexported fields
}

DefaultOps is the default set of operations as specified on the jsonlogic site.

Functions

func DottedRef

func DottedRef(data interface{}, ref interface{}) interface{}

DottedRef attempts to resolve a dotted reference into a Go type. Only map[string]interface{} is supported for now.

func IsDeepEqual

func IsDeepEqual(l, r interface{}) bool

IsDeepEqual is an equality check that will coerce values according to JavaScript rules, and compare map and array content..

func IsEqual

func IsEqual(l, r interface{}) bool

IsEqual is an exact equality check.

func IsSoftEqual

func IsSoftEqual(l, r interface{}) bool

IsSoftEqual is an equality check that will coerce values according to JavaScript rules.

func IsTrue

func IsTrue(i interface{}) bool

IsTrue implements the truthy/falsy semantics of JsonLogic as documented here: https://jsonlogic.com/truthy.html in addition, an emptry struct is considered true, as per jsonlogic JavaScript handling of "{}"

Types

type Argument

type Argument struct {
	Clause *Clause
	Value  interface{}
}

Argument represents any valid argument to a jsonlogic operator.

func (Argument) MarshalJSON

func (a Argument) MarshalJSON() ([]byte, error)

MarshalJSON implements json.Marshaler. It enforces rending of clause arguments as an array (even if there was just one non array argument in the original clause when unmarshaled).

func (*Argument) UnmarshalJSON

func (a *Argument) UnmarshalJSON(bs []byte) error

UnmarshalJSON implements json.Unmarshaler.

type Arguments

type Arguments []Argument

Arguments represents the list of arguments to a jsonlogic Clause.

func (*Arguments) UnmarshalJSON

func (args *Arguments) UnmarshalJSON(bs []byte) error

UnmarshalJSON implements json.Unmarshaler.

type Clause

type Clause struct {
	Operator  Operator
	Arguments Arguments
}

Clause represents a JsonLogic clause.

func (Clause) MarshalJSON

func (c Clause) MarshalJSON() ([]byte, error)

MarshalJSON implements json.Marshaler. It enforces rending of clause arguments as an array (even if there was just one non array argument in the original clause when unmarshaled).

func (*Clause) UnmarshalJSON

func (c *Clause) UnmarshalJSON(bs []byte) error

UnmarshalJSON parses JSON data as a JsonLogic Clause.

type ClauseFunc

type ClauseFunc func(ctx context.Context, data interface{}) interface{}

ClauseFunc takes input data, returns a result which could be any valid json type. JsonLogic seems to prefer returning null to returning any specific errors. The context argument is not used by any of the standard operations, but may be used by custom operations to provide rich functionality.

func BuildArgFunc

func BuildArgFunc(arg Argument, ops OpsSet) (ClauseFunc, error)

BuildArgFunc is a utility function for building new operations. It should be called once for each argument during compilation, and the resulting function should be call at execution time, to retrieve the calculated value of the argument.

func Compile

func Compile(c *Clause) (ClauseFunc, error)

Compile builds a ClauseFunc that will execute the provided rule against the data.

type Operator

type Operator struct {
	Name string
}

Operator represents a jsonlogic Operator.

type OpsSet

type OpsSet map[string]func(args Arguments, ops OpsSet) (ClauseFunc, error)

OpsSet operation names to a function that can build an instance of that operation.

Example

ExampleOpsSet demonstrates extending the set of available operations.

Operations are collected together in an OpSet that can be asked to compile a given clause. Each operator (op) supported by a given OpSet must implement a BuildArgFunc that compiles that operation and returns the resulting ClauseFunc that will execute the operation at evaluation time. The build function is usually devided into two stages.

At Compile time we check we have the correct number of arguments, and deal with errors and boundary conditions. We then call the BuildArgFunc for each argument that we would like to use at Evaluation time. The compile time stage then returns a closure over the arg functions we collect as a ClausFunc, that does the Exectution time evaluation.

At Execution time, we call the claus functions we collected in the closure to retrieve the result of the argument for this particular call, and return and answer as neccessary.

The following example implements a regular expression match operation.

package main

import (
	"context"
	"encoding/json"
	"fmt"
	"regexp"

	"github.com/QubitProducts/jsonlogic"
)

func init() {
}

// ExampleOpsSet demonstrates extending the set of available operations.
//
// Operations are collected together in an OpSet that can be asked to compile a
// given clause. Each operator (op) supported by a given OpSet must implement
// a BuildArgFunc that compiles that operation and returns the resulting
// ClauseFunc that will execute the operation at evaluation time. The build
// function is usually devided into two stages.
//
// At Compile time we check we have the correct number of arguments, and deal
// with errors and boundary conditions. We then call the BuildArgFunc for each
// argument that we would like to use at Evaluation time. The compile time stage
// then returns a closure over the arg functions we collect as a ClausFunc, that
// does the Exectution time evaluation.
//
// At Execution time, we call the claus functions we collected in the closure to
// retrieve the result of the argument for this particular call, and return
// and answer as neccessary.
//
// The following example implements a regular expression match operation.
func main() {
	buildMatchOp := func(args jsonlogic.Arguments, ops jsonlogic.OpsSet) (jsonlogic.ClauseFunc, error) {
		// We want two args, 1 for the data we are going to match, and one
		// for the regex itself.
		if len(args) < 2 {
			return func(ctx context.Context, data interface{}) interface{} {
				return false
			}, nil
		}

		// We build a function for the data the user
		// gives us to match against.
		lArg, err := jsonlogic.BuildArgFunc(args[0], ops)
		if err != nil {
			return nil, err
		}

		// We build a function for the second argument that
		// we hope will become a string to can compile to
		// a regexp.
		rArg, err := jsonlogic.BuildArgFunc(args[1], ops)
		if err != nil {
			return nil, err
		}

		return func(ctx context.Context, data interface{}) interface{} {
			// We evaluate the first argument, using
			// the data the user provided.
			lval := lArg(ctx, data)
			lstr, ok := lval.(string)
			if !ok {
				// We only match against strings, everything else
				// is false.
				return false
			}

			// We evaluate the second argument, using
			// the data the user provided.
			rval := rArg(ctx, data)
			rstr, ok := rval.(string)
			if !ok {
				// We can only build regexp out of strings.
				return false
			}

			// we compile oour string regexp (this could be cached).
			rx, err := regexp.Compile(rstr)
			if err != nil {
				return false // JsonLogic never errors, bad things return false
			}

			// Finally we call out string match and return the answer
			// we shoud on return string, float64, bool, []map[string]interface{},
			// []interface{} (where the interfaces only consist of the same set of
			// types.
			return rx.MatchString(lstr)
		}, nil
	}

	// Add our function to an OpSet
	ops := jsonlogic.DefaultOps
	ops["match"] = buildMatchOp

	cls := jsonlogic.Clause{}
	_ = json.Unmarshal([]byte(`{"match": [{"var":""},"this"]}`), &cls)

	cf, _ := ops.Compile(&cls)

	var tests = []interface{}{
		`this matches`,
		`that doesn't`,
		float64(1),
	}
	ctx := context.Background()
	for _, t := range tests {
		fmt.Printf("match(%#v) = %v\n", t, jsonlogic.IsTrue(cf(ctx, t)))
	}
}
Output:

match("this matches") = true
match("that doesn't") = false
match(1) = false

func (OpsSet) Compile

func (ops OpsSet) Compile(c *Clause) (ClauseFunc, error)

Compile compiles a given clause using the operation constructors in this OpsSet

Jump to

Keyboard shortcuts

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