pboettch / json-schema-validator

JSON schema validator for JSON for Modern C++
Other
466 stars 134 forks source link

A way to validate object against specific sub definition in schema #149

Closed qu1ck closed 2 years ago

qu1ck commented 3 years ago

Say there is a schema with sub definitions A and B while A also references B

{
    "$schema": "http://json-schema.org/draft-07/schema#",
    "type": "integer",
    "definitions": {
        "A": {
            "type": "object",
            "properties": {
                "b": {
                    "$ref": "#/definitions/B"
                }
            }
        },
        "B": {
            "type": "integer"
        }
    }
}

Is there a way to validate that an object conforms to schema A?

pboettch commented 3 years ago

Yes, that's possible. Of course I'd say.

What have you tried to achieve it? (It's a little bit off-topic here, without giving you the solution, I'd prefer you to find out yourself)

qu1ck commented 3 years ago

I haven't tried anything as in writing code, I just looked at the api in json-schema.hpp and I see no way to specify anywhere that validation should happen against schema #/definitions/A.

I only see API to set root schema and to validate json object against that root schema.

If A didn't reference B then I could set root schema as root["definitions"]["A"] but then $ref will not be able to resolve #/definitions/B.

What am I missing?

pboettch commented 3 years ago

Oh, I see. I'm assuming you're just starting with schema?

There is nothing to be done on the C++-API of the validator for what you want to do. Everything has to be defined in the schema. The validator just "compiles" the schema and then uses it as is to validate instances. So, in your schema from above you define the root-schema as integer, so effectively the part in the definitions can't be referenced inside the same schema.

If you use root-schema of object type, then you can:

{
    "$schema": "http://json-schema.org/draft-07/schema#",
    "type": "object",
    "definitions": {
        "A": {
            "type": "object",
            "properties": {
                "b": {
                    "$ref": "#/definitions/B"
                }
            }
        },
        "B": {
            "type": "integer"
        }
    },
    "properties": {
        "propname": { "$ref": "#/definitions/B"}
    }
}

Would validate an instance like this

{
  "propname": 1
}
qu1ck commented 3 years ago

Yeah, sorry if my example was a bit too simplistic, I used integer to just show that what root schema describes itself is not important.

The actual problem I want to solve looks like this (only hierarchy of definitions described)

repository
+package
+packageversion
+resource

And both repository and packageversion have resource fields and package has array of packageversion fields. And of course repository has array of package fields.

Now sometimes I have a json object of package that I want to validate and I don't have a whole repository. Can I do that using this common schema or do I have to break it up into several schemas and potentially redefine resource ?

In my mind the API for doing that would look something like validator.set_root_schema(schema) and then validator.validate(object, "#/definitions/package") to specify that the object should conform to package definition within the schema and not the whole schema.

pboettch commented 3 years ago

OK, I see, sorry to have taken you as a newbie.

You'd be surprised, or maybe not, but there is a request which resembles yours quite a bit: #135

There, the idea is to do one parsing of all schemas and sub-schemas and then create linked validator-instances based on a json-paths (IIRC). (Additionally dynamic additions of new sub-schemas without reparsing everything again)

That would solve your problem as well, no?

json_validator validator(loader);
validator.set_root_schema(schema);

auto &sub_validator = validator.get_some_good_name("#/definitions/package")
sub_validator.validate(package_instance);

How would you know which json-path to pick based on the instance-type?

Would you allow sub-schema breakouts when a reference in your selection sub-schema has a reference to a schema outside it's scope? Because in my idea it would work.

pboettch commented 3 years ago

Adding the validator.get_some_good_name("#/definitions/package")-method would be more complicated than implementing your validator.validate(object, "#/definitions/package") idea.

It is actually already possible with just one minor addition to the validate call - which is the initial json-uri of where you want to start.

But still, it would breakout the scope if an upper-lever reference appears.

pboettch commented 3 years ago

What do you think? https://github.com/pboettch/json-schema-validator/commit/2194addca4994a6bc2c0e8c71edca962ae79aed3#diff-d584b8fb27883417319b4f50effb4f09f37520061dcd115e374357f9488f7957R64

qu1ck commented 3 years ago

Diff looks good, solves my problem nicely.

There, the idea is to do one parsing of all schemas and sub-schemas and then create linked validator-instances based on a json-paths (IIRC). (Additionally dynamic additions of new sub-schemas without reparsing everything again)

That would work too but I can see that it would be harder to implement.

How would you know which json-path to pick based on the instance-type?

I think it's reasonable to require the path to be provided either explicitly in code or in object itself using "$schema": "<schemauri>#<pathtosubdefinition>". But maybe I'm misunderstanding the question?

pboettch commented 3 years ago

Would you be able to try out my changes to see whether it's OK?

Keeping a list of entrypoints in each sub-part of a program using a schema with multiple direct uses of "sub-schema" sounds reasonable.

Before merging this feature, we'd need more tests. Especially some with cross-file references, to see whether this makes sense. (I strongly thinks it will work out nicely).

qu1ck commented 3 years ago

Yes, I can try it out but my usage will be relatively simple like the test you already added. No multi file schemas with cross references. I'll be back with some links in a day or two.

qu1ck commented 3 years ago

Sorry for delay, needed to get my feature in a somewhat workable state. TLDR: it works.

Here is the schema (still work in progress, had to rearrange it a bit for unrelated reasons): https://gitlab.com/qu1ck/kicad/-/blob/pcm/pcm/schemas/pcm.v1.schema.json

And here I'm validating this object: https://gitlab.com/qu1ck/kicad-pcm-repository/-/raw/master/repository.json against a sub definition of the schema: https://gitlab.com/qu1ck/kicad/-/blob/f98df2a4264cab8dbe1e13261e88fc46165223c1/pcm/pcm.cpp#L110