danielaparker / jsoncons

A C++, header-only library for constructing JSON and JSON-like data formats, with JSON Pointer, JSON Patch, JSON Schema, JSONPath, JMESPath, CSV, MessagePack, CBOR, BSON, UBJSON
https://danielaparker.github.io/jsoncons
Other
697 stars 160 forks source link

Big numbers can be incorrectly rejected by schema validation #521

Closed Bktero closed 2 months ago

Bktero commented 2 months ago

While validating a schema, some big numbers are considered as invalid.

Here is a code to reproduce the issue:

#include <jsoncons/json.hpp>
#include <jsoncons_ext/jsonschema/jsonschema.hpp>
#include <iostream>
#include <string>

using jsoncons::ojson;
namespace jsonschema = jsoncons::jsonschema;

int main() {
    std::string schema_str = R"(
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$defs": {
    "hexaStringOrPositiveInteger": {
      "description": "A hexadecimal integer in a string (eg: 0xHE770 or HE770) or a positive integer",
      "anyOf": [
        {
          "type": "string",
          "pattern": "^(0x)?[0-9a-fA-F]+$"
        },
        {
          "type": "integer",
          "minimum": 0
        }
      ]
    }
  },
  "type": "array",
  "items": {
    "$ref": "#/$defs/hexaStringOrPositiveInteger"
  }
}
  )";

    std::string data_str = R"(
[
    "0x7FFFFFFFFFFFFFFF",
    9223372036854775807,

    "0xFFFFFFFFFFFFFFFF",
    18446744073709551615,

    "0x1FFFFFFFFFFFFFFFF",
    36893488147419103231,

    "0xFFFFFFFFFFFFFFFFF",
    295147905179352825855
]
    )";

    ojson schema = ojson::parse(schema_str);
    jsonschema::json_schema<ojson> compiled = jsonschema::make_json_schema(std::move(schema));
    ojson data = ojson::parse(data_str);

    puts("Try...");
    try {
        compiled.validate(data);
        puts("OK");
    }
    catch (const std::exception &e) {
        puts("Catched");
        std::cout << e.what() << "\n";
    }
}

I compile this code with on Debian with GCC:

% gcc --version 
gcc (Debian 12.2.0-14) 12.2.0
Copyright (C) 2022 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

The actual output with jsoncons v0.175.0 is:

Try...
Catched
/3: Must be valid against at least one schema, but found no matching schemas

I would expect:

Try...
OK

About my JSON schema: I want user to enter either integer or hexastring to represent addresses inside a computer's memory, so basically std::uint64_t values. This is why I have my custom hexaStringOrPositiveInteger type.

About the test data: each couple of data is the same value in both hexa and decimal format. Other numbers close to 0xFFFFFFFFFFFFFFFF (the max value on 64 bits) are also several rejected.

This data is properly validated by at least one other tool (namely https://www.jsonschemavalidator.net/).

Bktero commented 2 months ago

As I was trying to reduce even more the code, I realized that the validation stops on the first error.

Here is a code:

#include <jsoncons/json.hpp>
#include <jsoncons_ext/jsonschema/jsonschema.hpp>
#include <iostream>
#include <string>

using jsoncons::ojson;
namespace jsonschema = jsoncons::jsonschema;

void validate(const char *data_str) {
    std::string schema_str = R"(
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "integer"
}
  )";

    ojson schema = ojson::parse(schema_str);
    jsonschema::json_schema<ojson> compiled = jsonschema::make_json_schema(std::move(schema));
    ojson data = ojson::parse(data_str);

    puts("-------------------------------");
    puts("Try...");
    puts(data_str);
    try {
        compiled.validate(data);
        puts("OK");
    }
    catch (const std::exception &e) {
        puts("Catched");
        std::cout << e.what() << "\n";
    }
}

int main() {
    validate("9223372036854775807");
    validate("18446744073709551610");
    validate("18446744073709551615");
    validate("36893488147419103231");
    validate("295147905179352825855");
}

The output is:

-------------------------------
Try...
9223372036854775807
OK
-------------------------------
Try...
18446744073709551610
Catched
: Expected integer, found integer
-------------------------------
Try...
18446744073709551615
Catched
: Expected integer, found integer
-------------------------------
Try...
36893488147419103231
Catched
: Expected integer, found string
-------------------------------
Try...
295147905179352825855
Catched
: Expected integer, found string
danielaparker commented 2 months ago

@Bktero, Thanks for raising these issues, they should be resolved now on master. Can you verify?

Bktero commented 2 months ago

Hello @danielaparker

I have fetched the commit in my CMakeLists.txt.

My 1st code still prints "/3: Must be valid against at least one schema, but found no matching schemas". However, my second code now prints "OK" for each value.

I have combined my 2 examples into one.

#include <jsoncons/json.hpp>
#include <jsoncons_ext/jsonschema/jsonschema.hpp>
#include <iostream>
#include <string>

using jsoncons::ojson;
namespace jsonschema = jsoncons::jsonschema;

void validate(const char *data_str) {
    std::string schema_str = R"(
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$defs": {
    "hexaStringOrPositiveInteger": {
      "description": "A hexadecimal integer in a string (eg: 0xHE770 or HE770) or a positive integer",
      "anyOf": [
        {
          "type": "string",
          "pattern": "^(0x)?[0-9a-fA-F]+$"
        },
        {
          "type": "integer",
          "minimum": 0
        }
      ]
    }
  },
  "$ref": "#/$defs/hexaStringOrPositiveInteger"
}
  )";

    puts("-------------------------------");
    puts("Try...");
    puts(data_str);

    ojson schema = ojson::parse(schema_str);
    jsonschema::json_schema<ojson> compiled = jsonschema::make_json_schema(std::move(schema));
    ojson data = ojson::parse(data_str);

    try {
        compiled.validate(data);
        puts("OK");
    }
    catch (const std::exception &e) {
        puts("Catched");
        std::cout << e.what() << "\n";
    }
}

int main() {
    validate(R"("0x7FFFFFFFFFFFFFFF")");
    validate(R"(9223372036854775807)");
    validate(R"("0xFFFFFFFFFFFFFFFF")");
    validate(R"(18446744073709551615)");
    validate(R"("0x1FFFFFFFFFFFFFFFF")");
    validate(R"(36893488147419103231)");
    validate(R"("0xFFFFFFFFFFFFFFFFF")");
    validate(R"(295147905179352825855)");
}

The output is:

-------------------------------
Try...
"0x7FFFFFFFFFFFFFFF"
"0x7FFFFFFFFFFFFFFF", n/a
OK
-------------------------------
Try...
9223372036854775807
9223372036854775807, n/a
OK
-------------------------------
Try...
"0xFFFFFFFFFFFFFFFF"
"0xFFFFFFFFFFFFFFFF", n/a
OK
-------------------------------
Try...
18446744073709551615
18446744073709551615, n/a
Catched
: Must be valid against at least one schema, but found no matching schemas
-------------------------------
Try...
"0x1FFFFFFFFFFFFFFFF"
"0x1FFFFFFFFFFFFFFFF", n/a
OK
-------------------------------
Try...
36893488147419103231
"36893488147419103231", bigint
OK
-------------------------------
Try...
"0xFFFFFFFFFFFFFFFFF"
"0xFFFFFFFFFFFFFFFFF", n/a
OK
-------------------------------
Try...
295147905179352825855
"295147905179352825855", bigint
OK

Note that you have committed a debug trace ;)

So 18446744073709551615 works when validated as an integer, but not as an union of an integer and a string.

danielaparker commented 2 months ago

Just FYI, you can use

jsoncons::json_decoder<ojson> decoder;
compiled.validate(data, decoder);
ojson result = decoder.get_result();
std::cout << pretty_print(result) << "\n";

to print all the errors.

I don't think you're checking the right commit, the code that you showed with the debug trace is old, the line with the debug trace was removed before my last push to master yesterday. Your test case is in the jsonschema test suite, and it passes for all compilers that we test with.

Bktero commented 2 months ago

I was checking the commit mentioned in this issue's activity. Sorry.

The latest commit of the main branch (currently 7e2f789d64121412bec0c3df093b0b1be1ecf634) works fine. Thanks! :)

You can close this issue.