Skip to content

Implementation of all JSONLogic operators#65

Merged
m-olko merged 18 commits into
mainfrom
json_logic/operators
Mar 11, 2026
Merged

Implementation of all JSONLogic operators#65
m-olko merged 18 commits into
mainfrom
json_logic/operators

Conversation

@m-olko
Copy link
Copy Markdown
Contributor

@m-olko m-olko commented Feb 17, 2026

This PR

Sorry for such huge commit, I experimented with antigravity and still need to learn.

Implements all of the operators for JSONLogic engine, co-authored by antigravity. All of the operators were then checked manually by me and improved for better integration with our codebase and introperability.

Additionally after @tangenti suggestion, added json_logic cli for easier testing and validation.

Related Issues

Fixes #32
Fixes #33
Fixes #34
Fixes #35
Fixes #36

Notes

Result for ./check_progress:

Running official tests...
========================================
Test Progress Report
========================================
Total Tests: 278
Passing:     225
Failing:     53
Success Rate: 80.9%
========================================

Running annotated tests...
========================================
Test Progress Report
========================================
Total Tests: 225
Passing:     225
Failing:     0
Success Rate: 100.0%
========================================

How to test

gbazelisk test //providers/flagd/tests/evaluator/json_logic:json_logic_test
./providers/flagd/tests/evaluator/json_logic/check_progress.py

Signed-off-by: Marcin Olko <molko@google.com>
Signed-off-by: Marcin Olko <molko@google.com>
Signed-off-by: Marcin Olko <molko@google.com>
Signed-off-by: Marcin Olko <molko@google.com>
Signed-off-by: Marcin Olko <molko@google.com>
Signed-off-by: Marcin Olko <molko@google.com>
Signed-off-by: Marcin Olko <molko@google.com>
@m-olko
Copy link
Copy Markdown
Contributor Author

m-olko commented Feb 17, 2026

/gemini review

@gemini-code-assist
Copy link
Copy Markdown
Contributor

Summary of Changes

Hello @m-olko, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly enhances the JSON logic evaluation capabilities by integrating a wide array of new operators across logical, numeric, string, and array categories. It also introduces a more robust error handling mechanism using absl::StatusOr and refines numeric type management for greater accuracy and consistency. These changes expand the expressiveness and reliability of the JSON logic evaluator, allowing for more complex and precise rule definitions.

Highlights

  • Expanded JSON Logic Operators: Implemented a comprehensive set of new JSON logic operators, including logical (or, !, !!, if, ?:, ===, !==), numeric (+, -, *, /, %, min, max, <, <=, >, >=), string (cat, substr, in), array (merge), and data access (missing, missing_some) operations.
  • Improved Error Handling: Migrated the JsonLogic::Apply and JsonLogic::ApplyOp methods, along with all operator functions, to use absl::StatusOr<nlohmann::json> for robust error propagation and handling during evaluation.
  • Robust Numeric Operations: Introduced a new Number class to manage numeric values, ensuring correct type promotion and precision handling across various arithmetic and comparison operations.
  • Refined var Operator Behavior: Updated the var operator to strictly expect string keys for variable access, aligning with standard JSON pointer behavior and improving argument validation.
  • Build System Updates: Modified the BUILD files to include new Abseil dependencies (absl/status:statusor, absl/strings) required for the enhanced error handling and string operations.
Changelog
  • providers/flagd/src/evaluator/json_logic/BUILD
    • Reordered source and header files for consistency.
    • Added new dependencies for Abseil status/statusor and strings.
  • providers/flagd/src/evaluator/json_logic/array.cpp
    • Implemented the Merge array operation.
  • providers/flagd/src/evaluator/json_logic/array.h
    • Declared the Merge function.
    • Included absl/status/statusor.h and json_logic.h.
  • providers/flagd/src/evaluator/json_logic/data.cpp
    • Included std::optional and absl/status/status.h.
    • Refactored Var operator to use absl::StatusOr and improved argument handling.
    • Implemented Missing and MissingSome operators.
  • providers/flagd/src/evaluator/json_logic/data.h
    • Updated Var function signature to return absl::StatusOr<nlohmann::json>.
    • Declared Missing and MissingSome functions.
  • providers/flagd/src/evaluator/json_logic/json_logic.cpp
    • Updated Apply and ApplyOp methods to return absl::StatusOr<nlohmann::json>.
    • Registered numerous new logical, numeric, string, and array operators.
    • Removed automatic array wrapping for operation values.
  • providers/flagd/src/evaluator/json_logic/json_logic.h
    • Modified OpFunc and Apply/ApplyOp signatures to use absl::StatusOr<nlohmann::json>.
  • providers/flagd/src/evaluator/json_logic/logic.cpp
    • Implemented Or, Not, DoubleNegation, If, StrictEquals, and StrictNotEquals operators.
    • Updated And operator to use absl::StatusOr.
  • providers/flagd/src/evaluator/json_logic/logic.h
    • Declared new logical operators and updated And signature.
  • providers/flagd/src/evaluator/json_logic/numeric.cpp
    • Introduced a Number class for robust numeric type handling.
    • Implemented Min, Max, Add, Subtract, Multiply, Divide, Modulo, LessThan, LessThanOrEqual, GreaterThan, and GreaterThanOrEqual operators.
  • providers/flagd/src/evaluator/json_logic/numeric.h
    • Declared new numeric operators.
  • providers/flagd/src/evaluator/json_logic/string_ops.cpp
    • Implemented Cat, Substr, and In operators.
  • providers/flagd/src/evaluator/json_logic/string_ops.h
    • Declared new string operators.
  • providers/flagd/tests/evaluator/json_logic/BUILD
    • Removed the ops_test target.
  • providers/flagd/tests/evaluator/json_logic/generate_tests_annotations.py
    • Updated var operator handling to expect string keys only.
  • providers/flagd/tests/evaluator/json_logic/json_logic_annotated_test.cpp
    • Updated test assertions to correctly handle absl::StatusOr return types.
  • providers/flagd/tests/evaluator/json_logic/json_logic_official_test.cpp
    • Updated test assertions to correctly handle absl::StatusOr return types.
  • providers/flagd/tests/evaluator/json_logic/json_logic_test.cpp
    • Updated test assertions to correctly handle absl::StatusOr return types.
    • Modified CustomOperation lambda signature to return absl::StatusOr<json>.
  • providers/flagd/tests/evaluator/json_logic/tests_annotated.json
    • Removed a test case for the var operator with an integer key.
Activity
  • The pull request introduces a new feature to expand JSON logic operators.
  • This pull request addresses and fixes issue #1234523.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request is a significant enhancement to the JSON logic evaluator, adding a wide range of operators for data access, logic, numeric computations, and string manipulations. The transition to using absl::StatusOr for error handling is a great improvement for robustness.

My review focuses on a few areas to further improve consistency and correctness:

  • Error Handling: There's an inconsistency in how errors are propagated. In some cases, they are swallowed and converted to null, while in others, the status is passed up. Consistently propagating the status, as highlighted by the rule regarding absl::StatusOr propagation, would make debugging easier.
  • Code Clarity: Some small improvements can be made for code clarity, such as replacing a generic catch(...) and using more idiomatic C++ for loops.
  • API Contract: A comment regarding the API for operator functions seems to be out of sync with the implementation, which could lead to confusion.

Overall, this is a very solid contribution with well-structured code, especially the Number class for handling numeric operations. The suggested changes are aimed at making the implementation even more robust and maintainable.

Comment thread providers/flagd/src/evaluator/json_logic/json_logic.cpp Outdated
Comment thread providers/flagd/src/evaluator/json_logic/json_logic.h Outdated
Comment thread providers/flagd/src/evaluator/json_logic/array.cpp Outdated
Comment thread providers/flagd/src/evaluator/json_logic/data.cpp
Comment thread providers/flagd/tests/evaluator/json_logic/json_logic_test.cpp Outdated
Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request implements a wide range of operators for the JsonLogic evaluator, including data access, logic, numeric, string, and array operations. The implementation correctly transitions to using absl::StatusOr for robust error handling throughout the operator functions. My review focuses on ensuring correct error propagation, improving code clarity, and addressing potential issues. I've identified several critical compilation errors related to incorrect absl::StatusOr return types and a high-severity issue where error statuses are being suppressed, which directly align with the rule regarding absl::StatusOr error propagation. Additionally, I've provided suggestions to improve code conciseness and adhere to best practices like avoiding catch(...).

Comment thread providers/flagd/src/evaluator/json_logic/logic.cpp Outdated
Comment thread providers/flagd/src/evaluator/json_logic/logic.cpp Outdated
Comment thread providers/flagd/src/evaluator/json_logic/logic.cpp Outdated
Comment thread providers/flagd/src/evaluator/json_logic/json_logic.cpp Outdated
Comment thread providers/flagd/src/evaluator/json_logic/array.cpp
Comment thread providers/flagd/src/evaluator/json_logic/data.cpp
Signed-off-by: Marcin Olko <molko@google.com>
Signed-off-by: Marcin Olko <molko@google.com>
Signed-off-by: Marcin Olko <molko@google.com>
@m-olko
Copy link
Copy Markdown
Contributor Author

m-olko commented Feb 17, 2026

/gemini review

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request is a significant feature addition, implementing a wide range of operators for the JsonLogic evaluator, including data access, logical, numeric, string, and array operations. The code is well-structured, with new operators organized into separate files and a robust Number class for handling various numeric types. My review identifies a few areas for improvement: a potential bug in large unsigned number arithmetic, a suggestion to enhance error handling robustness by using more specific exception catches, and recommendations to align the behavior of equality operators with the reference implementation for certain edge cases to ensure predictability.

Comment thread providers/flagd/src/evaluator/json_logic/numeric.cpp Outdated
Comment thread providers/flagd/src/evaluator/json_logic/data.cpp
Comment thread providers/flagd/src/evaluator/json_logic/logic.cpp Outdated
Comment thread providers/flagd/src/evaluator/json_logic/logic.cpp Outdated
Signed-off-by: Marcin Olko <molko@google.com>
@m-olko
Copy link
Copy Markdown
Contributor Author

m-olko commented Feb 18, 2026

/gemini review

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a comprehensive set of JsonLogic operators. The implementation is extensive and aligns well with the standard json-logic specification. The code is well-structured. My review includes suggestions to enhance robustness and correctness, particularly regarding absl::StatusOr error propagation and numeric operations.

Comment thread providers/flagd/src/evaluator/json_logic/data.cpp
Comment on lines +178 to +181
if (rhs_val == 0) {
return absl::InvalidArgumentError("Modulo by zero");
}
return Number(lhs_val % rhs_val);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The current implementation of integer modulo using the % operator can produce incorrect results for mixed signed and unsigned integer types due to C++'s integer promotion rules. For instance, an operation like -1 % 5u would evaluate incorrectly because the signed -1 is promoted to a large unsigned value before the modulo operation.

To ensure correct behavior across all integer types, it's safer to handle mixed-sign cases by promoting to floating-point numbers and using std::fmod. This prevents unexpected outcomes from implicit type promotions.

            if (rhs_val == 0) {
              return absl::InvalidArgumentError("Modulo by zero");
            }
            if constexpr (std::is_signed_v<decltype(lhs_val)> !=
                          std::is_signed_v<decltype(rhs_val)>) {
              return Number(std::fmod(static_cast<double>(lhs_val),
                                      static_cast<double>(rhs_val)));
            } else {
              return Number(lhs_val % rhs_val);
            }

@m-olko m-olko changed the title Json logic/operators Implementation of all JSONLogic operators Feb 18, 2026
@tangenti
Copy link
Copy Markdown

The PR is still marked as a draft - is it ready for review?

@m-olko m-olko marked this pull request as ready for review February 26, 2026 08:28
@m-olko
Copy link
Copy Markdown
Contributor Author

m-olko commented Feb 26, 2026

The PR is still marked as a draft - is it ready for review?

Sorry, I missed that. Yes, it is ready for review.

@m-olko
Copy link
Copy Markdown
Contributor Author

m-olko commented Feb 27, 2026

@oxddr @tangenti Could you take a look on this PR?

m-olko added 2 commits March 2, 2026 16:38
Signed-off-by: Marcin Olko <molko@google.com>
Signed-off-by: Marcin Olko <molko@google.com>
@m-olko
Copy link
Copy Markdown
Contributor Author

m-olko commented Mar 3, 2026

FYI, This is the information from generate_test_annotations.py showing how many tests were excluded and why:

Included 225 tests.
Skipped 53 tests.

Skipped Reasons Summary:

- 8 tests: Uses ignored operator 'all'
    Logic: {"all": [{"var": "integers"}, {">=": [{"var": ""}, 1]}]}  Expected: true
    Logic: {"all": [{"var": "integers"}, {"==": [{"var": ""}, 1]}]}  Expected: false
    Logic: {"all": [{"var": "integers"}, {"<": [{"var": ""}, 1]}]}  Expected: false
    Logic: {"all": [{"var": "integers"}, {"<": [{"var": ""}, 1]}]}  Expected: false
    Logic: {"all": [{"var": "items"}, {">=": [{"var": "qty"}, 1]}]}  Expected: true
    Logic: {"all": [{"var": "items"}, {">": [{"var": "qty"}, 1]}]}  Expected: false
    Logic: {"all": [{"var": "items"}, {"<": [{"var": "qty"}, 1]}]}  Expected: false
    Logic: {"all": [{"var": "items"}, {">=": [{"var": "qty"}, 1]}]}  Expected: false

- 8 tests: Uses ignored operator 'none'
    Logic: {"none": [{"var": "integers"}, {">=": [{"var": ""}, 1]}]}  Expected: false
    Logic: {"none": [{"var": "integers"}, {"==": [{"var": ""}, 1]}]}  Expected: false
    Logic: {"none": [{"var": "integers"}, {"<": [{"var": ""}, 1]}]}  Expected: true
    Logic: {"none": [{"var": "integers"}, {"<": [{"var": ""}, 1]}]}  Expected: true
    Logic: {"none": [{"var": "items"}, {">=": [{"var": "qty"}, 1]}]}  Expected: false
    Logic: {"none": [{"var": "items"}, {">": [{"var": "qty"}, 1]}]}  Expected: false
    Logic: {"none": [{"var": "items"}, {"<": [{"var": "qty"}, 1]}]}  Expected: true
    Logic: {"none": [{"var": "items"}, {">=": [{"var": "qty"}, 1]}]}  Expected: true

- 8 tests: Uses ignored operator 'some'
    Logic: {"some": [{"var": "integers"}, {">=": [{"var": ""}, 1]}]}  Expected: true
    Logic: {"some": [{"var": "integers"}, {"==": [{"var": ""}, 1]}]}  Expected: true
    Logic: {"some": [{"var": "integers"}, {"<": [{"var": ""}, 1]}]}  Expected: false
    Logic: {"some": [{"var": "integers"}, {"<": [{"var": ""}, 1]}]}  Expected: false
    Logic: {"some": [{"var": "items"}, {">=": [{"var": "qty"}, 1]}]}  Expected: true
    Logic: {"some": [{"var": "items"}, {">": [{"var": "qty"}, 1]}]}  Expected: true
    Logic: {"some": [{"var": "items"}, {"<": [{"var": "qty"}, 1]}]}  Expected: false
    Logic: {"some": [{"var": "items"}, {">=": [{"var": "qty"}, 1]}]}  Expected: false

- 6 tests: Uses ignored operator 'reduce'
    Logic: {"reduce": [{"var": "integers"}, {"+": [{"var": "current"}, {"var": "accumulator"}]}, 0]}  Expected: 10
    Logic: {"reduce": [{"var": "integers"}, {"+": [{"var": "current"}, {"var": "accumulator"}]}, {"var": "start_with"}]}  Expected: 69
    Logic: {"reduce": [{"var": "integers"}, {"+": [{"var": "current"}, {"var": "accumulator"}]}, 0]}  Expected: 0
    Logic: {"reduce": [{"var": "integers"}, {"*": [{"var": "current"}, {"var": "accumulator"}]}, 1]}  Expected: 24
    Logic: {"reduce": [{"var": "integers"}, {"*": [{"var": "current"}, {"var": "accumulator"}]}, 0]}  Expected: 0
    Logic: {"reduce": [{"var": "desserts"}, {"+": [{"var": "accumulator"}, {"var": "current.qty"}]}, 0]}  Expected: 6

- 4 tests: Uses ignored operator 'filter'
    Logic: {"filter": [{"var": "integers"}, true]}  Expected: [1, 2, 3]
    Logic: {"filter": [{"var": "integers"}, false]}  Expected: []
    Logic: {"filter": [{"var": "integers"}, {">=": [{"var": ""}, 2]}]}  Expected: [2, 3]
    Logic: {"filter": [{"var": "integers"}, {"%": [{"var": ""}, 2]}]}  Expected: [1, 3]

- 3 tests: Uses ignored operator 'map'
    Logic: {"map": [{"var": "integers"}, {"*": [{"var": ""}, 2]}]}  Expected: [2, 4, 6]
    Logic: {"map": [{"var": "integers"}, {"*": [{"var": ""}, 2]}]}  Expected: []
    Logic: {"map": [{"var": "desserts"}, {"var": "qty"}]}  Expected: [1, 2, 3]

- 2 tests: Operator '+' has non-numeric literal argument: 1 (type: str)
    Logic: {"+": ["1", 1]}  Expected: 2
    Logic: {"if": [{"+": "1"}, "apple", "banana"]}  Expected: "apple"

- 2 tests: Operator '+' has non-numeric literal argument: 0 (type: str)
    Logic: {"===": [0, {"+": "0"}]}  Expected: true
    Logic: {"if": [{"+": "0"}, "apple", "banana"]}  Expected: "banana"

- 1 tests: Operator '==' has mismatched types: int and str
    Logic: {"==": [1, "1"]}  Expected: true

- 1 tests: Operator '!=' has mismatched types: int and str
    Logic: {"!=": [1, "1"]}  Expected: false

- 1 tests: Operator '>' has non-numeric literal argument: 2 (type: str)
    Logic: {">": ["2", 1]}  Expected: true

- 1 tests: Operator '>=' has non-numeric literal argument: 2 (type: str)
    Logic: {">=": ["2", 1]}  Expected: true

- 1 tests: Operator '<' has non-numeric literal argument: 1 (type: str)
    Logic: {"<": ["1", 2]}  Expected: true

- 1 tests: Operator '<=' has non-numeric literal argument: 1 (type: str)
    Logic: {"<=": ["1", 2]}  Expected: true

- 1 tests: Operator 'cat' has non-string literal argument: 1
    Logic: {"cat": [1, 2]}  Expected: "12"

- 1 tests: Operator 'cat' has non-string literal argument: 2
    Logic: {"cat": ["Robocop", 2]}  Expected: "Robocop2"

- 1 tests: Operator '*' has non-numeric literal argument: 1 (type: str)
    Logic: {"*": ["1", 1]}  Expected: 1

- 1 tests: Operator '-' has non-numeric literal argument: 1 (type: str)
    Logic: {"-": ["1", 1]}  Expected: 0

- 1 tests: Operator '/' has non-numeric literal argument: 1 (type: str)
    Logic: {"/": ["1", 1]}  Expected: 1

- 1 tests: Operator 'var' has non-string key: 1 (type: int)
    Logic: {"var": 1}  Expected: "banana"

m-olko added 4 commits March 5, 2026 09:13
Signed-off-by: Marcin Olko <molko@google.com>
Signed-off-by: Marcin Olko <molko@google.com>
Signed-off-by: Marcin Olko <molko@google.com>
Signed-off-by: Marcin Olko <molko@google.com>
Comment thread providers/flagd/src/evaluator/json_logic/cli.cpp Outdated
Comment thread providers/flagd/src/evaluator/json_logic/data.cpp
const nlohmann::json& data,
std::string_view op_name) {
auto resolved_res = eval.Apply(values, data);
if (!resolved_res.ok()) return resolved_res.status();
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that's a lot of auto... :)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I removed as much as possible now, but in the Number object, those auto allow for easily templating lambdas. In C++20 you can have Explicit template paramter list for lambdas, but in C++17 you either need to use auto, or create code with much more boilerplate, so I chose the first option. I think in this Number setup, it is quite readable what the code is trying to accomplish.

Signed-off-by: Marcin Olko <molko@google.com>
@m-olko m-olko requested a review from alichka06 March 11, 2026 10:08
@m-olko m-olko merged commit 160c7de into main Mar 11, 2026
3 checks passed
@m-olko m-olko deleted the json_logic/operators branch March 11, 2026 13:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

4 participants