I have a git folder containing multiple json schema used to validate objects, taking the form:
{
"type": "object",
"properties": {
"data": {
"type": "object",
"properties": {
"property1": { "type": "number" },
"property2": { "type": "number" },
"property3": { "type": "string" },
"sharedProperty": { "enum": [ "value1", "value2", "value3", "value4", "value5" ] }
},
"required": [
"property1",
"property2",
"property3",
"sharedProperty"
]
}
},
"required": [
"data"
]
}
I want to make sharedProperty common across the x number of schema in the folder, such that whenever an update to the property is required, it only needs to be done once in one central place. Something along the lines of:
{
"type": "object",
"properties": {
"data": {
"type": "object",
"properties": {
"property1": { "type": "number" },
"property2": { "type": "number" },
"property3": { "type": "string" },
"sharedProperty": { "$ref": "/schema/sharedProperty.json" }
},
"required": [
"property1",
"property2",
"property3",
"sharedProperty"
]
}
},
"required": [
"data"
]
}
Is referencing an external file inside a json possible? Does it require any additional code in the index.js file to facilitate calling the shared property from inside the schema? There's then the additional question of how to combine referencing another file with enum in the schema itself.
Related
I'm having trouble coming up with a schema that can accommodate these scenarios:
This field can have A, and/or only one of B or C
This field can have only A
This field can have only one of B or one of C
A, B and C have the same schema.
Basically B and C can not exist simultaneously.
Example scenarios:
// Valid
{
"exampleItem": {
"A": [{
"key": "item A",
"description": "test desc"
}],
"B": [{
"key": "item B",
"description": "test desc"
}]
}
}
// Invalid
{
"exampleItem": {
"B": [{
"key": "item B",
"description": "test desc"
}],
"C": [{
"key": "item C",
"description": "test desc"
}]
}
}
What I've tried:
{
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {
"exampleItemSchema": {
"type": "object",
"oneOf": [
{
"type": "object",
"properties": {
"A": {
"type": "array",
"items": {
"type": "object",
"required": [ "key", "description" ],
"properties": {
"key": {
"type": "string"
},
"description": {
"type": "string"
}
}
}
},
"B": {
"type": "array",
"items": {
"type": "object",
"required": [ "key", "description" ],
"properties": {
"key": {
"type": "string"
},
"description": {
"type": "string"
}
}
}
}
}
},
{
"type": "object",
"properties": {
"A": {
"type": "array",
"items": {
"type": "object",
"required": [ "key", "description" ],
"properties": {
"key": {
"type": "string"
},
"description": {
"type": "string"
}
}
}
},
"C": {
"type": "array",
"items": {
"type": "object",
"required": [ "key", "description" ],
"properties": {
"key": {
"type": "string"
},
"description": {
"type": "string"
}
}
}
}
}
}
]
}
}
}
I'm using oneOf right now, but I'm quite certain it's not what I want. I can't use anyOf because I don't want B and C to exist together, and allOf would assert against A, B and C.
To assert that a property can not exist, you can either do:
{
"not": {
"required": [
"property name"
]
}
}
or:
{
"properties": {
"property name": false
}
}
You can assert that a property must exist with the required keyword.
You can combine schemas together with allOf, anyOf, and oneOf.
Remember that subschemas are not just used to influence the overall result, but can be used as pre-conditions for another subschema to be evaluated, and that subschema will form part of the overall evaluation results. The keywords not, and if/then/else use preconditions to decide how they will evaluate. The conditions in a not and if are only used to determine a true/false result, and nothing else asserted in those subschemas will be used in the final result.
This should be all the tools in the toolbox that you need.
(For readability, I would keep all the properties keywords (that define what A, B and C should look like, should they exist and are permitted) at the top layer, and then use sub-assertions with if/then/else and not just to state which properties must or must not exist, and in what combinations.)
I need to express an array of different objects in a schema. The array, called contents, can contain any number of elements, but they must be one of two types of object: One type of object represents a piece of text, the other type of object represents an image.
So far, I've not been able to find a way to enforce the validation correctly. It seems (nested) required inside a oneOf doesn't work, so I tried using definitions but that doesn't seem to fit.
I've tried a few online validators, but they seem happy for me to add illegal values in item-2 value object. Its the value property that seems to be the biggest problem. Unfortunately, due to legacy issues, I'm stuck with this being an object in an array.
Is it possible to validate and enforce correct type/requirements for this object?
(this is the data, not a schema. Unfortunately, we also used the keyword type when we design the original json layout!)
{
"uuid":"780aa509-6b40-4cfe-9620-74a9659bfd59",
"contents":
[
{
"name":"item-1",
"label":"My editable text Label",
"value":"This text is editable",
"type":"text"
},
{
"name":"item-2",
"label":"My editable image label",
"index":0,
"type":"image",
"value":
[
{
"name":"1542293213356.png",
"rect":[0,0,286,286]
}
]
}
],
"version":"2.0"
}
Well, I think this is it, though the online validator doesn't seem 100% reliable. Editing the values isn't always invalidating the object.
{
"$schema": "http://json-schema.org/draft-06/schema#",
"type": "object",
"properties": {
"uuid": { "type": "string" },
"version": { "type": "string" },
"contents": {
"$ref": "#/definitions/contents"
},
},
"required": ["uuid", "version"],
"definitions": {
"image": {
"type": "object",
"properties": {
"name": { "type": "string" },
"label": { "type": "string" },
"type": { "enum": ["image", "text"] },
"value": { "type": "object" }
},
"required": ["name", "label", "type", "value"]
},
"text": {
"type": "object",
"properties": {
"name": { "type": "string" },
"label": { "type": "string" },
"type": { "enum": ["image", "text"] },
"value": { "type": "string" }
},
"required": ["name", "label", "type", "value"]
},
"contents": {
"type": "array",
"contains": {
"oneOf": [
{ "$ref": "#/definitions/image" },
{ "$ref": "#/definitions/text" }
]
},
},
},
}
Given this JSON object:
{
"objects": {
"foo": {
"id": 1,
"name": "Foo"
},
"bar": {
"id": 2,
"name": "Bar"
}
}
}
This is an object containing sub objects where each sub object has the same structure - they're all the same type. Each sub-object is keyed uniquely, so it acts like a named array.
I want to validate that each object within the objects property validates against a JSON Schema reference.
If the objects property was an array, such as:
{
"objects": [
{
"id": 1,
"name": "Foo"
},
{
"id": 2,
"name": "Bar"
}
]
}
I could validate this with a schema definition such as:
{
"id": "my-schema",
"required": [
"objects"
],
"properties": {
"objects": {
"type": "array",
"items": {
"type": "object",
"required": [
"id",
"name"
],
"properties": {
"id": {
"type": "integer"
},
"name": {
"type": "string"
}
}
}
}
}
}
This is achieved because the type is array, and this permits the validation of items.
Is it possible to do something similar, but with nested objects?
Thanks!
You can try something like this:
{
"id": "my-schema",
"type": "object",
"properties": {
"objects": {
"type": "object",
"patternProperties": {
"[a-z]+": {
"type": "object",
"properties": {
"id": {
"type": "integer"
},
"name": {
"type": "string"
}
},
"additionalProperties": false,
"required": [
"id",
"name"
]
}
}
}
}
}
The above answer works for restricting the object property names to lower-case letters. If you do not need to restrict the property names, then this is simpler:
{
"id": "my-schema",
"type": "object",
"properties": {
"objects": {
"type": "object",
"additionalProperties": {
"type": "object",
"properties": {
"id": {
"type": "integer"
},
"name": {
"type": "string }
}
},
"required": [
"id",
"name"
]
}
}
}
}
I omitted the inner "additionalProperties": false from the above answer because I find that using that keyword causes more problems than it solves, but it is a valid use of the keyword if you want validation to fail on the inner objects if they have properties other than "name" and "id".
I will read user input objects which should be in well-formed.
That is, the input objects could now have any key or sub-structure that is not defined in the interface.
How could I throw an exception, if a user gives an invalid object?
Pre-defined interface
export interface InputStructureInterface {
"tableName": string,
"viewType": string,
"structureName": string,
"sections": Array<Section>,
}
interface Section{
"name": string,
"fields": Array<Field>
}
interface Field{
"fieldName": string,
"relationType": string,
"relationName": null,
"fieldUi": FieldUi
}
interface FieldUi {
"fieldType": string,
"label": strin
}
Valid input structure
This structure is a subset under the defined InputStructureInterface
{
"tableName": "User",
"viewType": "List View",
"structureName": "personal_data_settings_list_view",
"sections": [
{
"name": null,
"fields": [
{
"fieldName": "Name",
"relationType": null,
"relationName": null,
"fieldUi": {
"fieldType": "string",
"label": "Name"
},
}
]
}
]
}
Invalid input structure
Because viewTypeTHIS_IS_A_TYPO, nameTHIS_IS_A_TYPO are not present on the interface
{
"tableName": "User",
"viewTypeTHIS_IS_A_TYPO": "List View",
"structureName": "personal_data_settings_list_view",
"sections": [
{
"nameTHIS_IS_A_TYPO": null,
"fields": [
{
"fieldNameTHIS_IS_A_TYPO": "Name"
}
]
}
]
}
The TypeScript will just enforce the types in compile time. If you want to make this kind of validations you need to use some kind of json-schema validation library. Like this one for example: https://github.com/epoberezkin/ajv
UPDATE
For example, using this library (https://github.com/epoberezkin/ajv) you can do something like this:
import * as Ajv from 'ajv';
const ajv = new Ajv();
const schema = {
"type": "object",
"properties": {
"tableName": { "type": "string" },
"viewType": { "type": "string" },
"structureName": { "type": "string" },
"sections": {
"type": "array",
"items": [
{
"type": "object",
"properties": {
"name": { "type": ["string", "null"] },
"fields": {
"type": "array",
"items": [
{
"type": "object",
"properties": {
"fieldName": { "type": "string" },
"relationType": { "type": ["string", "null"] },
"relationName": { "type": ["string", "null"] },
"fieldUi": {
"fieldType": { "type": "string" },
"label": { "type": "string" }
}
},
"required": ["fieldName", "relationType", "relationName"],
"additionalProperties": false
}
]
}
},
"required": ["name", "fields"],
"additionalProperties": false
}
]
}
},
"required": ["tableName", "viewType", "structureName"],
"additionalProperties": false
};
const validate = ajv.compile(schema);
let valid = validate(data); // <-- pass your json object here
if (!valid) {
console.log(validate.errors);
}
To install the library: npm install ajv
I don't know if I'm just blind or something but how can I do the following:
I have a User model with a hasOne relation to a UserData model. I only want one property of UserData directly in the results of User.
The relation in User looks like this:
"relations": {
"userData": {
"type": "hasOne",
"model": "UserData"
}
}
And the default scope in User:
"scope": {
"include": "userData"
}
So the result for one User is:
[
{
"id": 5,
"email": "example#example.com",
"name": "Example",
"userData": {
"id": 5,
"birthdate": "1971-09-06T00:00:00.000Z"
}
}
]
But what I want is this:
[
{
"id": 5,
"email": "example#example.com",
"name": "Example",
"birthdate": "1971-09-06T00:00:00.000Z"
}
]
How can I achive this?
Edit:
The two model definitions:
ChiliUser:
{
"name": "ChiliUser",
"base": "ChiliUserData",
"idInjection": true,
"options": {
"validateUpsert": true,
"mysql": {
"table": "person"
}
},
"properties": {
"id": {
"type": "number"
},
"email": {
"type": "string"
},
"password": {
"type": "string"
},
"vorname": {
"type": "string"
},
"name": {
"type": "string"
},
"spitzname": {
"type": "string"
},
"strasse": {
"type": "string"
},
"plz": {
"type": "number"
},
"ort": {
"type": "string"
},
"geolat": {
"type": "string"
},
"geolng": {
"type": "string"
}
},
"validations": [],
"relations": {},
"acls": [],
"methods": []
}
ChiliUserData:
{
"name": "ChiliUserData",
"base": "PersistedModel",
"idInjection": true,
"options": {
"validateUpsert": true,
"mysql": {
"table": "person_data"
}
},
"properties": {
"id": {
"type": "number"
},
"person_id": {
"type": "number"
},
"birthdate": {
"type": "date"
}
},
"validations": [],
"relations": {},
"acls": [],
"methods": []
}
Are you using the built-in User model? If you are, you can simply extend it with your UserData model using base: "User", and the underlying schema will be as you desire:
{
"name": "UserData",
"base": "User",
"properties": {}
...
}
But this setup would mean you'd use UserData over User in your code. Since the built-in User model gets in the way, I'd recommend a different word like Person or Customer, in which case it would look like:
{
"name": "Person",
"base": "User",
"properties": {
...extra user data properties only...
}
...
}
This way, only Person will have all the extra "user data" fields on it, but will also include all the fields/methods defined for User inside node_modules/loopback/common/models/User.json. If you're using MySQL, the Person table will be created with the combination of fields from both User and Person.