Json logic error

Build complex rules, serialize them as JSON, share them between front-end and back-end
  • Accessing Data
    • var
    • missing
    • missing_some
  • Logic and Boolean Operations
    • if
    • ==
    • ===
    • !=
    • !==
    • !
    • !!
    • or
    • and
  • Numeric Operations
    • >, >=, <, and <=
    • Between
    • max and min
    • Arithmetic, + - * /
    • %
  • Array Operations
    • map, reduce, and filter
    • all, none, and some
    • merge
    • in
  • String Operations
    • in
    • cat
    • substr
  • Miscellaneous
    • log

Accessing Data

var

Retrieve data from the provided data object.

Most JsonLogic rules operate on data supplied at run-time. Typically this data is an object, in which case the argument to var is a property name.

Note, every operation will be demonstrated with a live example box. Feel free to edit the logic and the data and see what happens when you apply your change! Here’s what the example above would look like in JavaScript:

jsonLogic.apply(
  { "var" : ["a"] }, // Logic
  { "a":1, "b":2 }   // Data
);
// 1

If you like, we support syntactic sugar to skip the array around single arguments :

You can supply a default, as the second argument, for values that might be missing in the data object. (Note, the skip-the-array sugar won’t work here because you’re passing two arguments to var):

The key passed to var can use dot-notation to get the property of a property (to any depth you need):

You can also use the var operator to access an array by numeric index:

Here’s a complex rule that mixes literals and data. The pie isn’t ready to eat unless it’s cooler than 110 degrees, and filled with apples.

You can also use var with an empty string to get the entire data object – which is really useful in map, filter, and reduce rules.

missing

Takes an array of data keys to search for (same format as var). Returns an array of any keys that are missing from the data object, or an empty array.

Note, in JsonLogic, empty arrays are falsy. So you can use missing with if like:

missing_some

Takes a minimum number of data keys that are required, and an array of keys to search for (same format as var or missing). Returns an empty array if the minimum is met, or an array of the missing keys otherwise.

This is useful if you’re using missing to track required fields, but occasionally need to require N of M fields.

Logic and Boolean Operations

if

The if statement typically takes 3 arguments: a condition (if), what to do if it’s true (then), and what to do if it’s false (else), like:

If can also take more than 3 arguments, and will pair up arguments like if/then elseif/then elseif/then else. Like:

See the Fizz Buzz implementation for a larger example.

==

Tests equality, with type coercion. Requires two arguments.

===

Tests strict equality. Requires two arguments.

!=

Tests not-equal, with type coercion.

!==

Tests strict not-equal.

!

Logical negation (“not”). Takes just one argument.

Note: unary operators can also take a single, non array argument:

!!

Double negation, or “cast to a boolean.” Takes a single argument.

Note that JsonLogic has its own spec for truthy to ensure that rules will run consistently across interpreters. (e.g., empty arrays are falsy, string “0” is truthy.)

or

or can be used for simple boolean tests, with 1 or more arguments.

At a more sophisticated level, or returns the first truthy argument, or the last argument.

and

and can be used for simple boolean tests, with 1 or more arguments.

At a more sophisticated level, and returns the first falsy argument, or the last argument.

Numeric Operations

>, >=, <, and <=

Greater than:

Greater than or equal to:

Less than:

Less than or equal to:

Between

You can use a special case of < and <= to test that one value is between two others:

Between exclusive:

Between inclusive:

This is most useful with data:

max and min

Return the maximum or minimum from a list of values.

Arithmetic, + - * /

Addition, subtraction, multiplication, and division.

Because addition and multiplication are associative, they happily take as many args as you want:

Passing just one argument to - returns its arithmetic negative (additive inverse).

Passing just one argument to + casts it to a number.

%

Modulo. Finds the remainder after the first argument is divided by the second argument.

This can be paired with a loop in the language that parses JsonLogic to create stripes or other effects.

In Javascript:

var rule = {"if": [{"%": [{"var":"i"}, 2]}, "odd", "even"]};
for(var i = 1; i <= 4 ; i++){
  console.log(i, jsonLogic.apply(rule, {"i":i}));
}
/* Outputs:
1 "odd"
2 "even"
3 "odd"
4 "even"
*/

Array Operations

map, reduce, and filter

You can use map to perform an action on every member of an array. Note, that inside the logic being used to map, var operations are relative to the array element being worked on.

You can use filter to keep only elements of the array that pass a test. Note, that inside the logic being used to map, var operations are relative to the array element being worked on.

Also note, the returned array will have contiguous indexes starting at zero (typical for JavaScript, Python and Ruby) it will not preserve the source indexes (making it unlike PHP’s array_filter).

You can use reduce to combine all the elements in an array into a single value, like adding up a list of numbers. Note, that inside the logic being used to reduce, var operations only have access to an object like:

{
    "current" : // this element of the array,
    "accumulator" : // progress so far, or the initial value
}

all, none, and some

These operations take an array, and perform a test on each member of that array.

The most interesting part of these operations is that inside the test code, var operations are relative to the array element being tested.

It can be useful to use {"var":""} to get the entire array element within the test.

Or it can be useful to test an object based on its properties:

Note that none will return true for an empty array, while all and some will return false.

merge

Takes one or more arrays, and merges them into one array. If arguments aren’t arrays, they get cast to arrays.

Merge can be especially useful when defining complex missing rules, like which fields are required in a document. For example, this vehicle paperwork always requires the car’s VIN, but only needs the APR and term if you’re financing.

in

If the second argument is an array, tests that the first argument is a member of the array:

String Operations

in

If the second argument is a string, tests that the first argument is a substring:

cat

Concatenate all the supplied arguments. Note that this is not a join or implode operation, there is no “glue” string.

substr

Get a portion of a string.

Give a positive start position to return everything beginning at that index. (Indexes of course start at zero.)

Give a negative start position to work backwards from the end of the string, then return everything.

Give a positive length to express how many characters to return.

Give a negative length to stop that many characters before the end.

Miscellaneous

log

Logs the first value to console, then passes it through unmodified.

This can be especially helpful when debugging a large rule.

(Check your developer console!)

Go JSON Logic

test workflow
codecov
Go Report Card

Implementation of JSON Logic in Go Lang.

What’s JSON Logic?

JSON Logic is a DSL to write logic decisions in JSON. It’s has a great specification and is very simple to learn.
The official website has a great documentation with examples.

How to use it

The use of this library is very straightforward. Here’s a simple example:

package main

import (
	"bytes"
	"fmt"
	"strings"

	"github.com/diegoholiveira/jsonlogic/v3"
)

func main() {
	logic := strings.NewReader(`{"==": [1, 1]}`)
	data := strings.NewReader(`{}`)

	var result bytes.Buffer

	jsonlogic.Apply(logic, data, &result)

	fmt.Println(result.String())
}

This will output true in your console.

Here’s another example, but this time using variables passed in the data parameter:

package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"strings"

	"github.com/diegoholiveira/jsonlogic/v3"
)

type (
	User struct {
		Name     string `json:"name"`
		Age      int    `json:"age"`
		Location string `json:"location"`
	}

	Users []User
)

func main() {
	logic := strings.NewReader(`{
        "filter": [
            {"var": "users"},
            {">=": [
                {"var": ".age"},
                18
            ]}
        ]
    }`)

	data := strings.NewReader(`{
        "users": [
            {"name": "Diego", "age": 33, "location": "Florianópolis"},
            {"name": "Jack", "age": 12, "location": "London"},
            {"name": "Pedro", "age": 19, "location": "Lisbon"},
            {"name": "Leopoldina", "age": 30, "location": "Rio de Janeiro"}
        ]
    }`)

	var result bytes.Buffer

	err := jsonlogic.Apply(logic, data, &result)
	if err != nil {
		fmt.Println(err.Error())

		return
	}

	var users Users

	decoder := json.NewDecoder(&result)
	decoder.Decode(&users)

	for _, user := range users {
		fmt.Printf("    - %sn", user.Name)
	}
}

If you have a function you want to expose as a JSON Logic operation, you can use:

package main

import (
	"bytes"
	"fmt"
	"strings"

	"github.com/diegoholiveira/jsonlogic/v3"
)

func main() {
	// add a new operator "strlen" for get string length
	jsonlogic.AddOperator("strlen", func(values, data interface{}) interface{} {
		v, ok := values.(string)
		if ok {
			return len(v)
		}
		return 0
	})

	logic := strings.NewReader(`{ "strlen": { "var": "foo" } }`)
	data := strings.NewReader(`{"foo": "bar"}`)

	var result bytes.Buffer

	jsonlogic.Apply(logic, data, &result)

	fmt.Println(result.String()) // the string length of "bar" is 3
}

License

This project is licensed under the MIT License — see the LICENSE file for details

Go JSON Logic

Build Status
codecov

Implementation of JSON Logic in Go Lang.

What’s JSON Logic?

JSON Logic is a DSL to write logic decisions in JSON. It’s has a great specification and is very simple to learn.
The official website has a great documentation with examples: http://jsonlogic.com

How to use it

The use of this library is very straightforward. Here’s a simple example:

package main

import (
	"encoding/json"
	"fmt"

	"github.com/diegoholiveira/jsonlogic"
)

func main() {
	var logic interface{}

	err := json.Unmarshal([]byte(`{"==": [1, 1]}`), &logic)
	if err != nil {
		fmt.Println(err.Error())

		return
	}

	var result interface{}

	jsonlogic.Apply(
		logic,
		nil,
		&result,
	)

	fmt.Println(result)
}

This will output true in your console.

Here’s another example, but this time using variables passed in the data parameter:

package main

import (
	"encoding/json"
	"fmt"

	"github.com/diegoholiveira/jsonlogic"
)

func main() {
	var logic interface{}
	var data interface{}
	var result interface{}

	json.Unmarshal([]byte(`{
		"filter": [
			{"var": "users"},
			{">=": [
				{"var": ".age"},
				18
			]}
		]
	}`), &logic)

	json.Unmarshal([]byte(`{
		"users": [
			{"name": "Diego", "age": 33, "location": "Florianópolis"},
			{"name": "Jack", "age": 12, "location": "London"},
			{"name": "Pedro", "age": 19, "location": "Lisbon"},
			{"name": "Leopoldina", "age": 30, "location": "Rio de Janeiro"}
		]
	}`), &data)

	err := jsonlogic.Apply(logic, data, &result)
	if err != nil {
		fmt.Println(err.Error())

		return
	}

	fmt.Println("Users older than 18:")
	for _, _user := range result.([]interface{}) {
		user := _user.(map[string]interface{})

		fmt.Printf("    - %sn", user["name"].(string))
	}
}

Limitations

The Apply function have three params as input:

  • the first one is the logic to be executed;
  • next you have the data that can be used by the logic;
  • and the last one is the variable to store the result.

The type of those params must be interface{} to be easy to reflect of it and use it.
Also, all values passed in any of the two input params must be one of those:

bool for JSON booleans,
float64 for JSON numbers,
string for JSON strings, and
nil for JSON null.

This is the same values that encoding/json work with.

Here’s an example of an invalid data:

func main() {
	var rules interface{}
	var result interface{}

	json.Unmarshal([]byte(`{
		"filter": [
			{"var": "users"},
			{">=": [
				{"var": ".age"},
				18
			]}
		]
	}`), &rules)

	data := interface{}(map[string]interface{}{
		"users": []interface{}{
			map[string]interface{}{
				"name":     string("Diego"),
				"age":      int(33),
				"location": string("Florianópolis"),
			},
		},
	})

	err := jsonlogic.Apply(rules, data, &result)
	if err != nil {
		fmt.Println(err.Error())

		return
	}

	fmt.Println("Users older than 18:")
	for _, _user := range result.([]interface{}) {
		user := _user.(map[string]interface{})

		fmt.Printf("    - %sn", user["name"].(string))
	}
}

This will produce this error: panic: interface conversion: interface {} is int, not float64.
So, to avoid this error, make sure to always work with types compatible with encoding/json.

License

This project is licensed under the MIT License — see the LICENSE file for details

  • Introduction
  • Error Objects
  • Error Lists
  • Responses
    • HTTP Status
  • JSON:API Exceptions
    • Helper Methods
  • Validation Errors
    • Source Pointers
  • Error Rendering
    • JSON Responses
    • Middleware Responses
    • Always Rendering JSON:API Errors
    • Custom Rendering Logic
    • Converting Exceptions
  • Error Reporting

# Introduction

The JSON:API specification defines error objects (opens new window)
that are used to provide information to a client about problems encountered
while performing an operation.

Errors are returned to the client in the top-level errors member of the
JSON document.

Laravel JSON:API makes it easy to return errors to the client — either
as responses, or by throwing exceptions. In addition, the exception
renderer you added to your exception handler during
installation takes care of
converting standard Laravel exceptions to JSON:API error responses
if the client has sent an Accept: application/vnd.api+json header.

# Error Objects

Use our LaravelJsonApiCoreDocumentError object to create a JSON:API
error object. The easiest way to construct an error object is using the
static fromArray method. For example:

The fromArray method accepts all the error object members
defined in the specification. (opens new window)

Alternatively, if you want to use setters, use the static make method
to fluently construct your error object:

The available setters are:

  • setAboutLink
  • setCode
  • setDetail
  • setId
  • setLinks
  • setMeta
  • setStatus
  • setSource
  • setSourceParameter
  • setSourcePointer
  • setTitle

# Error Lists

If you need to return multiple errors at once, use our
LaravelJsonApiCoreDocumentErrorList class. This accepts any number
of error objects to its constructor. For example:

Use the push method to add errors after constructing the error list:

# Responses

Both the Error and ErrorList classes implement Laravel’s Responsable
interface. This means you can return them directly from controller actions
and they will be converted to a JSON:API error response.

If you need to customise the error response, then you need to use our
LaravelJsonApiCoreResponsesErrorResponse class. Either create
a new one, passing in the Error or ErrorList object:

Or alternatively, use the prepareResponse method on either the Error
or ErrorList object:

The ErrorResponse class has all the helper methods
required to customise both the headers and the JSON:API document that
is returned in the response.

For example, if we were adding a header and meta to our response:

# HTTP Status

The JSON:API specification says:

When a server encounters multiple problems for a single request,
the most generally applicable HTTP error code SHOULD be used in the response.
For instance, 400 Bad Request might be appropriate for multiple 4xx errors
or 500 Internal Server Error might be appropriate for multiple 5xx errors.

Our ErrorResponse class takes care of calculating the HTTP status for you.

If there is only one error, or all the errors have the same status, then
the response status will match this status.

If you have multiple errors with different statuses, the response status
will be 400 Bad Request if the error objects only have 4xx status codes.
If there are any 5xx status codes, the response status will be
500 Internal Server Error.

If the response has no error objects, or none of the error objects have a
status, then the response will have a 500 Internal Server Error status.

If you want the response to have a specific HTTP status, use the
withStatus method. For example:

# JSON:API Exceptions

Our LaravelJsonApiCoreExceptionsJsonApiException allows you to terminate
processing of a request by throwing an exception with JSON:API errors
attached.

The exception expects its first argument to be either an Error or an
ErrorList object. For example:

The JsonApiException class has all the helper methods
required to customise both the headers and the JSON:API document that
is returned in the response. Use the static make method if you need to
call any of these methods. For example:

There is also a handy static error method. This allows you to fluently
construct an exception for a single error, providing either an Error
object or an array. For example:

# Helper Methods

The JsonApiException class has a number of helper methods:

  • is4xx
  • is5xx
  • getErrors

# is4xx

Returns true if the HTTP status code is a client error, i.e. in the 400-499
range.

# is5xx

Returns true if the HTTP status code is a server error, i.e. in the 500-599
range.

# getErrors

Use the getErrors() method to retrieve the JSON:API error objects from the
exception. For example, if we wanted to log the errors:

# Validation Errors

Our implementation of resource requests and
query parameter requests already takes
care of converting Laravel validation error messages to JSON:API errors.

If however you have a scenario where you want to convert a failed validator
to JSON:API errors manually, we provide the ability to do this.

You will need to resolve an instance of LaravelJsonApiValidationFactory
out of the service container. For example, you could use the app helper,
or use dependency injection by type-hinting it in a constructor of a service.

Once you have the factory instance, use the createErrors method,
providing it with the validator instance. For example, in a controller
action:

The object this returns is Responsable — so you can return it directly
from a controller action. If you want to convert it to an error response,
use our prepareResponse pattern as follows:

# Source Pointers

By default this process will convert validation error keys to JSON source
pointers. For example, if you have a failed message for the foo.bar
value, the resulting error object will have a source pointer of
/foo/bar.

If you need to prefix the pointer value, use the withSourcePrefix method.
The following example would convert foo.bar to /data/attributes/foo/bar:

If you need to fully customise how the validation key should be converted,
provide a Closure to the withPointers method:

# Error Rendering

As described in the installation instructions,
the following should have been added to the register method on your
application’s exception handler:

The Laravel exception handler already takes care of converting exceptions to
either application/json or text/html responses. Our exception handler
effectively adds JSON:API error responses as a third media type. If the
client has sent a request with an Accept header of application/vnd.api+json,
then they will receive the exception response as a JSON:API error response —
even if the endpoint they are hitting is not one of your JSON:API server
endpoints.

WARNING

There are some scenarios where the Laravel exception handler does not call
any registered renderables. For example, if an exception implements Laravel’s
Responsable interface, our exception parser will not be invoked as the handler
uses the Responsable::toResponse() method on the exception to generate a
response.

# JSON Responses

If a client encounters an exception when using an Accept header of
application/json, they will still receive Laravel’s default JSON exception
response, rather than a JSON:API response.

If you want our exception parser to render JSON exception responses instead
of the default Laravel response, use the acceptsJson() method when registering
our exception parser:

# Middleware Responses

Sometimes you may want exceptions to be converted to JSON:API errors if the
current route has a particular middleware applied to it. The most common example
of this would be if you want JSON:API errors to always be rendered if the
current route has the api middleware.

In this scenario, use the acceptsMiddleware() method when registering our
exception parser. For example:

TIP

You can provide multiple middleware names to the acceptsMiddleware() method.
When you do this, it will match a route that contains any of the provided
middleware.

# Always Rendering JSON:API Errors

If you want our exception parser to always convert exceptions to JSON:API
errors, use the acceptsAll() helper method:

# Custom Rendering Logic

If you want your own logic for when a JSON:API exception response should be
rendered, pass a closure to the accept() method.

For example, let’s say we wanted our API to always return JSON:API exception
responses, regardless of what Accept header the client sent. We would use
the request is() method to check if the path is our API:

TIP

If you return false from the callback, the normal exception rendering logic
will run — meaning a client that has sent an Accept header with the JSON:API
media type will still receive a JSON:API response. This is semantically correct,
as the Accept header value should be respected.

# Converting Exceptions

Our exception parser is built so that you can easily add support for
custom exceptions to the JSON:API rendering process. The implementation works
using a pipeline, meaning you can add your own handlers for converting
exceptions to JSON:API errors.

For example, imagine our application had a PaymentFailed exception, that
we wanted to convert to JSON:API errors if thrown to the exception handler.
We would write the following class:

We can then add it to the JSON:API exception parser using either the
prepend or append method:

# Error Reporting

As described in the installation instructions,
the following should have been added to the $dontReport property on your
application’s exception handler:

This prevents our JsonApiException from being reported in your application’s
error log. This is a sensible starting position, as the JsonApiException
class is effectively a HTTP exception that needs to be rendered to the client.

However, this does mean that any JsonApiException that has a 5xx status
code (server-side error) will not be reported in your error log. Therefore,
an alternative is to use the helper methods on the JsonApiException class
to determine whether or not the exception should be reported.

To do this, we will use the reportable() method to register a callback
for the JSON:API exception class. (At the same time, we remove the exception
class from the $dontReport property.) For example, the following will stop
the propagation of JSON:API exceptions to the default logging stack if the
exception does not have a 5xx status:

In the following example, we log 4xx statuses as debug information, while
letting all other JSON:API exceptions propagate to the default logging stack:

Понравилась статья? Поделить с друзьями:
  • Json error битрикс
  • Json error utf8
  • Json error unknown
  • Json error syntax error 4 100
  • Json error status 200