Repository: diegoholiveira/jsonlogic
Pull request #100 (2025-01-17 01:20:55)

Fix some comparisons
I noticed that `{"==": ["0", 0]}` stopped returning true at some point, which is preventing us from upgrading.  The JS library returns true for it.  I noticed the official tests didn't exercise/test this. so I've opened [a PR there](https://github.com/jwadhams/json-logic/pull/48) to add a bunch more tests of various type-coerced comparisons.

I also added the ability to run the tests with a local `tests.json` file which I used to fix those tests.


Patch:
diff --git a/comp.go b/comp.go
index 1b67ef6..2180364 100644
--- a/comp.go
+++ b/comp.go
@@ -4,6 +4,7 @@ import (
 	"math"
 	"reflect"
 	"strconv"
+	"strings"
 )
 
 // at simulate undefined in javascript
@@ -31,6 +32,10 @@ func toNumberForLess(v any) float64 {
 			return 0
 		}
 	case string:
+		if strings.TrimSpace(value) == "" {
+			return 0
+		}
+
 		n, err := strconv.ParseFloat(value, 64)
 		switch err {
 		case strconv.ErrRange, nil:
@@ -74,15 +79,12 @@ func equals(a, b any) bool {
 		return a == b
 	}
 
-	if isNumber(a) {
-		return isPrimitive(b) && toNumber(a) == toNumber(b)
+	if isPrimitive(a) && isPrimitive(b) && (isNumber(a) || isNumber(b)) {
+		return toNumberForLess(a) == toNumberForLess(b)
 	}
 
-	if isBool(a) {
-		if !isBool(b) {
-			return false
-		}
-		return isTrue(a) == isTrue(b)
+	if isBool(a) || isBool(b) {
+		return isTruthy(a) == isTruthy(b)
 	}
 
 	if !isString(a) || !isString(b) {
@@ -91,3 +93,11 @@ func equals(a, b any) bool {
 
 	return toString(a) == toString(b)
 }
+
+func isTruthy(v any) bool {
+	return toNumberForLess(v) == 1
+}
+
+func isFalsey(v any) bool {
+	return toNumberForLess(v) == 0
+}


Test patch:
diff --git a/comp_test.go b/comp_test.go
index f1bc012..bf5ee62 100644
--- a/comp_test.go
+++ b/comp_test.go
@@ -67,4 +67,8 @@ func TestEqualsWithBooleans(t *testing.T) {
 func TestEqualsWithNil(t *testing.T) {
 	assert.True(t, equals(nil, nil))
 	assert.False(t, equals(nil, ""))
-}
\ No newline at end of file
+}
+
+func TestEqualsWithNumberAndString(t *testing.T) {
+	assert.True(t, equals("1", 1))
+}
diff --git a/internal/json_logic_pr_48_tests.json b/internal/json_logic_pr_48_tests.json
new file mode 100644
index 0000000..74ffa58
--- /dev/null
+++ b/internal/json_logic_pr_48_tests.json
@@ -0,0 +1,559 @@
+[
+    "# Non-rules get passed through",
+    [ true, {}, true ],
+    [ false, {}, false ],
+    [ 17, {}, 17 ],
+    [ 3.14, {}, 3.14 ],
+    [ "apple", {}, "apple" ],
+    [ null, {}, null ],
+    [ ["a","b"], {}, ["a","b"] ],
+
+    "# Single operator tests",
+    [ {"==":[false,false]}, {}, true ],
+    [ {"==":[0,false]}, {}, true ],
+    [ {"==":[false,0]}, {}, true ],
+    [ {"==":[false,"0"]}, {}, true ],
+    [ {"==":[1,true]}, {}, true ],
+    [ {"==":["1",true]}, {}, true ],
+    [ {"==":["1.000",true]}, {}, true ],
+    [ {"==":["0",0]}, {}, true ],
+    [ {"==":["0.0000",0]}, {}, true ],
+    [ {"==":["0.0000",false]}, {}, true ],
+    [ {"==":["0.0000","0"]}, {}, false ],
+    [ {"==":["",0]}, {}, true ],
+    [ {"==":[" ",0]}, {}, true ],
+    [ {"==":["  ",0]}, {}, true ],
+    [ {"==":["  ",false]}, {}, true ],
+    [ {"==":[0,""]}, {}, true ],
+    [ {"==":[1,1]}, {}, true ],
+    [ {"==":[1,"1"]}, {}, true ],
+    [ {"==":["1",1]}, {}, true ],
+    [ {"==":["42.0",42]}, {}, true ],
+    [ {"==":[42.0000,"42"]}, {}, true ],
+    [ {"==":["42.0000",42]}, {}, true ],
+    [ {"==":[1,2]}, {}, false ],
+    [ {"==":[true,"true"]}, {}, false ],
+    [ {"==":["true",true]}, {}, false ],
+    [ {"==":["a ","a"]}, {}, false ],
+    [ {"===":[1,1]}, {}, true ],
+    [ {"===":[1,"1"]}, {}, false ],
+    [ {"===":[1,2]}, {}, false ],
+    [ {"!=":[1,2]}, {}, true ],
+    [ {"!=":[1,1]}, {}, false ],
+    [ {"!=":[1,"1"]}, {}, false ],
+    [ {"!=":["1",1]}, {}, false ],
+    [ {"!==":[1,2]}, {}, true ],
+    [ {"!==":[1,1]}, {}, false ],
+    [ {"!==":[1,"1"]}, {}, true ],
+    [ {">":[2,1]}, {}, true ],
+    [ {">":[1,1]}, {}, false ],
+    [ {">":[1,2]}, {}, false ],
+    [ {">":["2",1]}, {}, true ],
+    [ {">=":[2,1]}, {}, true ],
+    [ {">=":[1,1]}, {}, true ],
+    [ {">=":[1,2]}, {}, false ],
+    [ {">=":["2",1]}, {}, true ],
+    [ {"<":["",1]}, {}, true ],
+    [ {"<":["",-1]}, {}, false ],
+    [ {"<":[""," "]}, {}, true ],
+    [ {"<":[2,1]}, {}, false ],
+    [ {"<":[1,1]}, {}, false ],
+    [ {"<":[1,2]}, {}, true ],
+    [ {"<":["1",2]}, {}, true ],
+    [ {"<":[1,2,3]}, {}, true ],
+    [ {"<":[1,1,3]}, {}, false ],
+    [ {"<":[1,4,3]}, {}, false ],
+    [ {"<=":[2,1]}, {}, false ],
+    [ {"<=":[1,1]}, {}, true ],
+    [ {"<=":[1,2]}, {}, true ],
+    [ {"<=":["1",2]}, {}, true ],
+    [ {"<=":[1,2,3]}, {}, true ],
+    [ {"<=":[1,4,3]}, {}, false ],
+    [ {"!":[false]}, {}, true ],
+    [ {"!":false}, {}, true ],
+    [ {"!":[true]}, {}, false ],
+    [ {"!":true}, {}, false ],
+    [ {"!":0}, {}, true ],
+    [ {"!":1}, {}, false ],
+    [ {"or":[true,true]}, {}, true ],
+    [ {"or":[false,true]}, {}, true ],
+    [ {"or":[true,false]}, {}, true ],
+    [ {"or":[false,false]}, {}, false ],
+    [ {"or":[false,false,true]}, {}, true ],
+    [ {"or":[false,false,false]}, {}, false ],
+    [ {"or":[false]}, {}, false ],
+    [ {"or":[true]}, {}, true ],
+    [ {"or":[1,3]}, {}, 1 ],
+    [ {"or":[3,false]}, {}, 3 ],
+    [ {"or":[false,3]}, {}, 3 ],
+    [ {"and":[true,true]}, {}, true ],
+    [ {"and":[false,true]}, {}, false ],
+    [ {"and":[true,false]}, {}, false ],
+    [ {"and":[false,false]}, {}, false ],
+    [ {"and":[true,true,true]}, {}, true ],
+    [ {"and":[true,true,false]}, {}, false ],
+    [ {"and":[false]}, {}, false ],
+    [ {"and":[true]}, {}, true ],
+    [ {"and":[1,3]}, {}, 3 ],
+    [ {"and":[3,false]}, {}, false ],
+    [ {"and":[false,3]}, {}, false ],
+    [ {"?:":[true,1,2]}, {}, 1 ],
+    [ {"?:":[false,1,2]}, {}, 2 ],
+    [ {"in":["Bart",["Bart","Homer","Lisa","Marge","Maggie"]]}, {}, true ],
+    [ {"in":["Milhouse",["Bart","Homer","Lisa","Marge","Maggie"]]}, {}, false ],
+    [ {"in":["Spring","Springfield"]}, {}, true ],
+    [ {"in":["i","team"]}, {}, false ],
+    [ {"cat":"ice"}, {}, "ice" ],
+    [ {"cat":["ice"]}, {}, "ice" ],
+    [ {"cat":["ice","cream"]}, {}, "icecream" ],
+    [ {"cat":[1,2]}, {}, "12" ],
+    [ {"cat":["Robocop",2]}, {}, "Robocop2" ],
+    [ {"cat":["we all scream for ","ice","cream"]}, {}, "we all scream for icecream" ],
+    [ {"%":[1,2]}, {}, 1 ],
+    [ {"%":[2,2]}, {}, 0 ],
+    [ {"%":[3,2]}, {}, 1 ],
+    [ {"max":[1,2,3]}, {}, 3 ],
+    [ {"max":[1,3,3]}, {}, 3 ],
+    [ {"max":[3,2,1]}, {}, 3 ],
+    [ {"max":[1]}, {}, 1 ],
+    [ {"min":[1,2,3]}, {}, 1 ],
+    [ {"min":[1,1,3]}, {}, 1 ],
+    [ {"min":[3,2,1]}, {}, 1 ],
+    [ {"min":[1]}, {}, 1 ],
+
+    [ {"+":[1,2]}, {}, 3 ],
+    [ {"+":[2,2,2]}, {}, 6 ],
+    [ {"+":[1]}, {}, 1 ],
+    [ {"+":["1",1]}, {}, 2 ],
+    [ {"*":[3,2]}, {}, 6 ],
+    [ {"*":[2,2,2]}, {}, 8 ],
+    [ {"*":[1]}, {}, 1 ],
+    [ {"*":["1",1]}, {}, 1 ],
+    [ {"-":[2,3]}, {}, -1 ],
+    [ {"-":[3,2]}, {}, 1 ],
+    [ {"-":[3]}, {}, -3 ],
+    [ {"-":["1",1]}, {}, 0 ],
+    [ {"/":[4,2]}, {}, 2 ],
+    [ {"/":[2,4]}, {}, 0.5 ],
+    [ {"/":["1",1]}, {}, 1 ],
+
+    "Substring",
+    [{"substr":["jsonlogic", 4]}, null, "logic"],
+    [{"substr":["jsonlogic", -5]}, null, "logic"],
+    [{"substr":["jsonlogic", 0, 1]}, null, "j"],
+    [{"substr":["jsonlogic", -1, 1]}, null, "c"],
+    [{"substr":["jsonlogic", 4, 5]}, null, "logic"],
+    [{"substr":["jsonlogic", -5, 5]}, null, "logic"],
+    [{"substr":["jsonlogic", -5, -2]}, null, "log"],
+    [{"substr":["jsonlogic", 1, -5]}, null, "son"],
+
+    "Merge arrays",
+    [{"merge":[]}, null, []],
+    [{"merge":[[1]]}, null, [1]],
+    [{"merge":[[1],[]]}, null, [1]],
+    [{"merge":[[1], [2]]}, null, [1,2]],
+    [{"merge":[[1], [2], [3]]}, null, [1,2,3]],
+    [{"merge":[[1, 2], [3]]}, null, [1,2,3]],
+    [{"merge":[[1], [2, 3]]}, null, [1,2,3]],
+    "Given non-array arguments, merge converts them to arrays",
+    [{"merge":1}, null, [1]],
+    [{"merge":[1,2]}, null, [1,2]],
+    [{"merge":[1,[2]]}, null, [1,2]],
+
+    "Too few args",
+    [{"if":[]}, null, null],
+    [{"if":[true]}, null, true],
+    [{"if":[false]}, null, false],
+    [{"if":["apple"]}, null, "apple"],
+
+    "Simple if/then/else cases",
+    [{"if":[true, "apple"]}, null, "apple"],
+    [{"if":[false, "apple"]}, null, null],
+    [{"if":[true, "apple", "banana"]}, null, "apple"],
+    [{"if":[false, "apple", "banana"]}, null, "banana"],
+
+    "Empty arrays are falsey",
+    [{"if":[ [], "apple", "banana"]}, null, "banana"],
+    [{"if":[ [1], "apple", "banana"]}, null, "apple"],
+    [{"if":[ [1,2,3,4], "apple", "banana"]}, null, "apple"],
+
+    "Empty strings are falsey, all other strings are truthy",
+    [{"if":[ "", "apple", "banana"]}, null, "banana"],
+    [{"if":[ "zucchini", "apple", "banana"]}, null, "apple"],
+    [{"if":[ "0", "apple", "banana"]}, null, "apple"],
+
+    "You can cast a string to numeric with a unary + ",
+    [{"===":[0,"0"]}, null, false],
+    [{"===":[0,{"+":"0"}]}, null, true],
+    [{"if":[ {"+":"0"}, "apple", "banana"]}, null, "banana"],
+    [{"if":[ {"+":"1"}, "apple", "banana"]}, null, "apple"],
+
+    "Zero is falsy, all other numbers are truthy",
+    [{"if":[ 0, "apple", "banana"]}, null, "banana"],
+    [{"if":[ 1, "apple", "banana"]}, null, "apple"],
+    [{"if":[ 3.1416, "apple", "banana"]}, null, "apple"],
+    [{"if":[ -1, "apple", "banana"]}, null, "apple"],
+
+    "Truthy and falsy definitions matter in Boolean operations",
+    [{"!" : [ [] ]}, {}, true],
+    [{"!!" : [ [] ]}, {}, false],
+    [{"and" : [ [], true ]}, {}, [] ],
+    [{"or" : [ [], true ]}, {}, true ],
+
+    [{"!" : [ 0 ]}, {}, true],
+    [{"!!" : [ 0 ]}, {}, false],
+    [{"and" : [ 0, true ]}, {}, 0 ],
+    [{"or" : [ 0, true ]}, {}, true ],
+
+    [{"!" : [ "" ]}, {}, true],
+    [{"!!" : [ "" ]}, {}, false],
+    [{"and" : [ "", true ]}, {}, "" ],
+    [{"or" : [ "", true ]}, {}, true ],
+
+    [{"!" : [ "0" ]}, {}, false],
+    [{"!!" : [ "0" ]}, {}, true],
+    [{"and" : [ "0", true ]}, {}, true ],
+    [{"or" : [ "0", true ]}, {}, "0" ],
+
+    "If the conditional is logic, it gets evaluated",
+    [{"if":[ {">":[2,1]}, "apple", "banana"]}, null, "apple"],
+    [{"if":[ {">":[1,2]}, "apple", "banana"]}, null, "banana"],
+
+    "If the consequents are logic, they get evaluated",
+    [{"if":[ true, {"cat":["ap","ple"]}, {"cat":["ba","na","na"]} ]}, null, "apple"],
+    [{"if":[ false, {"cat":["ap","ple"]}, {"cat":["ba","na","na"]} ]}, null, "banana"],
+
+    "If/then/elseif/then cases",
+    [{"if":[true, "apple", true, "banana"]}, null, "apple"],
+    [{"if":[true, "apple", false, "banana"]}, null, "apple"],
+    [{"if":[false, "apple", true, "banana"]}, null, "banana"],
+    [{"if":[false, "apple", false, "banana"]}, null, null],
+
+    [{"if":[true, "apple", true, "banana", "carrot"]}, null, "apple"],
+    [{"if":[true, "apple", false, "banana", "carrot"]}, null, "apple"],
+    [{"if":[false, "apple", true, "banana", "carrot"]}, null, "banana"],
+    [{"if":[false, "apple", false, "banana", "carrot"]}, null, "carrot"],
+
+    [{"if":[false, "apple", false, "banana", false, "carrot"]}, null, null],
+    [{"if":[false, "apple", false, "banana", false, "carrot", "date"]}, null, "date"],
+    [{"if":[false, "apple", false, "banana", true, "carrot", "date"]}, null, "carrot"],
+    [{"if":[false, "apple", true, "banana", false, "carrot", "date"]}, null, "banana"],
+    [{"if":[false, "apple", true, "banana", true, "carrot", "date"]}, null, "banana"],
+    [{"if":[true, "apple", false, "banana", false, "carrot", "date"]}, null, "apple"],
+    [{"if":[true, "apple", false, "banana", true, "carrot", "date"]}, null, "apple"],
+    [{"if":[true, "apple", true, "banana", false, "carrot", "date"]}, null, "apple"],
+    [{"if":[true, "apple", true, "banana", true, "carrot", "date"]}, null, "apple"],
+
+    "Arrays with logic",
+    [[1, {"var": "x"}, 3], {"x": 2}, [1, 2, 3]],
+    [{"if": [{"var": "x"}, [{"var": "y"}], 99]}, {"x": true, "y": 42}, [42]],
+
+    "# Compound Tests",
+    [ {"and":[{">":[3,1]},true]}, {}, true ],
+    [ {"and":[{">":[3,1]},false]}, {}, false ],
+    [ {"and":[{">":[3,1]},{"!":true}]}, {}, false ],
+    [ {"and":[{">":[3,1]},{"<":[1,3]}]}, {}, true ],
+    [ {"?:":[{">":[3,1]},"visible","hidden"]}, {}, "visible" ],
+
+    "# Data-Driven",
+    [ {"var":["a"]},{"a":1},1 ],
+    [ {"var":["b"]},{"a":1},null ],
+    [ {"var":["a"]},null,null ],
+    [ {"var":"a"},{"a":1},1 ],
+    [ {"var":"b"},{"a":1},null ],
+    [ {"var":"a"},null,null ],
+    [ {"var":["a", 1]},null,1 ],
+    [ {"var":["b", 2]},{"a":1},2 ],
+    [ {"var":"a.b"},{"a":{"b":"c"}},"c" ],
+    [ {"var":"a.q"},{"a":{"b":"c"}},null ],
+    [ {"var":["a.q", 9]},{"a":{"b":"c"}},9 ],
+    [ {"var":1}, ["apple","banana"], "banana" ],
+    [ {"var":"1"}, ["apple","banana"], "banana" ],
+    [ {"var":"1.1"}, ["apple",["banana","beer"]], "beer" ],
+    [ {"and":[{"<":[{"var":"temp"},110]},{"==":[{"var":"pie.filling"},"apple"]}]},{"temp":100,"pie":{"filling":"apple"}},true ],
+    [ {"var":[{"?:":[{"<":[{"var":"temp"},110]},"pie.filling","pie.eta"]}]},{"temp":100,"pie":{"filling":"apple","eta":"60s"}},"apple" ],
+    [ {"in":[{"var":"filling"},["apple","cherry"]]},{"filling":"apple"},true ],
+    [ {"var":"a.b.c"}, null, null ],
+    [ {"var":"a.b.c"}, {"a":null}, null ],
+    [ {"var":"a.b.c"}, {"a":{"b":null}}, null ],
+    [ {"var":""}, 1, 1 ],
+    [ {"var":null}, 1, 1 ],
+    [ {"var":[]}, 1, 1 ],
+
+    "Missing",
+    [{"missing":[]}, null, []],
+    [{"missing":["a"]}, null, ["a"]],
+    [{"missing":"a"}, null, ["a"]],
+    [{"missing":"a"}, {"a":"apple"}, []],
+    [{"missing":["a"]}, {"a":"apple"}, []],
+    [{"missing":["a","b"]}, {"a":"apple"}, ["b"]],
+    [{"missing":["a","b"]}, {"b":"banana"}, ["a"]],
+    [{"missing":["a","b"]}, {"a":"apple", "b":"banana"}, []],
+    [{"missing":["a","b"]}, {}, ["a","b"]],
+    [{"missing":["a","b"]}, null, ["a","b"]],
+
+    [{"missing":["a.b"]}, null, ["a.b"]],
+    [{"missing":["a.b"]}, {"a":"apple"}, ["a.b"]],
+    [{"missing":["a.b"]}, {"a":{"c":"apple cake"}}, ["a.b"]],
+    [{"missing":["a.b"]}, {"a":{"b":"apple brownie"}}, []],
+    [{"missing":["a.b", "a.c"]}, {"a":{"b":"apple brownie"}}, ["a.c"]],
+
+
+    "Missing some",
+    [{"missing_some":[1, ["a", "b"]]}, {"a":"apple"}, [] ],
+    [{"missing_some":[1, ["a", "b"]]}, {"b":"banana"}, [] ],
+    [{"missing_some":[1, ["a", "b"]]}, {"a":"apple", "b":"banana"}, [] ],
+    [{"missing_some":[1, ["a", "b"]]}, {"c":"carrot"}, ["a", "b"]],
+
+    [{"missing_some":[2, ["a", "b", "c"]]}, {"a":"apple", "b":"banana"}, [] ],
+    [{"missing_some":[2, ["a", "b", "c"]]}, {"a":"apple", "c":"carrot"}, [] ],
+    [{"missing_some":[2, ["a", "b", "c"]]}, {"a":"apple", "b":"banana", "c":"carrot"}, [] ],
+    [{"missing_some":[2, ["a", "b", "c"]]}, {"a":"apple", "d":"durian"}, ["b", "c"] ],
+    [{"missing_some":[2, ["a", "b", "c"]]}, {"d":"durian", "e":"eggplant"}, ["a", "b", "c"] ],
+
+
+    "Missing and If are friends, because empty arrays are falsey in JsonLogic",
+    [{"if":[ {"missing":"a"}, "missed it", "found it" ]}, {"a":"apple"}, "found it"],
+    [{"if":[ {"missing":"a"}, "missed it", "found it" ]}, {"b":"banana"}, "missed it"],
+
+    "Missing, Merge, and If are friends. VIN is always required, APR is only required if financing is true.",
+    [
+        {"missing":{"merge":[ "vin", {"if": [{"var":"financing"}, ["apr"], [] ]} ]} },
+        {"financing":true},
+        ["vin","apr"]
+    ],
+
+    [
+        {"missing":{"merge":[ "vin", {"if": [{"var":"financing"}, ["apr"], [] ]} ]} },
+        {"financing":false},
+        ["vin"]
+    ],
+
+    "Filter, map, all, none, and some",
+    [
+        {"filter":[{"var":"integers"}, true]},
+        {"integers":[1,2,3]},
+        [1,2,3]
+    ],
+    [
+        {"filter":[{"var":"integers"}, false]},
+        {"integers":[1,2,3]},
+        []
+    ],
+    [
+        {"filter":[{"var":"integers"}, {">=":[{"var":""},2]}]},
+        {"integers":[1,2,3]},
+        [2,3]
+    ],
+    [
+        {"filter":[{"var":"integers"}, {"%":[{"var":""},2]}]},
+        {"integers":[1,2,3]},
+        [1,3]
+    ],
+
+    [
+        {"map":[{"var":"integers"}, {"*":[{"var":""},2]}]},
+        {"integers":[1,2,3]},
+        [2,4,6]
+    ],
+    [
+        {"map":[{"var":"integers"}, {"*":[{"var":""},2]}]},
+        null,
+        []
+    ],
+    [
+        {"map":[{"var":"desserts"}, {"var":"qty"}]},
+        {"desserts":[
+            {"name":"apple","qty":1},
+            {"name":"brownie","qty":2},
+            {"name":"cupcake","qty":3}
+        ]},
+        [1,2,3]
+    ],
+
+    [
+        {"reduce":[
+            {"var":"integers"},
+            {"+":[{"var":"current"}, {"var":"accumulator"}]},
+            0
+        ]},
+        {"integers":[1,2,3,4]},
+        10
+    ],
+    [
+        {"reduce":[
+            {"var":"integers"},
+            {"+":[{"var":"current"}, {"var":"accumulator"}]},
+            {"var": "start_with"}
+        ]},
+        {"integers":[1,2,3,4], "start_with": 59},
+        69
+    ],
+    [
+        {"reduce":[
+            {"var":"integers"},
+            {"+":[{"var":"current"}, {"var":"accumulator"}]},
+            0
+        ]},
+        null,
+        0
+    ],
+    [
+        {"reduce":[
+            {"var":"integers"},
+            {"*":[{"var":"current"}, {"var":"accumulator"}]},
+            1
+        ]},
+        {"integers":[1,2,3,4]},
+        24
+    ],
+    [
+        {"reduce":[
+            {"var":"integers"},
+            {"*":[{"var":"current"}, {"var":"accumulator"}]},
+            0
+        ]},
+        {"integers":[1,2,3,4]},
+        0
+    ],
+    [
+        {"reduce": [
+            {"var":"desserts"},
+            {"+":[ {"var":"accumulator"}, {"var":"current.qty"}]},
+            0
+        ]},
+        {"desserts":[
+            {"name":"apple","qty":1},
+            {"name":"brownie","qty":2},
+            {"name":"cupcake","qty":3}
+        ]},
+        6
+    ],
+
+
+    [
+        {"all":[{"var":"integers"}, {">=":[{"var":""}, 1]}]},
+        {"integers":[1,2,3]},
+        true
+    ],
+    [
+        {"all":[{"var":"integers"}, {"==":[{"var":""}, 1]}]},
+        {"integers":[1,2,3]},
+        false
+    ],
+    [
+        {"all":[{"var":"integers"}, {"<":[{"var":""}, 1]}]},
+        {"integers":[1,2,3]},
+        false
+    ],
+    [
+        {"all":[{"var":"integers"}, {"<":[{"var":""}, 1]}]},
+        {"integers":[]},
+        false
+    ],
+    [
+        {"all":[ {"var":"items"}, {">=":[{"var":"qty"}, 1]}]},
+        {"items":[{"qty":1,"sku":"apple"},{"qty":2,"sku":"banana"}]},
+        true
+    ],
+    [
+        {"all":[ {"var":"items"}, {">":[{"var":"qty"}, 1]}]},
+        {"items":[{"qty":1,"sku":"apple"},{"qty":2,"sku":"banana"}]},
+        false
+    ],
+    [
+        {"all":[ {"var":"items"}, {"<":[{"var":"qty"}, 1]}]},
+        {"items":[{"qty":1,"sku":"apple"},{"qty":2,"sku":"banana"}]},
+        false
+    ],
+    [
+        {"all":[ {"var":"items"}, {">=":[{"var":"qty"}, 1]}]},
+        {"items":[]},
+        false
+    ],
+
+
+    [
+        {"none":[{"var":"integers"}, {">=":[{"var":""}, 1]}]},
+        {"integers":[1,2,3]},
+        false
+    ],
+    [
+        {"none":[{"var":"integers"}, {"==":[{"var":""}, 1]}]},
+        {"integers":[1,2,3]},
+        false
+    ],
+    [
+        {"none":[{"var":"integers"}, {"<":[{"var":""}, 1]}]},
+        {"integers":[1,2,3]},
+        true
+    ],
+    [
+        {"none":[{"var":"integers"}, {"<":[{"var":""}, 1]}]},
+        {"integers":[]},
+        true
+    ],
+    [
+        {"none":[ {"var":"items"}, {">=":[{"var":"qty"}, 1]}]},
+        {"items":[{"qty":1,"sku":"apple"},{"qty":2,"sku":"banana"}]},
+        false
+    ],
+    [
+        {"none":[ {"var":"items"}, {">":[{"var":"qty"}, 1]}]},
+        {"items":[{"qty":1,"sku":"apple"},{"qty":2,"sku":"banana"}]},
+        false
+    ],
+    [
+        {"none":[ {"var":"items"}, {"<":[{"var":"qty"}, 1]}]},
+        {"items":[{"qty":1,"sku":"apple"},{"qty":2,"sku":"banana"}]},
+        true
+    ],
+    [
+        {"none":[ {"var":"items"}, {">=":[{"var":"qty"}, 1]}]},
+        {"items":[]},
+        true
+    ],
+
+    [
+        {"some":[{"var":"integers"}, {">=":[{"var":""}, 1]}]},
+        {"integers":[1,2,3]},
+        true
+    ],
+    [
+        {"some":[{"var":"integers"}, {"==":[{"var":""}, 1]}]},
+        {"integers":[1,2,3]},
+        true
+    ],
+    [
+        {"some":[{"var":"integers"}, {"<":[{"var":""}, 1]}]},
+        {"integers":[1,2,3]},
+        false
+    ],
+    [
+        {"some":[{"var":"integers"}, {"<":[{"var":""}, 1]}]},
+        {"integers":[]},
+        false
+    ],
+    [
+        {"some":[ {"var":"items"}, {">=":[{"var":"qty"}, 1]}]},
+        {"items":[{"qty":1,"sku":"apple"},{"qty":2,"sku":"banana"}]},
+        true
+    ],
+    [
+        {"some":[ {"var":"items"}, {">":[{"var":"qty"}, 1]}]},
+        {"items":[{"qty":1,"sku":"apple"},{"qty":2,"sku":"banana"}]},
+        true
+    ],
+    [
+        {"some":[ {"var":"items"}, {"<":[{"var":"qty"}, 1]}]},
+        {"items":[{"qty":1,"sku":"apple"},{"qty":2,"sku":"banana"}]},
+        false
+    ],
+    [
+        {"some":[ {"var":"items"}, {">=":[{"var":"qty"}, 1]}]},
+        {"items":[]},
+        false
+    ],
+
+    "EOF"
+]
diff --git a/internal/testing.go b/internal/testing.go
index 58bf5e1..afcb0e7 100644
--- a/internal/testing.go
+++ b/internal/testing.go
@@ -6,14 +6,15 @@ import (
 	"io"
 	"log"
 	"net/http"
+	"os"
 	"reflect"
 )
 
 type (
 	Test struct {
-		Rule     io.Reader
-		Data     io.Reader
-		Expected io.Reader
+		Rule     any
+		Data     any
+		Expected any
 	}
 
 	Tests []Test
@@ -31,27 +32,41 @@ func convertInterfaceToReader(i any) io.Reader {
 	return &result
 }
 
-func GetScenariosFromOfficialTestSuite() Tests {
-	var tests Tests
+// This gets the tests.json file that we've proposed become the new official one in
+// https://github.com/jwadhams/json-logic/pull/48 but that hasn't merged yet.
+func GetScenariosFromProposedOfficialTestSuite() Tests {
+	var err error
+	buffer, err := os.ReadFile("internal/json_logic_pr_48_tests.json")
+	if err != nil {
+		log.Fatal(err)
+	}
 
+	return getScenariosFromFile(buffer)
+}
+
+func GetScenariosFromOfficialTestSuite() Tests {
 	response, err := http.Get("http://jsonlogic.com/tests.json")
 	if err != nil {
 		log.Fatal(err)
-
-		return tests
 	}
 
-	buffer, _ := io.ReadAll(response.Body)
+	buffer, err := io.ReadAll(response.Body)
+	if err != nil {
+		log.Fatal(err)
+	}
 
 	response.Body.Close()
+	return getScenariosFromFile(buffer)
+}
 
-	var scenarios []any
-
-	err = json.Unmarshal(buffer, &scenarios)
+func getScenariosFromFile(buffer []byte) Tests {
+	var (
+		tests     Tests
+		scenarios []any
+		err       = json.Unmarshal(buffer, &scenarios)
+	)
 	if err != nil {
 		log.Fatal(err)
-
-		return tests
 	}
 
 	// add missing but relevant scenarios
@@ -69,9 +84,9 @@ func GetScenariosFromOfficialTestSuite() Tests {
 		}
 
 		tests = append(tests, Test{
-			Rule:     convertInterfaceToReader(scenario.([]any)[0]),
-			Data:     convertInterfaceToReader(scenario.([]any)[1]),
-			Expected: convertInterfaceToReader(scenario.([]any)[2]),
+			Rule:     scenario.([]any)[0],
+			Data:     scenario.([]any)[1],
+			Expected: scenario.([]any)[2],
 		})
 	}
 
diff --git a/jsonlogic_test.go b/jsonlogic_test.go
index 90bc105..ec4e8ec 100644
--- a/jsonlogic_test.go
+++ b/jsonlogic_test.go
@@ -4,7 +4,6 @@ import (
 	"bytes"
 	"encoding/json"
 	"fmt"
-	"io"
 	"strings"
 	"testing"
 
@@ -15,24 +14,35 @@ import (
 )
 
 func TestRulesFromJsonLogic(t *testing.T) {
-	tests := internal.GetScenariosFromOfficialTestSuite()
-
-	for i, test := range tests {
-		t.Run(fmt.Sprintf("Scenario_%d", i), func(t *testing.T) {
-			var result bytes.Buffer
-
-			err := jsonlogic.Apply(test.Rule, test.Data, &result)
-			if err != nil {
-				t.Fatal(err)
-			}
+	suites := map[string][]internal.Test{
+		"Official": internal.GetScenariosFromOfficialTestSuite(),
+		"Proposed in https://github.com/jwadhams/json-logic/pull/48": internal.GetScenariosFromProposedOfficialTestSuite(),
+	}
 
-			if b, err := io.ReadAll(test.Expected); err == nil {
-				assert.JSONEq(t, string(b), result.String())
+	for suiteName, tests := range suites {
+		t.Run(suiteName, func(t *testing.T) {
+			for i, test := range tests {
+				t.Run(fmt.Sprintf("Scenario_%d", i), func(t *testing.T) {
+					result, err := jsonlogic.ApplyInterface(test.Rule, test.Data)
+					if err != nil {
+						t.Fatal(err)
+					}
+
+					assert.Equal(t, test.Expected, result, "Applying rule %v to data %v", toJSON(test.Rule), toJSON(test.Data))
+				})
 			}
 		})
 	}
 }
 
+func toJSON(val any) string {
+	res, err := json.Marshal(val)
+	if err != nil {
+		panic(err)
+	}
+	return string(res)
+}
+
 func TestDivWithOnlyOneValue(t *testing.T) {
 	rule := strings.NewReader(`{"/":[4]}`)
 	data := strings.NewReader(`null`)