How to get "title" from AJV error? - javascript

I've got a JSON Schema that looks like this:
{
"required": [
"instructions"
],
"properties": {
"instructions": {
"title": "Instructions",
"minItems": 3,
"type": "array",
"items": {
"required": [
"typeId",
"teamId",
"disciplineId"
],
"properties": {
"typeId": {
"minimum": 1,
"title": "Appointment Type",
"type": "integer"
},
"teamId": {
"minimum": 1,
"title": "Team",
"type": "integer"
},
"disciplineId": {
"minimum": 1,
"title": "Discipline",
"type": "integer"
},
"prefClinicianId": {
"title": "Pref. Clinician",
"anyOf": [
{
"type": "null"
},
{
"minimum": 1,
"type": "integer"
}
]
},
"prefTime": {
"title": "Pref. Time",
"anyOf": [
{
"type": "null"
},
{
"type": "integer"
}
]
},
"childRequired": {
"title": "Child Req'd",
"type": "boolean"
}
},
"type": "object"
}
}
},
"type": "object"
}
As you can see, I've added titles to all the properties. However, the error object I get back looks like:
[
{
"keyword": "minItems",
"dataPath": ".instructions",
"schemaPath": "#/properties/instructions/minItems",
"params": {
"limit": 3
},
"message": "should NOT have less than 3 items"
},
{
"keyword": "type",
"dataPath": ".instructions[0].typeId",
"schemaPath": "#/properties/instructions/items/properties/typeId/type",
"params": {
"type": "integer"
},
"message": "should be integer"
},
{
"keyword": "type",
"dataPath": ".instructions[0].teamId",
"schemaPath": "#/properties/instructions/items/properties/teamId/type",
"params": {
"type": "integer"
},
"message": "should be integer"
},
{
"keyword": "type",
"dataPath": ".instructions[0].disciplineId",
"schemaPath": "#/properties/instructions/items/properties/disciplineId/type",
"params": {
"type": "integer"
},
"message": "should be integer"
}
]
As you can see, the title is not in there. How can I get the titles with the errors?
Please note that this question is specific to AJV.

When you create your AJV object set the verbose option to true.
This will add a parentSchema property to the ajv error with the original schema. It will also add a schema property that contains the specific schema attribute that caused the validation failure.
Here's an example:
var ajv = new Ajv({
$data: true,
verbose: true
});
let schema = {
title: 'object title',
type: 'object',
properties: {
str: {
title: "A string property",
type: "string"
}
}
};
let data = {
str: 3
};
ajv.validate(schema, data)
console.log('ERRORS: ', this.ajv.errors)
<script src="https://cdnjs.cloudflare.com/ajax/libs/ajv/5.3.0/ajv.bundle.js"></script>

Related

Dynamic values not setting in nested array

I am trying to change the values of my nested object by using the data from another object but for some reason it is never setting the value. If i put static text in there it works but just doesn't work if the data from my other object
const projectFormTypes = [
{
"type": "Theatrical",
"sections" : {
"project": {
"fields": [
{
"label": "Project Title",
"name": "title",
"required": true,
"type": "textbox",
"visible": true,
'value': ''
},
]
},
"participants": {
"fields": [
{
"label": "First Name",
"name": "firstName",
"required": false,
"type": "textbox",
"visible": true,
"value": "first name 1"
},
]
},
"earnings": {
"fields": [
{
"label": "Compensation Amount",
"name": "wages",
"prefix": "$",
"required": false,
"type": "textbox",
"visible": true,
"value": 100000
},
]
}
}
},
]
const projectData = [{"fileDetailRecordId":3,"detailRecordId":"341697P3","signatoryName":"comp 2","signatoryId":"comp sag","contract":"Theatrical","sagId":"aftra 2","title":"Project 2","principalPhotoDate":"2019-12-13","madeFor":"Interactive Media","ppe":"2019-12-13","sessionDateTv":"2019-12-13","marketPaid":"In-Flight","commercialTitle":"Project 2","sessionDate":"2019-12-13","useType":"Clip Use","ssn":"987654","firstName":"test","middleName":"test","lastName":"test","loanOutNumber":"45687","loanOutName":"54854","performerType":"Background","performerCategory":"Dancer","paymentType":"Payroll","wages":"852963","subjectToPh":"852963","phContrib":"8529363","contribPercent":"10.0000","detailStatus":"DRAFT","earningsFileId":341697,"detailStatusId":{"parentRefId":32,"activeFlag":true,"refCode":"detail_processing","refValue":"Processing","comments":"detail is being processed","createdBy":"NS","createdAt":"2018-10-04T01:33:18.000+0000","updatedBy":"NS","updatedAt":"2019-06-19T17:45:39.000+0000","cmsProcessEfDetailLogList":[],"referenceId":33,"cmsEarningsFileList":[],"cmsEarningsFileList1":[]},"createdBy":"UI","updatedBy":"UI"},{"fileDetailRecordId":1,"detailRecordId":"341697P1","signatoryName":"comp name","signatoryId":"comp aftra","contract":"Theatrical","sagId":"aftra id","title":"Project 1","principalPhotoDate":"2019-12-13","madeFor":"Foreign TV","ppe":"2019-12-13","sessionDateTv":"2019-12-13","marketPaid":"Network","commercialTitle":"Project 1","sessionDate":"2019-12-13","useType":"Phono Conversation","ssn":"456","firstName":"first name 1","middleName":"middle name 1","lastName":"last name 1","loanOutNumber":"456","loanOutName":"456","performerType":"AFTRA Staff","performerCategory":"Actor","paymentType":"Deferred","wages":"500","subjectToPh":"500","phContrib":"500","contribPercent":"1000.0000","detailStatus":"DRAFT","earningsFileId":341697,"detailStatusId":{"parentRefId":32,"activeFlag":true,"refCode":"detail_processing","refValue":"Processing","comments":"detail is being processed","createdBy":"NS","createdAt":"2018-10-04T01:33:18.000+0000","updatedBy":"NS","updatedAt":"2019-06-19T17:45:39.000+0000","cmsProcessEfDetailLogList":[],"referenceId":33,"cmsEarningsFileList":[],"cmsEarningsFileList1":[]},"createdBy":"UI","updatedBy":"UI"},{"fileDetailRecordId":2,"detailRecordId":"341697P2","signatoryName":"comp name","signatoryId":"comp aftra","contract":"Theatrical","sagId":"aftra id","title":"Project 1","principalPhotoDate":"2019-12-13","madeFor":"Foreign TV","ppe":"2019-12-13","sessionDateTv":"2019-12-13","marketPaid":"Home Video","commercialTitle":"Project 1","sessionDate":"2019-12-13","useType":"Clip Use","ssn":"123","firstName":"last name 2","middleName":"last name 2","lastName":"last name 2","loanOutNumber":"456","loanOutName":"456","performerType":"AFTRA Staff","performerCategory":"Dance Coreographer","paymentType":"Deferred","wages":"800","subjectToPh":"800","phContrib":"800","contribPercent":"50.0000","detailStatus":"DRAFT","earningsFileId":341697,"detailStatusId":{"parentRefId":32,"activeFlag":true,"refCode":"detail_processing","refValue":"Processing","comments":"detail is being processed","createdBy":"NS","createdAt":"2018-10-04T01:33:18.000+0000","updatedBy":"NS","updatedAt":"2019-06-19T17:45:39.000+0000","cmsProcessEfDetailLogList":[],"referenceId":33,"cmsEarningsFileList":[],"cmsEarningsFileList1":[]},"createdBy":"UI","updatedBy":"UI"}]
const pp = Object.keys(projectFormTypes).forEach(function(r) {
for(let key in projectFormTypes[r].sections){
for(let o in projectFormTypes[r].sections[key].fields){
projectFormTypes[r].sections[key].fields[o].value = projectData[0][o.name];
}
}
});
console.log('result', projectFormTypes);
projectData[0][o.name]
should be
projectData[0][projectFormTypes[r].sections[key].fields[o].name]
o is the index in the fields array, and numbers don't have a name property. You want the name of the current element of the for loop.
But the code is simplified and less error prone if you use forEach() for all the nested loops.
const projectFormTypes = [{
"type": "Theatrical",
"sections": {
"project": {
"fields": [{
"label": "Project Title",
"name": "title",
"required": true,
"type": "textbox",
"visible": true,
'value': ''
}, ]
},
"participants": {
"fields": [{
"label": "First Name",
"name": "firstName",
"required": false,
"type": "textbox",
"visible": true,
"value": "first name 1"
}, ]
},
"earnings": {
"fields": [{
"label": "Compensation Amount",
"name": "wages",
"prefix": "$",
"required": false,
"type": "textbox",
"visible": true,
"value": 100000
}, ]
}
}
}, ]
const projectData = [{"fileDetailRecordId":3,"detailRecordId":"341697P3","signatoryName":"comp 2","signatoryId":"comp sag","contract":"Theatrical","sagId":"aftra 2","title":"Project 2","principalPhotoDate":"2019-12-13","madeFor":"Interactive Media","ppe":"2019-12-13","sessionDateTv":"2019-12-13","marketPaid":"In-Flight","commercialTitle":"Project 2","sessionDate":"2019-12-13","useType":"Clip Use","ssn":"987654","firstName":"test","middleName":"test","lastName":"test","loanOutNumber":"45687","loanOutName":"54854","performerType":"Background","performerCategory":"Dancer","paymentType":"Payroll","wages":"852963","subjectToPh":"852963","phContrib":"8529363","contribPercent":"10.0000","detailStatus":"DRAFT","earningsFileId":341697,"detailStatusId":{"parentRefId":32,"activeFlag":true,"refCode":"detail_processing","refValue":"Processing","comments":"detail is being processed","createdBy":"NS","createdAt":"2018-10-04T01:33:18.000+0000","updatedBy":"NS","updatedAt":"2019-06-19T17:45:39.000+0000","cmsProcessEfDetailLogList":[],"referenceId":33,"cmsEarningsFileList":[],"cmsEarningsFileList1":[]},"createdBy":"UI","updatedBy":"UI"},{"fileDetailRecordId":1,"detailRecordId":"341697P1","signatoryName":"comp name","signatoryId":"comp aftra","contract":"Theatrical","sagId":"aftra id","title":"Project 1","principalPhotoDate":"2019-12-13","madeFor":"Foreign TV","ppe":"2019-12-13","sessionDateTv":"2019-12-13","marketPaid":"Network","commercialTitle":"Project 1","sessionDate":"2019-12-13","useType":"Phono Conversation","ssn":"456","firstName":"first name 1","middleName":"middle name 1","lastName":"last name 1","loanOutNumber":"456","loanOutName":"456","performerType":"AFTRA Staff","performerCategory":"Actor","paymentType":"Deferred","wages":"500","subjectToPh":"500","phContrib":"500","contribPercent":"1000.0000","detailStatus":"DRAFT","earningsFileId":341697,"detailStatusId":{"parentRefId":32,"activeFlag":true,"refCode":"detail_processing","refValue":"Processing","comments":"detail is being processed","createdBy":"NS","createdAt":"2018-10-04T01:33:18.000+0000","updatedBy":"NS","updatedAt":"2019-06-19T17:45:39.000+0000","cmsProcessEfDetailLogList":[],"referenceId":33,"cmsEarningsFileList":[],"cmsEarningsFileList1":[]},"createdBy":"UI","updatedBy":"UI"},{"fileDetailRecordId":2,"detailRecordId":"341697P2","signatoryName":"comp name","signatoryId":"comp aftra","contract":"Theatrical","sagId":"aftra id","title":"Project 1","principalPhotoDate":"2019-12-13","madeFor":"Foreign TV","ppe":"2019-12-13","sessionDateTv":"2019-12-13","marketPaid":"Home Video","commercialTitle":"Project 1","sessionDate":"2019-12-13","useType":"Clip Use","ssn":"123","firstName":"last name 2","middleName":"last name 2","lastName":"last name 2","loanOutNumber":"456","loanOutName":"456","performerType":"AFTRA Staff","performerCategory":"Dance Coreographer","paymentType":"Deferred","wages":"800","subjectToPh":"800","phContrib":"800","contribPercent":"50.0000","detailStatus":"DRAFT","earningsFileId":341697,"detailStatusId":{"parentRefId":32,"activeFlag":true,"refCode":"detail_processing","refValue":"Processing","comments":"detail is being processed","createdBy":"NS","createdAt":"2018-10-04T01:33:18.000+0000","updatedBy":"NS","updatedAt":"2019-06-19T17:45:39.000+0000","cmsProcessEfDetailLogList":[],"referenceId":33,"cmsEarningsFileList":[],"cmsEarningsFileList1":[]},"createdBy":"UI","updatedBy":"UI"}]
projectFormTypes.forEach(function(type) {
Object.values(type.sections).forEach(function(section) {
section.fields.forEach(function(field) {
field.value = projectData[0][field.name];
});
});
});
console.log('result', projectFormTypes);

Using anyOf to see objects name?

I have a schema here where I would like to have a drop down, to select an option, and from there - depending on the selection different options appear; all being nested within an array to have multiples of them.
I have noticed that when I am filling in with dummy data, the output json isnt storing the name of the selected option
so the data.json looks something like this:
{
"page1": [
{
"imageOptions": {
"imageHeightType": "vh",
"imageHeight": 50
},
"textboxArea": {
"headerText": "Header for selection1",
"headingTag": "h1",
"textBoxOpacity": 15
}
},
{
"content": "This is a complety different selection, yet there is no name to tell the difference between these two difference objects"
}
]
}
As you can see theres no object to wrap these two different items within the page1 array - Ideally would like something like:
{
"page1": [
{
// Title of object goes here from schema
"imageOptions": {
"imageHeightType": "vh",
"imageHeight": 50
},
"textboxArea": {
"headerText": "Header for selection1",
"headingTag": "h1",
"textBoxOpacity": 15
}
},
{
// Title of object goes here from schema
"content": "This is a completely different selection, yet there is no name to tell the difference between these two difference objects"
}
]
}
Is there a way to make this so? I have looked on the docs for AnyOf but not much luck. Quite new to React-JsonSchema-Forms.
Below is my current Schema:
{
"type": "object",
"properties": {
"page1": {
"type": "array",
"items": {
"type": "object",
"anyOf": [
{
"title": "Full Width Image",
"type": "object",
"properties": {
"imageOptions": {
"type": "object",
"title": "Image",
"properties": {
"image": {
"type": "string",
"title": "Image",
"format": "data-url"
},
"imageHeightType": {
"enum": [
"px",
"vh"
]
},
"imageHeight": {
"type": "number",
"title": "Image Height"
}
}
},
"textboxArea": {
"type": "object",
"title": "Textbox Area",
"properties": {
"headerText": {
"type": "string",
"title": "Heading Text"
},
"headingTag": {
"enum": [
"h1",
"h2",
"h3"
]
},
"imageText": {
"type": "string",
"title": "Body Text"
},
"textboxPosition": {
"title": "Textbox Position",
"enum": [
"Left",
"Center",
"Right"
]
},
"textboxColor": {
"title": "Color of Textbox Area",
"type": "string"
},
"textBoxOpacity": {
"title": "Textbox Opacity %",
"type": "integer",
"minimum": 0,
"maximum": 100,
"multipleOf": 5
}
}
}
}
},
{
"title": "Custom Block",
"type": "object",
"properties": {
"content": {
"type": "string"
}
}
}
]
}
}
}
}
Also Link to the online schema editor if it helps understand my issue
Why not just add a name-like property to each object? You can then hide/disable it if you want:
schema:
"anyOf": [
{
"title": "Full Width Image",
"type": "object",
"properties": {
"name": {
"type": "string",
"default": "fullWidthImage"
},
"imageOptions": {
"type": "object",
"title": "Image",
"properties": {...}
...
}
...
}
},
{
"title": "Custom Block",
"type": "object",
"properties": {
"name": {
"type": "string",
"default": "custom"
},
"content": {
"type": "string"
}
}
}
]
uiSchema:
{
"page1": {
"items": {
"name": {
"ui:widget": "hidden"
},
"imageOptions": {...},
...
}
}
formData then should look like this:
{
"page1": [
{
"name": "fullWidthImage",
"imageOptions": {
"imageHeightType": "vh",
"imageHeight": 50
},
"textboxArea": {
"headerText": "Header for selection1",
"headingTag": "h1",
"textBoxOpacity": 15
}
},
{
"name": "custom",
"content": "This is a complety different selection, yet there is no name to tell the difference between these two difference objects"
}
]
}

angular-schema-form array length

I have this schema:
{
"type": "object",
"title": "Comment",
"required": [
"comments"
],
"properties": {
"comments": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {
"title": "Name",
"type": "string"
},
"email": {
"title": "Email",
"type": "string",
"pattern": "^\\S+#\\S+$",
"description": "Email will be used for evil."
},
"spam": {
"title": "Spam",
"type": "boolean",
"default": true
},
"comment": {
"title": "Comment",
"type": "string",
"maxLength": 20,
"validationMessage": "Don't be greedy!"
}
},
"required": [
"name",
"comment"
]
}
}
}
}
This is an array of comments. I can add and remove comments.
How can I render 2 items of the array always by default ?
I've tried with maxItems and minItems but those parameters don't render items.
There are two ways to do it currently.
First is to set them in the default model so it looks something like:
$scope.model = {
"comments": [{},{}]
}
The second would be to use onChange on the array:
"onChange": function(val) { if(val.length < 2) val.push({}); }
The difference between the two being that the onChange will not allow it to drop below the minimum length you set while the first option is just for the initial default.

How to throw an exception, if an object structure is not matching/fitting another one

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

Loopback and MongoDB

I've been trying to create a collection that contains an array of objects in loopback.
I want a store data formatted like this:
{
id: "16356135616aaasad", //autogenerated by mongo
"name" : "a name",
"valores": [
{"valor": 567, "fecha": "2016-08-18T00:00:00.000Z"},
{"valor": 569, "fecha": "2016-08-19T00:00:00.000Z"},
...
]
}
I have the following configuration in loopback:
indicador.json
{
"name": "Indicador",
"plural": "indicadores",
"base": "PersistedModel",
"idInjection": true,
"options": {
"validateUpsert": true,
"autoId": true
},
"properties": {
"nombre": {
"type": "string",
"required": true
}
},
"relations": {
"historico": {
"type": "embedsMany",
"model": "Valor"
}
}
}
this is a base collection associated to another model (not persistent)
valor.json
{
"name": "Valor",
"plural": "valores",
"base": "Model",
"properties": {
"valor": {
"type": "number",
"required": true
},
"fecha": {
"type": "date",
"required": true
}
}
}
The problem it's when i try to send a post to the endpoint. If i send this data
{
"nombre": "UF",
"valores": [
{
"valor": 0,
"fecha": "2016-08-18"
}
]
}
The API responses this:
{
"error": {
"name": "ValidationError",
"status": 422,
"message": "The `Indicador` instance is not valid. Details: `valores` contains invalid item at index `0`: `id` is blank (value: [ { valor: 0, fecha: 2016...} ]).",
"statusCode": 422,
"details": {
"context": "Indicador",
"codes": {
"valores": [
"invalid"
]
},
"messages": {
"valores": [
"contains invalid item at index `0`: `id` is blank"
]
}
},
"stack": "ValidationError: The `Indicador` instance is not valid. Details: `valores` contains invalid item at index `0`: `id` is blank (value: [ { valor: 0, fecha: 2016...} ]).\n at /home/dev/app/node_modules/loopback-datasource-juggler/lib/dao.js:322:12\n at ModelConstructor.<anonymous> (/home/dev/app/node_modules/loopback-datasource-juggler/lib/validations.js:492:11)\n at ModelConstructor.next (/home/dev/app/node_modules/loopback-datasource-juggler/lib/hooks.js:81:12)\n at ModelConstructor.<anonymous> (/home/dev/app/node_modules/loopback-datasource-juggler/lib/validations.js:489:23)\n at ModelConstructor.trigger (/home/dev/app/node_modules/loopback-datasource-juggler/lib/hooks.js:71:12)\n at ModelConstructor.Validatable.isValid (/home/dev/app/node_modules/loopback-datasource-juggler/lib/validations.js:455:8)\n at /home/dev/app/node_modules/loopback-datasource-juggler/lib/dao.js:318:9\n at doNotify (/home/dev/app/node_modules/loopback-datasource-juggler/lib/observer.js:98:49)\n at doNotify (/home/dev/app/node_modules/loopback-datasource-juggler/lib/observer.js:98:49)\n at doNotify (/home/dev/app/node_modules/loopback-datasource-juggler/lib/observer.js:98:49)"
}
}
¿Why i get this error?
The id inside the objects of the array that I need isn't necessary for me. I don't understand why happen this.
You need to specify that embedded model doe not need id
"relations": {
"historico": {
"type": "embedsMany",
"model": "Valor",
"options": {
"forceId": false,
"validate": true,
"persistent": false
}
}
}
You need to add "defaultFn":"uuid" to the id property in the json of the model that is going to be embeded in order to have the id generated. In your case it will look like the following:
{
"name": "Valor",
"plural": "valores",
"base": "Model",
"properties": {
"id":{
"type": "string",
"defaultFn":"uuid",
"id":true
},
"valor": {
"type": "number",
"required": true
},
"fecha": {
"type": "date",
"required": true
}
}
}

Categories