valijson

repository·master·Indexed 19 days ago

https://github.com/tristanpenman/valijson

A header-only JSON Schema validation library for Modern C++ that supports JSON Schema v7. It uses an adapter pattern to remain compatible with various JSON parser libraries (such as RapidJSON, NlohmannJSON, and JsonCpp) and provides version lines for C++14 (v1.0.x), C++17 (v1.1.x), and C++20 (v1.2.x).

Tokens
6.6K
Snippets
18
Records
29
Agent score
64%

What's inside valijson

  1. How JSON References are resolved during parsing

    master

    During the parsing process, Valijson identifies JSON References (objects containing the "$ref": "..." key).

    1. Internal References: If the reference points to another part of the current document, it is resolved locally.
    2. External References: If the reference points to an external document, Valijson uses the fetchDoc callback provided to populateSchema() to retrieve the document.
    3. Caching: Resolved external documents are stored in a DocumentCache to minimize redundant network/file operations and to help resolve circular references.
  2. Understand URI resolution edge cases in valijson::internal::uri

    master

    The function valijson::internal::uri::resolveRelativeUri is used to resolve URI references against a base resolution scope.

    Warning for Developers: The current implementation has several documented deviations from the RFC 3986 standard. If your application relies on strict RFC 3986 compliance for URI resolution, be aware of the following behaviors:

    • Scheme Detection: The implementation only recognizes schemes followed by ://. Absolute references like mailto:user@example.com, data:..., or file:/... may not be recognized as absolute and might be incorrectly resolved against the base URI.
    • Network-path References: References starting with // are treated as absolute paths that retain the old authority, rather than supplying a new authority. This also causes leading slashes to be collapsed.
    • Query and Fragment Handling: Resolving a fragment-only reference (e.g., #s) against a base URI that contains a query (e.g., ?q) will cause the base query to be lost.
    • Dot Segments: Terminal . and .. segments do not preserve the trailing slash that RFC 3986 implies.
    • Path Normalization: The implementation discards empty path segments (e.g., g//h becomes g/h).
    • Authority Extraction: The implementation may incorrectly include query strings or fragments in the authority if the base URI does not contain a / before the ? or #.
  3. Understand Constraints and the Visitor Pattern

    master

    In Valijson, JSON Schema validation keywords (like required, minimum, or type) are represented internally as Constraints.

    Constraints are implemented as data objects designed to be used with the Visitor Pattern. When you perform validation, a ValidationVisitor traverses these constraint objects to apply the validation logic to a target JSON document.

  4. How the Visitor Pattern is used for validation

    master
    Valijson uses the Visitor Pattern to implement validation. This allows the validation logic to remain independent of the specific JSON parser being used. The pattern is chosen because it aligns with the hierarchical and recursive structure of both JSON documents and JSON schemas.
  5. Choose the correct C++ standard version

    master

    Valijson is maintained across different version lines based on the required C++ standard:

    • v1.0.x: Legacy support for C++14.
    • v1.1.x (master branch): Current series targeting C++17.
    • v1.2.x: Planned series targeting C++20.

    Code written for v1.0.x (C++14) should generally migrate to v1.1.x (C++17) with minimal adjustments.

  6. JSON Schema support and limitations

    master

    Valijson supports validation keywords defined in JSON Schema Draft 7.

    Key details:

    • The default keyword is annotation-only and does not affect validation.
    • Enforced formats: date, time, date-time, and ipv4.
    • Local JSON References: Supported natively.
    • Remote references: Supported only when document-fetch and document-release callbacks are provided to SchemaParser::populateSchema().
  7. Manage memory for manually constructed schemas

    master

    When building a schema programmatically using the Schema class, Valijson uses RAII semantics. The root Schema object manages the memory for all allocated constraints and sub-schemas.

    • schema.createSubschema() returns a const Subschema*. This memory is owned by the root Schema and is freed when the root object goes out of scope.
    • Constraints added via schema.addConstraintToSubschema or schema.addConstraint are copied into the memory managed by the root Schema object.
    {
        Schema schema;
        const Subschema *subschema = schema.createSubschema();
    
        {
            TypeConstraint typeConstraint;
            typeConstraint.addNamedType(TypeConstraint::kString);
            schema.addConstraintToSubschema(typeConstraint, subschema);
        }
    
        PropertiesConstraint propertiesConstraint;
        propertiesConstraint.addPropertySubschema("description", subschema);
        schema.addConstraint(propertiesConstraint);
    }
    // All allocated memory is freed here when 'schema' goes out of scope
  8. Configure Strong vs Weak typing

    master

    You can control how strictly Valijson treats types during validation by passing a flag to the Validator constructor:

    • Strong Typing (Validator::kStrongTypes): The default. It does not attempt to cast between types (e.g., the string "23" will not satisfy a number constraint).
    • Weak Typing (Validator::kWeakTypes): Attempts to cast values to satisfy the schema (useful for libraries like Boost Property Tree that store all values as strings).
    // Strong typing (default)
    Validator validator;
    // or
    Validator validator(Validator::kStrongTypes);
    
    // Weak typing
    Validator validator(Validator::kWeakTypes);
  9. How subschemas are handled in recursive parsing

    master

    Keywords that require a nested schema (such as properties, additionalProperties, or patternProperties) trigger a recursive parsing step.

    Valijson uses makeOrReuseSchema() to handle this. This function manages the creation of new subschemas or the reuse of existing ones from the SchemaRegistry, ensuring that the hierarchical structure of the JSON Schema is correctly captured in the internal Schema representation.

    template<typename AdapterType>
    const Subschema * makeOrReuseSchema(
        Schema &rootSchema,
        const AdapterType &rootNode,
        const AdapterType &node,
        const opt::optional<std::string> currentScope,
        const std::string &nodePath,
        const typename FunctionPtrs<AdapterType>::FetchDoc fetchDoc,
        const Subschema *parentSubschema,
        const std::string *ownName,
        typename DocumentCache<AdapterType>::Type &docCache,
        SchemaRegistry &schemaRegistry);
  10. How Valijson's adapter architecture works

    master

    Valijson uses an adapter-based architecture to support multiple JSON parser libraries (such as NlohmannJSON, RapidJSON, and JsonCpp) without sacrificing performance.

    Instead of using heavy dynamic dispatch (polymorphism) for all operations, Valijson uses C++ Class Templates (a variation of Policy-based Design). This allows the compiler to optimize the validation logic specifically for the chosen parser, providing performance competitive with hand-written validators.

    Key Concepts:

    • Parser Adapter: A facade for a specific JSON parser that conforms to the interface required by Valijson's validator.
    • Static Dispatch: Used for the performance-critical validation paths via templates.
    • Dynamic Dispatch & Frozen Values: Used when the validator needs to store schema values (like those in an enum or const keyword) that must be compared against documents from potentially different parsers. The adapter provides a freeze operation that type-erases the data into a FrozenValue object, allowing the validator to interact with it through a uniform virtual interface.
  11. Configure Strict vs Permissive Date/Time formats

    master

    When validating date-time formats (RFC 3339), you can choose between strict and permissive modes via the Validator constructor:

    • Strict (Validator::kStrictDateTime): The default. Requires unambiguous date/time strings with local time zone modifiers (e.g., Z or +01:00).
    • Permissive (Validator::kPermissiveDateTime): Allows ambiguous date/time strings.
    // Permissive mode
    Validator validator(Validator::kStrongTypes, Validator::kPermissiveDateTime);
    
    // Strict mode (default)
    Validator validator(Validator::kStrongTypes, Validator::kStrictDateTime);
  12. Integrate Valijson as a Git submodule

    master

    You can add Valijson as a submodule in your project's third-party directory. When using this method, ensure you disable tests and examples in your CMakeLists.txt before adding the subdirectory. Link against the ValiJSON::valijson target.

    # Option 1: Clone with submodules
    git clone --recurse-submodules https://github.com/tristanpenman/valijson <project-path>/third-party/valijson
    
    # Option 2: Add to existing git repo
    cd <project-path>
    git submodule add https://github.com/tristanpenman/valijson third-party/valijson
    set(valijson_BUILD_TESTS OFF CACHE BOOL "Don't build valijson tests" FORCE)
    set(valijson_BUILD_EXAMPLES OFF CACHE BOOL "Don't build valijson examples" FORCE)
    add_subdirectory(third-party/valijson)
    
    add_executable(your-executable ...)
    
    target_link_libraries(your-executable ValiJSON::valijson)