From 5770f38d60cc60b37a6bb8e9eea91d90cd1ff3cd Mon Sep 17 00:00:00 2001 From: Florian Ruen Date: Mon, 31 Jul 2023 17:34:42 +0200 Subject: [PATCH 1/5] feat: implement method to get json logic with vars replaced by values --- jsonlogic.go | 22 ++++++++++++++++++++++ vars.go | 19 +++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/jsonlogic.go b/jsonlogic.go index 734ce79..bd5c676 100644 --- a/jsonlogic.go +++ b/jsonlogic.go @@ -515,6 +515,28 @@ func Apply(rule, data io.Reader, result io.Writer) error { return json.NewEncoder(result).Encode(output) } +func GetJsonLogicWithSolvedVars(rule, data json.RawMessage) ([]byte, error) { + if data == nil { + data = json.RawMessage("{}") + } + + // parse rule and data from json.RawMessage to interface + var _rule interface{} + var _data interface{} + + err := json.Unmarshal(rule, &_rule) + if err != nil { + return nil, err + } + + err = json.Unmarshal(data, &_data) + if err != nil { + return nil, err + } + + return solveVarsBackToJsonLogic(_rule, _data) +} + func ApplyRaw(rule, data json.RawMessage) (json.RawMessage, error) { if data == nil { data = json.RawMessage("{}") diff --git a/vars.go b/vars.go index 31fc2ac..70f4c4e 100644 --- a/vars.go +++ b/vars.go @@ -1,6 +1,7 @@ package jsonlogic import ( + "encoding/json" "strings" ) @@ -105,3 +106,21 @@ func getVar(value, data interface{}) interface{} { return _value } + +func solveVarsBackToJsonLogic(rule, data interface{}) ([]byte, error) { + ruleMap := rule.(map[string]interface{}) + result := make(map[string]interface{}) + + for operator, values := range ruleMap { + result[operator] = solveVars(values, data) + } + + // convert the result to json.RawMessage + body, err := json.Marshal(result) + + if err != nil { + return nil, err + } + + return body, nil +} From 39e184d89269124d19425f8e072cc69a3f0f6854 Mon Sep 17 00:00:00 2001 From: Florian Ruen Date: Mon, 31 Jul 2023 17:39:52 +0200 Subject: [PATCH 2/5] feat: change a comment --- vars.go | 1 - 1 file changed, 1 deletion(-) diff --git a/vars.go b/vars.go index 70f4c4e..be3f1c9 100644 --- a/vars.go +++ b/vars.go @@ -115,7 +115,6 @@ func solveVarsBackToJsonLogic(rule, data interface{}) ([]byte, error) { result[operator] = solveVars(values, data) } - // convert the result to json.RawMessage body, err := json.Marshal(result) if err != nil { From 9f623b192aaca6ed31a8f73f03789043fd1587e7 Mon Sep 17 00:00:00 2001 From: Florian Ruen Date: Mon, 31 Jul 2023 20:35:47 +0200 Subject: [PATCH 3/5] feat: update package name --- go.mod | 3 ++- go.sum | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index c0d9efa..c856767 100644 --- a/go.mod +++ b/go.mod @@ -1,8 +1,9 @@ -module github.com/diegoholiveira/jsonlogic/v3 +module github.com/CIDgravity/jsonlogic/v3 go 1.14 require ( + github.com/diegoholiveira/jsonlogic/v3 v3.2.7 github.com/mitchellh/copystructure v1.0.0 github.com/stretchr/testify v1.6.1 ) diff --git a/go.sum b/go.sum index f458732..e1ae89c 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,7 @@ github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/diegoholiveira/jsonlogic/v3 v3.2.7 h1:awX07pFPnlntZzRNBcO4a2Ivxa77NMt+narq/6xcS0E= +github.com/diegoholiveira/jsonlogic/v3 v3.2.7/go.mod h1:9oE8z9G+0OMxOoLHF3fhek3KuqD5CBqM0B6XFL08MSg= github.com/mitchellh/copystructure v1.0.0 h1:Laisrj+bAB6b/yJwB5Bt3ITZhGJdqmxquMKeZ+mmkFQ= github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= github.com/mitchellh/reflectwalk v1.0.0 h1:9D+8oIskB4VJBN5SFlmc27fSlIBZaov1Wpk/IfikLNY= From dece7751495e68a3bfd086f902b3f66e8ea30939 Mon Sep 17 00:00:00 2001 From: Florian Ruen Date: Mon, 31 Jul 2023 20:46:16 +0200 Subject: [PATCH 4/5] feat: update readme --- readme.md | 160 +++++++++++++----------------------------------------- 1 file changed, 37 insertions(+), 123 deletions(-) diff --git a/readme.md b/readme.md index c752bcc..be5163d 100644 --- a/readme.md +++ b/readme.md @@ -1,143 +1,57 @@ -# Go JSON Logic +# Go JSON Logic (CIDgravity custom version) -![test workflow](https://github.com/diegoholiveira/jsonlogic/actions/workflows/test.yml/badge.svg) -[![codecov](https://codecov.io/gh/diegoholiveira/jsonlogic/branch/master/graph/badge.svg)](https://codecov.io/gh/diegoholiveira/jsonlogic) -[![Go Report Card](https://goreportcard.com/badge/github.com/diegoholiveira/jsonlogic)](https://goreportcard.com/report/github.com/diegoholiveira/jsonlogic) +Forked from [https://github.com/diegoholiveira/jsonlogic](https://github.com/diegoholiveira/jsonlogic) +For original documentation, view the original repository -Implementation of [JSON Logic](http://jsonlogic.com) 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](http://jsonlogic.com) has a great documentation with examples. - -## How to use it - -The use of this library is very straightforward. Here's a simple example: - -```go -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: +Added function to generate the json logic with solved variables ```go -package main - -import ( - "bytes" - "encoding/json" - "fmt" - "strings" - - "github.com/diegoholiveira/jsonlogic/v3" -) +func solveVarsBackToJsonLogic(rule, data interface{}) ([]byte, error) { + ruleMap := rule.(map[string]interface{}) + result := make(map[string]interface{}) -type ( - User struct { - Name string `json:"name"` - Age int `json:"age"` - Location string `json:"location"` + for operator, values := range ruleMap { + result[operator] = solveVars(values, data) } - Users []User -) + body, err := json.Marshal(result) -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 + return nil, err } - var users Users - - decoder := json.NewDecoder(&result) - decoder.Decode(&users) - - for _, user := range users { - fmt.Printf(" - %s\n", user.Name) - } + return body, nil } -``` - -If you have a function you want to expose as a JSON Logic operation, you can use: -```go -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 +For example, if you specify the folowing rules model : - jsonlogic.Apply(logic, data, &result) - fmt.Println(result.String()) // the string length of "bar" is 3 +```json +{ + "and":[ + { "==":[{ "var":"VariableA" }, true] }, + { "==":[{ "var":"VariableB" }, true] }, + { ">=":[{ "var":"VariableC" }, 17179869184] }, + { "==":[{ "var":"VariableD" }, "0"] }, + { "<":[{ "var":"VariableE" }, 20] } + ] } + ``` -# License +You will get as output, the folowing response (using a specific data, all variables will be replaced with matching values) : + +```json +{ + "and":[ + { "==":[false, true] }, + { "==":[true, true] }, + { ">=":[34359738368, 17179869184] }, + { "==":[12, "0"] }, + { "<":[14, 20] } + ] +} -This project is licensed under the MIT License - see the LICENSE file for details +``` \ No newline at end of file From 4d3c856eab1398dcd6f9061ae7489a74a72350b3 Mon Sep 17 00:00:00 2001 From: Florian Ruen Date: Tue, 1 Aug 2023 09:50:11 +0200 Subject: [PATCH 5/5] feat: method to get json logic with solved vars --- .gitignore | 3 +- go.mod | 3 +- go.sum | 2 - jsonlogic_test.go | 52 ++++++++++++++ readme.md | 170 ++++++++++++++++++++++++++++++++++++++++++---- vars.go | 15 +++- 6 files changed, 225 insertions(+), 20 deletions(-) diff --git a/.gitignore b/.gitignore index c61e685..bc16316 100644 --- a/.gitignore +++ b/.gitignore @@ -11,4 +11,5 @@ # Output of the go coverage tool, specifically when used with LiteIDE *.out -.idea/ \ No newline at end of file +.idea/ +.vscode/ \ No newline at end of file diff --git a/go.mod b/go.mod index c856767..c0d9efa 100644 --- a/go.mod +++ b/go.mod @@ -1,9 +1,8 @@ -module github.com/CIDgravity/jsonlogic/v3 +module github.com/diegoholiveira/jsonlogic/v3 go 1.14 require ( - github.com/diegoholiveira/jsonlogic/v3 v3.2.7 github.com/mitchellh/copystructure v1.0.0 github.com/stretchr/testify v1.6.1 ) diff --git a/go.sum b/go.sum index e1ae89c..f458732 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,5 @@ github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/diegoholiveira/jsonlogic/v3 v3.2.7 h1:awX07pFPnlntZzRNBcO4a2Ivxa77NMt+narq/6xcS0E= -github.com/diegoholiveira/jsonlogic/v3 v3.2.7/go.mod h1:9oE8z9G+0OMxOoLHF3fhek3KuqD5CBqM0B6XFL08MSg= github.com/mitchellh/copystructure v1.0.0 h1:Laisrj+bAB6b/yJwB5Bt3ITZhGJdqmxquMKeZ+mmkFQ= github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= github.com/mitchellh/reflectwalk v1.0.0 h1:9D+8oIskB4VJBN5SFlmc27fSlIBZaov1Wpk/IfikLNY= diff --git a/jsonlogic_test.go b/jsonlogic_test.go index 709620d..1747f23 100644 --- a/jsonlogic_test.go +++ b/jsonlogic_test.go @@ -740,3 +740,55 @@ func TestIssue58_example(t *testing.T) { expected := `{"foo":"is_bar","path":"foo_is_bar"}` assert.JSONEq(t, expected, result.String()) } + +func TestJsonLogicWithSolvedVars(t *testing.T) { + rule := json.RawMessage(`{ + "or":[ + { + "and":[ + {"==": [{ "var":"is_foo" }, true ]}, + {"==": [{ "var":"is_bar" }, true ]}, + {">=": [{ "var":"foo" }, 17179869184 ]}, + {"==": [{ "var":"bar" }, 0 ]} + ] + }, + { + "and":[ + {"==": [{ "var":"is_bar" }, true ]}, + {"==": [{ "var":"is_foo" }, false ]}, + {"==": [{ "var":"foo" }, 34359738368 ]}, + {"==": [{ "var":"bar" }, 0 ]} + ] + }] + }`) + + data := json.RawMessage(`{"foo": 34359738368, "bar": 10, "is_foo": false, "is_bar": true}`) + + output, err := GetJsonLogicWithSolvedVars(rule, data) + + if err != nil { + t.Fatal(err) + } + + expected := `{ + "or":[ + { + "and":[ + { "==":[ false, true ] }, + { "==":[ true, true ] }, + { ">=":[ 34359738368, 17179869184 ] }, + { "==":[ 10, 0 ] } + ] + }, + { + "and":[ + { "==":[ true, true ] }, + { "==":[ false, false ] }, + { "==":[ 34359738368, 34359738368 ] }, + { "==":[ 10, 0 ] } + ] + }] + }` + + assert.JSONEq(t, expected, string(output)) +} diff --git a/readme.md b/readme.md index be5163d..8e4547b 100644 --- a/readme.md +++ b/readme.md @@ -1,30 +1,176 @@ -# Go JSON Logic (CIDgravity custom version) +# Go JSON Logic -Forked from [https://github.com/diegoholiveira/jsonlogic](https://github.com/diegoholiveira/jsonlogic) -For original documentation, view the original repository +![test workflow](https://github.com/diegoholiveira/jsonlogic/actions/workflows/test.yml/badge.svg) +[![codecov](https://codecov.io/gh/diegoholiveira/jsonlogic/branch/master/graph/badge.svg)](https://codecov.io/gh/diegoholiveira/jsonlogic) +[![Go Report Card](https://goreportcard.com/badge/github.com/diegoholiveira/jsonlogic)](https://goreportcard.com/report/github.com/diegoholiveira/jsonlogic) -Added function to generate the json logic with solved variables +Implementation of [JSON Logic](http://jsonlogic.com) 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](http://jsonlogic.com) has a great documentation with examples. + +## How to use it + +The use of this library is very straightforward. Here's a simple example: + +```go +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: ```go -func solveVarsBackToJsonLogic(rule, data interface{}) ([]byte, error) { - ruleMap := rule.(map[string]interface{}) - result := make(map[string]interface{}) +package main + +import ( + "bytes" + "encoding/json" + "fmt" + "strings" - for operator, values := range ruleMap { - result[operator] = solveVars(values, data) + "github.com/diegoholiveira/jsonlogic/v3" +) + +type ( + User struct { + Name string `json:"name"` + Age int `json:"age"` + Location string `json:"location"` } - body, err := json.Marshal(result) + 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 { - return nil, err + fmt.Println(err.Error()) + + return } - return body, nil + var users Users + + decoder := json.NewDecoder(&result) + decoder.Decode(&users) + + for _, user := range users { + fmt.Printf(" - %s\n", user.Name) + } } +``` +If you have a function you want to expose as a JSON Logic operation, you can use: + +```go +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 +} ``` +If you want to get the json logic used, with the variables replaced by their values : + +```go +package main + +import ( + "fmt" + "encoding/json" + + "github.com/diegoholiveira/jsonlogic/v3" +) + +func main() { + logic := json.RawMessage(`{ "==":[{ "var":"foo" }, true] }`) + data := json.RawMessage(`{"foo": "false"}`) + + result, err := jsonlogic.GetJsonLogicWithSolvedVars(logic, data) + + if err != nil { + fmt.Println(err) + } + + fmt.Println(string(result)) // will output { "==":[false, true] } +} + +``` + +# License + +This project is licensed under the MIT License - see the LICENSE file for details + + + For example, if you specify the folowing rules model : diff --git a/vars.go b/vars.go index be3f1c9..46294d5 100644 --- a/vars.go +++ b/vars.go @@ -2,6 +2,7 @@ package jsonlogic import ( "encoding/json" + "strconv" "strings" ) @@ -107,7 +108,7 @@ func getVar(value, data interface{}) interface{} { return _value } -func solveVarsBackToJsonLogic(rule, data interface{}) ([]byte, error) { +func solveVarsBackToJsonLogic(rule, data interface{}) (json.RawMessage, error) { ruleMap := rule.(map[string]interface{}) result := make(map[string]interface{}) @@ -115,11 +116,19 @@ func solveVarsBackToJsonLogic(rule, data interface{}) ([]byte, error) { result[operator] = solveVars(values, data) } - body, err := json.Marshal(result) + resultJson, err := json.Marshal(result) if err != nil { return nil, err } - return body, nil + // we need to use Unquote due to unicode characters (example \u003e= need to be >=) + // used for prettier json.RawMessage + resultEscaped, err := strconv.Unquote(strings.Replace(strconv.Quote(string(resultJson)), `\\u`, `\u`, -1)) + + if err != nil { + return nil, err + } + + return []byte(resultEscaped), nil }