Constructing JSON Schema with anyOf recursive items in array [duplicate] - javascript

I was looking around the docs and couldn't find any direct or indirect solution.
Is there any way to get validation on JSON objects without knowing exactly where the specific object is located?
For example, I want to validate the following sub-object:
{
"grandParent": {
"parent": {
"child": {
"name": "John"
}
}
}
}
The object can be part of a larger JSON file the can be structured as follows:
{
"root": {
"someKey": {
"grandParent": ...
},
"grandParent": ...,
...<go in even deeper>: {
"grandParent": ...
}
}
}
Can I create a json schema that validates the object no matter where it is?
Similar example in glob would be: root.**.grandParent.parent.child

You'll need to use a combination of additionalProperties, items, and recursive references.
First, we define the structure you want to validate. You have to define properties for each layer of the object.
Next, you want your root level to reference that definition. Because you're using pre draft 2019-09, you'll need to wrap that reference in an allOf.
Then you want to make sure that for objects, the values have the root schema applied, and for arrays, each item has the root schema applied.
The use of "$ref": "#" resolves to the root of the schema, which creates the cyclical reference.
Some implementations may not like this, but most should be able to handle it.
Here's a live demo of the below schema: https://jsonschema.dev/s/lBrZk
{
"$schema": "http://json-schema.org/draft-07/schema",
"definitions": {
"grandParentToChild": {
"properties": {
"grandParent": {
"properties": {
"parent": {
"properties": {
"child": {
"properties": {
"name": {
"type": "string"
}
}
}
}
}
}
}
}
}
},
"allOf": [
{
"$ref": "#/definitions/grandParentToChild"
}
],
"additionalProperties": {
"$ref": "#"
},
"items": {
"$ref": "#"
}
}

Related

Trying to access dot notation variable with a string

I'm working on creating a web form that can dynamically read a swagger endpoint to create form fields. Specifically right now I am trying to read the schemas from the component section defined by openAPI 3.
Example json:
{
"openapi": "3.0.1",
"info": {
.......
},
"paths": {
........
},
"components": {
"schemas": {
"FakeAppConfiguration": {
"type": "object",
"properties": {
"setting1": {
"type": "string",
"nullable": true
},
"setting2": {
"type": "string",
"nullable": true
}
},
"additionalProperties": false
},
"OtherFakeAppConfiguration": {
........
},
"ThirdFakeAppConfiguration": {
........
}
}
}
}
}
Using this snippet of json as an example, I can easily get the names of the schemas that are defined by using (json has already been loaded into data using fetch)
for (let schema in data.components.schemas)
{
//this will print out FakeAppConfiguration, OtherFakeAppConfiguration, ThirdFakeAppConfiguration
console.log(schema);
}
My problem now comes in trying to access each of these schema trees without calling them directly. I could easily do data.components.schemas.FakeAppConfiguration, but that would defeat the purpose of making this dynamic. I've been trying to somehow use the strings obtained in the above loop to access what I want to no avail. Some examples of things I've tried are below. Anyone able to help me get further access without calling the variable directly with dot notation? I have also considered doing manual parsing of the JSON, but trying to avoid that. This is a react app, so if anyone can think of a library that could help, I'm all ears there as well.
//treating like a key
data.components.schemas['FakeAppConfiguration']
//trying to create a map
interface SchemaDef {
type: string,
properties: Properties,
//....etc,etc
}
let i = 0;
let schemas: Map<string, SchemaDef> = new Map<string, SchemaDef>();
for (let schema in data.components.schemas)
{
schemas.set(schema, data.components.schemas[i]);
i++;
}
You could iterate over the Object.entries() of your "schemas" object.
let schemas = {
"FakeAppConfiguration": {
"type": "object",
"properties": {
"setting1": {
"type": "string",
"nullable": true
},
"setting2": {
"type": "string",
"nullable": true
}
},
},
"FakeAppConfiguration2": {
"type": "object",
"properties": {
"setting1": {
"type": "string",
"nullable": true
},
"setting2": {
"type": "string",
"nullable": true
}
},
}
};
for (let [key, value] of Object.entries(schemas)) {
console.log(key, "\n\n", value);
}

Customized schema resolving for invalid objects with AJV

I'm trying to find a way to alter schema validation to find most appropriate schema for a given object. Let's say we have a schema:
{
"oneOf": [
{
"$ref": "#/definitions/a"
},
{
"$ref": "#/definitions/b"
}
],
"definitions": {
"a": {
"type": "object",
"properties": {
"prop1": {
"enum": ["x"]
}
},
"required": ["prop1"]
},
"b": {
"type": "object",
"properties": {
"prop1": {
"enum": ["y"]
},
"prop2": {
"type": "string"
}
},
"required": ["prop1", "prop2"]
}
}
}
Now, if I have an object { "prop1": "y" }, I want it to be resolved as of #/definitions/b type, even if it's not really valid for this scheme. That is, I want to use just prop1 property for resolving.
I wonder if there is a way to do it using AJV custom keywords, without rebuilding the schema itself? In particular, if schema is not valid for an object, is it possible to use custom keywords to override it and make it valid?
If the objective is to only report errors from the correct schema you can use either "switch" (with v5 option, it is moved to ajv-keywords from version 5.0.0) or "if/then/else" (it is recommended as it is likely to be added in JSON Schema draft 7):
{
"id": "schema",
"if": { "properties": { "prop1": { "const": "x" } } },
"then": { "$ref": "#/definitions/a" },
"else": { "$ref": "#/definitions/b" }
}
If you need to know which schema was used for validation you can use a custom keyword to make a note of it:
{
"id": "schema",
"if": { "properties": { "prop1": { "const": "x" } } },
"then": {
"allOf": [
{ "schemaUsed": "schema#/definitions/a" },
{ "$ref": "#/definitions/a" }
]
},
"else": {
"allOf": [
{ "schemaUsed": "schema#/definitions/b" },
{ "$ref": "#/definitions/b" }
]
}
}
The keyword should be defined to store schema ID during validation in some variable so that after validation you could see which one was used.
If you need to get actual schema you can do:
var validate = ajv.getSchema('schema#/definitions/a'); // validating function
var schema = validate.schema; // schema as JSON

Angular - Convert Jackson output to JSON

The server I'm working with changed the REST format from plain JSON:
{
"removedVertices": [
{
"id": "1",
"info": {
"host": "myhost",
"port": "1111"
},
"name": "Roy",
"type": "Worker"
}
],
"id": "2",
"time": 1481183401573
}
To Jackson format:
{
"removedVertices": [
"java.util.ArrayList",
[
{
"id": "1",
"info": [
"java.util.HashMap",
{
"host": "myhost",
"port": "1111"
}
]
"name": "Roy",
"type": "Worker",
}
]
"id": "2",
"time": 1482392323858
}
How can I parse it the way it was before in Angular/Javascript?
Assuming only arrays are affected, I would use underscore.js and write a recursive function to remove the Jackson type.
function jackson2json(input) {
return _.mapObject(input, function(val, key) {
if (_.isArray(val) && val.length > 1) {
// discard the Jackson type and keep the 2nd element of the array
return val[1];
}
else if (_.isObject(val)) {
// apply the transformation recursively
return jackson2json(val);
}
else {
// keep the value unchanged (i.e. primitive types)
return val;
}
});
}
If the api should be restful, then the server should not return none plain json results. I think the server site need to fix that.
I think it is because the server enabled the Polymorphic Type Handling feature.
Read Jackson Default Typing for object containing a field of Map and JacksonPolymorphicDeserialization.
Disable the feature and you will get result identical to plain json.
The main difference i see is that in arrays you have an additional string element at index 0.
If you always get the same structure you can do like this:
function jacksonToJson(jackson) {
jackson.removedVertices.splice(0, 1);
jackson.removedVertices.forEach((rmVert) => {
rmVert.info.splice(0, 1);
});
return jackson;
}

findAndModify, How to make operations on document's array search objects and change fields values

I try to find one object in document's array, and update its fields.
db.rescuemodels.findAndModify({
query: {
"features": {
$elemMatch: {
"properties.title": "W"
}
}
},
update: {
$set: {
"features": {
"properties": {
"title": "XXX"
}
}
}
}
})
Query is fine, result is one matching element, but how to make update method change just one field in this example title? Because now it create new array or object and clean old array.
MongoDB has "Dot Notation" for this purpose, as well as the positional $ operator for referencing matched elements of an array:
db.rescuemodels.findAndModify({
"query": { "features.properties.title":"W" },
"update": { "$set": { "features.$.properties.title":"XXX" } }
})
Note that this only works when there is a single array present as in:
{
"features": [
{ "properties": { "name": "A" } },
{ "properties": { "name": "W" } }
}
}
If you are nesting arrays then MongoDB cannot match in "positional operator beyond the "outer" array only:
{
"features": [
{ "properties": [{ "name": "A" }, { "name": "W" }] },
]
}
Postional matching will not work there because you cannot do features.$.properties.$.name and the matched element index would be 0 and not 1 as this refers to the outer array.
Also note that under nodejs the MongoDB driver syntax for .findAndModify() is quite different to the shell syntax. The "query" and "update" parts are separate arguments there rather than the document form as used by the shell,
To update an individual element in the array "features" you can use the positional operator, $. Your query would look something like this...
db.rescuemodels.findAndModify({
query: {
"features": {
$elemMatch: {
"properties.title": "W"
}
}
},
update: {
$set: {
"features.$.properties.title": "XXX"
}
}
})

Accessing JavaScript Sub-properties by Name

I wrote the following JavaScript function (part of a larger "class") to help ensure anybody using the object stores attribute values in the "values" property.
function _updateAttributes(attribute, value) {
_attributes[attribute] = { values: { value: value }};
}
It works fine for a flat structure, but falls apart when I start trying to use it for sub-properties.
After running the following code:
myEntity.updateAttribute('name', 'Frankenstein');
myEntity.updateAttribute('name.source', 'John Doe');
I'd like the following structure:
{
"attributes": {
"name": {
"values": {
"value": "Frankenstein"
},
"source": {
"values": {
"value": "JohnDoe"
}
}
}
}
}
Instead, it's coming out like this:
{
"attributes": {
"name": {
"values": {
"value": "Frankenstein"
}
},
"name.source": {
"values": {
"value": "JohnDoe"
}
}
}
}
Is there any clean way to write this JavaScript or will I be faced with splitting out the strings and manually building the structure?
NOTE: I realize even the preferred structure is a little odd, but there's a Java object I'm mapping to that expects this format, so I don't have any options here.
You'll have to parse the string (parse is a bit strong, just a single split('.') with a loop).
But frankly, the cleaner way would simply be:
myEntity.name = {values: 'Frankenstein'};
myEntity.name.source = {values: 'John Doe'};

Categories

Resources