diff --git a/.jekyll-metadata b/.jekyll-metadata index fa09ef35..f9113b5e 100644 Binary files a/.jekyll-metadata and b/.jekyll-metadata differ diff --git a/_docs/logic/basics.md b/_docs/logic/basics.md index b04ec428..837aa410 100644 --- a/_docs/logic/basics.md +++ b/_docs/logic/basics.md @@ -2,7 +2,7 @@ layout: page title: JsonLogic Basics md_title: _JsonLogic_ Basics -bookmark: JSON Logic +bookmark: Basics permalink: /logic/:title/ icon: fas fa-tag order: "05.1" @@ -17,7 +17,7 @@ JSON Logic is expressed using single-keyed objects called _rules_. The key is t - Merging arrays: `{"merge":[ [1,2], [3,4] ]}` - Value detection: `{"in":[ "Ringo", ["John", "Paul", "George", "Ringo"] ]}` -> For rules that only have one parameter, that parameter can be expressed directly instead of in an array. This shorthand is provided as a syntactic sugar.* +> For rules that only have one parameter, that parameter can be expressed directly instead of in an array. This shorthand is provided as a syntactic sugar. {: .prompt-info } While explicitly listing rules is all well and good, the real power of this comes from the ability to reference input data using the `var` operator. This operator @@ -27,38 +27,25 @@ So if we want to ensure a value in the input data is less than 2, we could use ` There are many operators that work on different data types, ranging from string and array manipulation to arithmetic to boolean logic. The full list is [on their website](https://jsonlogic.com/operations.html), and their docs are pretty good, so I won't repeat the list here. -## In code {#logic-in-code} +## In code -The library defines an object model for rules, starting with the `Rule` base class. This type is fully serializeable, so if you have rules in a text format, just deserialize them to get a `Rule` instance. +The `JsonLogic` static class exposes an `.Apply(JsonNode? rule, JsonNode? context)` method that you'll use to process your rules. -```c# -var rule = JsonSerializer.Deserialize("{\"<\" : [1, 2]}"); -``` +To start, you need to get your rule and data into a `JsonNode`. You can do this by parsing text or by explicitly building the JSON using `JsonNode`'s easy API. -Once you have a rule instance, you can apply it using the `.Apply()` method, which takes a `JsonNode?` for the data. (JSON null and .Net null are unified for this model.) Sometimes, you may not have a data instance; rather you just want the rule to run. In these cases you can call `.Apply()` passing a `null` or by using the `.Apply()` extension method which takes no parameters. +Once you have your nodes, simply pass them into the `.Apply()` method. ```c# -var data = JsonNode.Parse("{\"foo\": \"bar\"}"); -var result = rule.Apply(data); +var rule = JsonNode.Parse("..."); +var data = JsonNode.Parse("..."); +var result = JsonLogic.Apply(rule, data); ``` -In addition to reading and deserializing rules, you can define them inline using the `JsonLogic` static class. This class defines methods for all of the built-in rules. +That's it. -Creating the "less than" rule with a variable lookup from above: +### Native AOT support -```c# -var rule = JsonLogic.LessThan(JsonLogic.Variable("foo.bar"), 2); -``` - -The `2` here is actually implicitly cast to a `LiteralRule` which is a stand-in for discrete JSON elements. It can hold any JSON value, and there are implicit casts for numeric, string\*, and boolean types, as well as `JsonElement`. For arrays and objects, you can either build nodes inline - -```c# -new JsonArray { 1, false, "string" }; -``` - -or via `JsonNode.Parse()`. - -\* _JSON null literals need to either be cast to `string`, use `JsonNull.Node` from Json.More.Net, or use the provided `LiteralRule.Null`. All of these result in the same semantic value._ +There's nothing that needs serializing and there's no reflection, so it's fully AOT-compatible. ## Gotchas for .Net developers {#logic-gotchas} @@ -78,8 +65,8 @@ The first check is whether they are they same type. If so, it just applies stri |anything|`null`|`false`| |object|anything|`false`| |anything|object|`false`| -|number|array|convert the array to comma-delimited string and apply loose equality\*\*| -|array|number|convert the array to comma-delimited string and apply loose equality\*\*| +|number|array|convert the array to comma-delimited string and apply loose equality\*| +|array|number|convert the array to comma-delimited string and apply loose equality\*| |number|anything|attempt to convert to number and apply strict equality| |anything|number|attempt to convert to number and apply strict equality| |array|string|convert the array to comma-delimited string and apply strict equality| @@ -87,7 +74,7 @@ The first check is whether they are they same type. If so, it just applies stri That _should_ cover everything, but in case something's missed, it'll just return `false`. -\*\* _These cases effectively mean that the array must have a single element that is loosely equal to the number, though perhaps something like `[1,234]` might pass. Again, the equality is **very** loose._ +\* _These cases effectively mean that the array must have a single element that is loosely equal to the number, though perhaps something like `[1,234]` might pass. Again, the equality is **very** loose._ ### Type conversion {#logic-conversions} @@ -111,7 +98,7 @@ That's it. Not much to it; just be aware that it happens. JSON Logic also supports [adding custom operations](https://jsonlogic.com/add_operation.html). -In C#, your operators will need to derive from the `Rule` abstract class. There is only a single method to implement, `Apply()`, and you'll need to add an `Operator` attribute. The logic in the rule doesn't need to be complex, but there are a couple things to be aware of: +In C#, your operators will need to implement the `IRule` interface. There is only a single method to implement, `Apply()`. The logic in the rule doesn't need to be complex, but there are a couple things to be aware of: - The arguments for your rule must correspond to the parameters of the constructor. - You're working with `JsonNode`s, so you'll need to detect compatible value types. There are a few extension methods that you can use, like `.Numberify()`, that try to "fuzzy-cast" to an appropriate value. @@ -119,19 +106,12 @@ In C#, your operators will need to derive from the `Rule` abstract class. There `Apply()` takes two parameters, both of which are data for variables to act upon. -- `data` represents the external data. -- `contextData` represents data that's passed to it by other rules. - -Several rules (`all`, `none`, and `some`) can pass data to their children. `var` will prioritize `contextData` when attempting to resolve the path. If `contextData` is (JSON) null or doesn't have data at the indicated path, the path will be resolved against `data`. +- `args` is the JSON value for the rule. For most of the built-in rules, this is an array. You'll need to perform any validation. +- `context` represents data that's passed to it by the user and other rules. It's implemented as a stack of `JsonNodes`. You can add to the stack but remember to remove it. You can see an example of this in the [code for the `reduce` rule](https://github.com/gregsdennis/json-everything/blob/d03a2344c6a6498826385849292013045c555233/JsonLogic/Rules/ReduceRule.cs#L76-L104). It's definitely recommended to go through the [code for the built-in ruleset](https://github.com/gregsdennis/json-everything/tree/master/JsonLogic/Rules) for examples. -Once your rule is defined, it needs to be registered using the `RuleRegistry.Register()` method. This will allow the rule to be automatically deserialized. - -Lastly, you'll need to create a JSON converter for your rule. Due to the dynamic nature of how rules are serialized, your converter MUST implement `IWeaklyTypedJsonConverter` which is defined by _Json.More.Net_. The library also defines a `WeaklyTypeJsonConverter` abstract class that you can use as a base. - -> Also see the [AOT section](#aot) below for AOT-compatibility requirements. -{: info-warning} +Once your rule is defined, you'll need to register an instance using the `RuleRegistry.Register(string, IRule)` method. This will associate your rule with its operator key. ## Overriding existing operators {#logic-overriding} @@ -139,27 +119,4 @@ While this library allows you to inherit from, and therefore override, the defau The rules in this library implement the Json Logic Specification. If you override this behavior, then you are no longer implementing that specification, and you lose interoperability with other implementations. If you want custom behavior _and_ have this custom behavior common across implementations, you'll need to also override the behavior in _every_ implementation and application you use. -## Ahead of Time (AOT) compatibility {#aot} - -_JsonLogic_ v5 includes updates to support [Native AOT applications](https://learn.microsoft.com/en-us/dotnet/core/deploying/native-aot/). In order to take advantage of this, there are a few things you'll need to do. - -First, on your `JsonSerializerContext`, add the following attributes: - -```c# -[JsonSerializable(typeof(Rule))] -``` - -It's recommended that you create a single `JsonSerializerOptions` object (or a few if you need different configurations) and reuse it rather than creating them ad-hoc. When you create one, you'll need to configure its `TypeInfoResolverChain` with your serializer context: - -```c# -var serializerOptions = new() -{ - TypeInfoResolverChain = { MySerializerContext.Default } -}; -``` - -If you don't have any custom rules, you're done. Congratulations. - -If you do have custom rules, you'll want to add `[JsonSerializable]` attributes for those as well. - -For AOT-compatibility, you'll need to register your rules using the `RuleRegistry.AddRule(JsonSerializerContext)` method overload, passing in your serializer context, to provide the library access to the `JsonTypeInfo` for your rule type. +One prime candidate for overriding is the `log` rule. Currently, in addition to returning the log entry (which it definitely should do), it writes the entry to the console. Ideally, you'll want to write that somewhere more useful to you. By registering your own rule, you can make it write to whatever you need. diff --git a/_docs/logic/close.md b/_docs/logic/close.md new file mode 100644 index 00000000..6e70edf6 --- /dev/null +++ b/_docs/logic/close.md @@ -0,0 +1,6 @@ +--- +title: __close +permalink: /logic/:title/ +close: true +order: "05.9" +--- diff --git a/_docs/logic/legacy.md b/_docs/logic/legacy.md new file mode 100644 index 00000000..9fd1ba3f --- /dev/null +++ b/_docs/logic/legacy.md @@ -0,0 +1,102 @@ +--- +layout: page +title: JsonLogic Basics (Legacy) +md_title: _JsonLogic_ Basics (Legacy) +bookmark: Legacy (pre-v5.2) +permalink: /logic/:title/ +icon: fas fa-tag +order: "05.2" +--- + +> As of v5.2.0, this approach is deemed legacy and will likely be removed from v6. Benchmarks show this approach to be twice as slow and consume twice the memory. +{: .prompt-warning } + +## Using the object model {#logic-object-model} + +The library also defines an object model for rules, starting with the `Rule` base class. This type is fully serializeable, so if you have rules in a text format, just deserialize them to get a `Rule` instance. + +```c# +var rule = JsonSerializer.Deserialize("{\"<\" : [1, 2]}"); +``` + +Once you have a rule instance, you can apply it using the `.Apply()` method, which takes a `JsonNode?` for the data. (JSON null and .Net null are unified for this model.) Sometimes, you may not have a data instance; rather you just want the rule to run. In these cases you can call `.Apply()` passing a `null` or by using the `.Apply()` extension method which takes no parameters. + +```c# +var data = JsonNode.Parse("{\"foo\": \"bar\"}"); +var result = rule.Apply(data); +``` + +In addition to reading and deserializing rules, you can define them inline using the `JsonLogic` static class. This class defines methods for all of the built-in rules. + +Creating the "less than" rule with a variable lookup from above: + +```c# +var rule = JsonLogic.LessThan(JsonLogic.Variable("foo.bar"), 2); +``` + +The `2` here is actually implicitly cast to a `LiteralRule` which is a stand-in for discrete JSON elements. It can hold any JSON value, and there are implicit casts for numeric, string\*, and boolean types, as well as `JsonElement`. For arrays and objects, you can either build nodes inline + +```c# +new JsonArray { 1, false, "string" }; +``` + +or via `JsonNode.Parse()`. + +\* _JSON null literals need to either be cast to `string`, use `JsonNull.Node` from Json.More.Net, or use the provided `LiteralRule.Null`. All of these result in the same semantic value._ + +## Creating new operators {#logic-new-operators} + +JSON Logic also supports [adding custom operations](https://jsonlogic.com/add_operation.html). + +In C#, your operators will need to derive from the `Rule` abstract class. There is only a single method to implement, `Apply()`, and you'll need to add an `Operator` attribute. The logic in the rule doesn't need to be complex, but there are a couple things to be aware of: + +- The arguments for your rule must correspond to the parameters of the constructor. +- You're working with `JsonNode`s, so you'll need to detect compatible value types. There are a few extension methods that you can use, like `.Numberify()`, that try to "fuzzy-cast" to an appropriate value. +- If you encounter invalid input, throw a `JsonLogicException` with an appropriate message. + +`Apply()` takes two parameters, both of which are data for variables to act upon. + +- `data` represents the external data. +- `contextData` represents data that's passed to it by other rules. + +Several rules (`all`, `none`, and `some`) can pass data to their children. `var` will prioritize `contextData` when attempting to resolve the path. If `contextData` is (JSON) null or doesn't have data at the indicated path, the path will be resolved against `data`. + +It's definitely recommended to go through the [code for the built-in ruleset](https://github.com/gregsdennis/json-everything/tree/master/JsonLogic/Rules) for examples. + +Once your rule is defined, it needs to be registered using the `RuleRegistry.Register()` method. This will allow the rule to be automatically deserialized. + +Lastly, you'll need to create a JSON converter for your rule. Due to the dynamic nature of how rules are serialized, your converter MUST implement `IWeaklyTypedJsonConverter` which is defined by _Json.More.Net_. The library also defines a `WeaklyTypeJsonConverter` abstract class that you can use as a base. + +> Also see the [AOT section](#aot) below for AOT-compatibility requirements. +{: info-warning} + +## Overriding existing operators {#logic-overriding} + +While this library allows you to inherit from, and therefore override, the default behavior of a `Rule`, you need to be aware of the implications. + +The rules in this library implement the Json Logic Specification. If you override this behavior, then you are no longer implementing that specification, and you lose interoperability with other implementations. If you want custom behavior _and_ have this custom behavior common across implementations, you'll need to also override the behavior in _every_ implementation and application you use. + +## Ahead of Time (AOT) compatibility {#aot} + +_JsonLogic_ v5 includes updates to support [Native AOT applications](https://learn.microsoft.com/en-us/dotnet/core/deploying/native-aot/). In order to take advantage of this, there are a few things you'll need to do. + +First, on your `JsonSerializerContext`, add the following attributes: + +```c# +[JsonSerializable(typeof(Rule))] +``` + +It's recommended that you create a single `JsonSerializerOptions` object (or a few if you need different configurations) and reuse it rather than creating them ad-hoc. When you create one, you'll need to configure its `TypeInfoResolverChain` with your serializer context: + +```c# +var serializerOptions = new() +{ + TypeInfoResolverChain = { MySerializerContext.Default } +}; +``` + +If you don't have any custom rules, you're done. Congratulations. + +If you do have custom rules, you'll want to add `[JsonSerializable]` attributes for those as well. + +For AOT-compatibility, you'll need to register your rules using the `RuleRegistry.AddRule(JsonSerializerContext)` method overload, passing in your serializer context, to provide the library access to the `JsonTypeInfo` for your rule type. diff --git a/_docs/logic/title.md b/_docs/logic/title.md new file mode 100644 index 00000000..76f796fc --- /dev/null +++ b/_docs/logic/title.md @@ -0,0 +1,7 @@ +--- +title: __title +bookmark: JSON Logic +permalink: /logic/:title/ +folder: true +order: "05" +---