JSON Schema 'Required' validation set/removed by checkbox - jsonschema

I am designing a web application modelled using JSON Schema. I am attempting to create a page with a text area and a checkbox. The text area is to explain why I like pizza. If the user clicks the check box, they are confirming they don't like pizza. The text box is 'required' unless the checkbox is selected. The checkbox is effectively operating as a boolean, but the components being used cannot be altered (because a user researcher said so). At the moment, I'm using AJV to validate my schema, and it is configured to throw errorMessages.required when a property is required but no input is entered/selected.
Unfortunately I'm completely inexperienced when it comes to JSON schema. Below is my current attempt to get this to validate. This renders correctly, but it doesn't work as I want - In my dev environment it just validates anything, yet on jsonschemavalidator.net it wont validate unless the checkbox is checked. How can my desired function be achieved?
{
$schema: 'http://json-schema.org/draft-07/schema#',
type: 'object',
additionalProperties: false,
propertyNames: {
enum: [
'q-why-i-love-pizza',
'q-i-hate-pizza'
]
},
properties: {
'q-why-i-love-pizza': {
type: 'string',
title: 'If you love pizza, tell us why',
maxLength: 500,
errorMessages: {
required: "Please tell us why you love pizza, or select 'I hate pizza'"
}
},
'q-i-hate-pizza': {
type: 'array',
maxItems: 1,
uniqueItems: true,
items: {
anyOf: [
{
title: 'I hate pizza',
const: 'hate'
}
]
},
errorMessages: {
required: "Please tell us why you love pizza, or select 'I hate pizza'"
}
}
},
allOf: [
{
$ref: '#/definitions/if-not-checked-then-q-why-i-love-pizza-is-required'
}
],
definitions: {
'if-not-checked-then-q-why-i-love-pizza-is-required': {
if: {
not: {
properties: {
'q-i-hate-pizza': {
const: 'hate'
}
}
}
},
then: {
required: ['q-why-i-love-pizza'],
propertyNames: {
enum: [
'q-i-hate-pizza',
'q-why-i-love-pizza'
]
}
}
}
}
}
EDIT:
I expect the following:
{
'q-why-i-love-pizza' : '',
'q-i-hate-pizza' : ['']
}
This should FAIL validation as no values have been selected.
{
'q-why-i-love-pizza' : 'I love pizza because it's amazing',
'q-i-hate-pizza' : ['']
}
This should PASS because the user has entered why they love pizza, so clicking the check box was not necessary.
{
'q-why-i-love-pizza' : '',
'q-i-hate-pizza' : ['hate']
}
This should PASS because although the user has not told us why they love pizza, they have checked the box to indicate they hate pizza.
{
'q-why-i-love-pizza' : 'I am a user, so decided to tell you I hate pizza too',
'q-i-hate-pizza' : ['hate']
}
This should also PASS because I need to accept the possibility that a user will tick the box to say they hate Pizza, but proceed to tell me anyway.
Solution:
{
type: "object",
properties: {
'q-why-i-love-pizza': {
type: 'string',
title: 'If you love pizza, tell us why',
maxLength: 500,
errorMessages: {
required: "Please tell us why you love pizza, or select 'I hate pizza'"
}
},
'q-i-hate-pizza': {
type: 'array',
maxItems: 1,
uniqueItems: true,
items: {
anyOf: [
{
title: 'I hate pizza',
const: 'hate'
}
]
},
errorMessages: {
required: "Please tell us why you love pizza, or select 'I hate pizza'"
}
}
},
allOf: [
{ $ref: "#/definitions/if-not-checked-then-q-offender-contact-description-is-required" }
],
definitions: {
"if-not-checked-then-q-offender-contact-description-is-required": {
if: {
not: {
required: ["q-i-hate-pizza"]
}
},
then: {
required: ["q-why-i-love-pizza"]
}
}
}
}

I've simplified your schema, so this might not be perfect, but this general approach should work.
{
"type": "object",
"properties": {
"q-why-i-love-pizza": { "type": "string", "maxLength": 500 },
"q-i-hate-pizza": { "const": ["hate"] }
},
"allOf": [
{ "$ref": "#/definitions/if-hates-pizza-then-q-why-i-love-pizza-is-required" }
],
"definitions": {
"if-hates-pizza-then-q-why-i-love-pizza-is-required": {
"if": { "not": { "required": ["q-i-hate-pizza"] } },
"then": { "required": ["q-why-i-love-pizza"] }
}
}
}

You're most of the way there, but you've missed something you may not have expected.
Your schema for if-not-checked-then-q-why-i-love-pizza-is-required.
The const keyword is only applicable to the property, IF it exists.
You need to add a required constraint to the if schema.
Obviously this is at a guess, as you haven't provided your JSON instance data.

Related

"INVALID_CURSOR_ARGUMENTS" from Github graphql API

I am using the following query:
query myOrgRepos {
organization(login: "COMPANY_NAME") {
repositories(first: 100) {
edges {
node {
name
defaultBranchRef {
target {
... on Commit {
history(after: "2021-01-01T23:59:00Z", before: "2023-02-06T23:59:00Z", author: { emails: "USER_EMAIL" }) {
edges {
node {
oid
}
}
}
}
}
}
}
}
}
}
}
But with accurate names for the orginization and emails, and am persistantly getting the following error for every repo.
{
"type": "INVALID_CURSOR_ARGUMENTS",
"path": [
"organization",
"repositories",
"edges",
20,
"node",
"defaultBranchRef",
"target",
"history"
],
"locations": [
{
"line": 10,
"column": 29
}
],
"message": "`2021-01-01T23:59:00Z` does not appear to be a valid cursor."
},
If I remove the after field, it works just fine. However, I kind of need it. Acording to all the docs that I have read both after and before take the same timestamp. Can't tell where I am going wrong here.
I have tried:
to narrow the gap between before and after
return only a single repository
remove after (works fine without it)

Counting $lookup and $unwind documents filtered with $match without getting rid of parent document when all results match

I have a collection "Owners" and I want to return a list of "Owner" matching a filter (any filter), plus the count of "Pet" from the "Pets" collection for that owner, except I don't want the dead pets. (made up example)
I need the returned documents to look exactly like an "Owner" document with the addition of the "petCount" field because I'm using Java Pojos with the Mongo Java driver.
I'm using AWS DocumentDB that does not support $lookup with filters yet. If it did I would use this and I'd be done:
db.Owners.aggregate( [
{ $match: {_id: UUID("b13e733d-2686-4266-a686-d3dae6501887")} },
{ $lookup: { from: 'Pets', as: 'pets', 'let': { ownerId: '$_id' }, pipeline: [ { $match: { $expr: { $ne: ['$state', 'DEAD'] } } } ] } },
{ $addFields: { petCount: { $size: '$pets' } } },
{ $project: { pets: 0 } }
]).pretty()
But since it doesn't this is what I got so far:
db.Owners.aggregate( [
{ $match: {_id: { $in: [ UUID("cbb921f6-50f8-4b0c-833f-934998e5fbff") ] } } },
{ $lookup: { from: 'Pets', localField: '_id', foreignField: 'ownerId', as: 'pets' } },
{ $unwind: { path: '$pets', preserveNullAndEmptyArrays: true } },
{ $match: { 'pets.state': { $ne: 'DEAD' } } },
{ "$group": {
"_id": "$_id",
"doc": { "$first": "$$ROOT" },
"pets": { "$push": "$pets" }
}
},
{ $addFields: { "doc.petCount": { $size: '$pets' } } },
{ $replaceRoot: { "newRoot": "$doc" } },
{ $project: { pets: 0 } }
]).pretty()
This works perfectly, except if an Owner only has "DEAD" pets, then the owner doesn't get returned because all the "document copies" got filtered out by the $match. I'd need the parent document to be returned with petCount = 0 when ALL of them are "DEAD". I cannot figure out how to do this.
Any ideas?
These are the supported operations for DocDB 4.0 https://docs.amazonaws.cn/en_us/documentdb/latest/developerguide/mongo-apis.html
EDIT: update to use $filter as $reduce not supported by aws document DB
You can use $filter to keep only not DEAD pets in the lookup array, then count the size of the remaining array.
Here is the Mongo playground for your reference.
$reduce version
You can use $reduce in your aggregation pipeline to to a conditional sum for the state.
Here is Mongo playground for your reference.
As of January 2022, Amazon DocumentDB added support for $reduce, the solution posted above should work for you.
Reference.

JSON Schema: Can I use "if" deeper than root of schema?

I want to validate objects like this
{
type: "user",
data: {id: 1},
}
and
{
type: "account",
data: {uuid: "xxxx"},
}
I thought I can write schema like this
{
type: "object",
properties: {
type: {enum: ["user", "account"]},
data: {
"if": {properties: {type: {const: "user"}}},
"then": {
type: "object",
properties: {
id: {type: "number"}
}
},
"else": {
type: "object",
properties: {
uuid: {type: "string"}
}
},
}
}
}
but it looks like I can't refer to root type field from context of data field. So I have two questions. Can I use if deeper than root? If I can then how to refer to parent or root object from nested object? I'm using node ajv for validation.
You're correct, you cannot apply validation to a different part of your data than where your subschems is being applied. Let me explain a little.
The first thing that happens when processing a JSON Schema is the Schema as a whole is "applied" to the instance as a whole.
properties is an applicator keyword, in that it doesn't assert any validation rules by itself. The VALUES of a properties object are subschemas (which are Schemas in their own right) which are APPLIED to the instance location value when the associated KEY matches.
To give you an exanple, in your Schema, the subschema at properties > type is applied to instance location type, which means the VALUE of the object key type. If type didn't exist in your data, the subschema in your Schema wouldn't do anything (it wouldn't be applied anywhere).
This is broadly the processing model of a Schema against an instance. Hopefully this explains why you can't work in the way you're expecting. However it's still possible to achive what you want.
then and else are conditional applicator keywords, and so need to be applied following the same model.
What you need to do is use if, then, else at the top level, but have deep / nested application of the validation you want to do.
Here's a demo with a new Schema and instance
https://jsonschema.dev/s/sejHF
{
"$schema": "http://json-schema.org/draft-07/schema",
"type": "object",
"properties": {
"type": {
"enum": [
"user",
"account"
]
},
"data": {}
},
"if": {
"properties": {
"type": {
"const": "user"
}
}
},
"then": {
"type": "object",
"properties": {
"data": {
"properties": {
"id": {
"type": "number"
}
}
}
}
},
"else": {
"type": "object",
"properties": {
"data": {
"properties": {
"uuid": {
"type": "string"
}
}
}
}
}
}
If you also want to make sure that id is present when type is user, then you also need to add required: ['id'] in the then subschema. (You'll need to do similar in the else clause if you want to check for uuid too.

JSON Schema - anyOf within conditional?

I am trying to define a JSON schema with conditionals. I built an MVE which already doesn't work as I expect it.
The object I want to validate is:
{
"keiner": false,
"abdominal": true,
"zervikal": false
}
The conditional rule is simple. When "keiner" is true, both other values have to be false. If "keiner" is false, at least one of the other two has to be true.
I wrote this schema:
{
"type": "object",
"properties": {
"keiner": { "type": "boolean" },
"abdominal": { "type": "boolean" }
},
"if": {
"properties": {
"keiner": { "const": true }
}
},
"then": {
"properties" : {
"abdominal": { "const": false },
"zervikal": {"const": false }
}
},
"else": {
"properties": {
"anyOf": [
{ "abdominal": { "const": true } },
{ "zervikal": { "const" : true } }
]
}
}
}
But the Newtonsoft online validator gives the error message
Unexpected token encountered when reading value for 'anyOf'. Expected StartObject, Boolean, got StartArray.
for the line in which ´anyOf´ starts. This confuses me, as all examples I can find show anyOf followed by an array of options.
So what am I doing wrong? Why cannot I have a startArray after anyOf, and how do I write the schema correctly?
I guess this is the schema you are looking for:

How do I query for tag names with :find in SnapshotStore store config

I am trying to setup a filter that is similar to a defect view within a Trend chart. The filter in the defect view is:
(State < Closed) AND (Severity <= Major) AND (Tags !contains Not a Stop Ship)
I cannot seem to get the Tags find to work correctly. Any suggestions?
this.myTrendChart = Ext.create('Rally.ui.chart.Chart', {
storeType: 'Rally.data.lookback.SnapshotStore',
storeConfig: {
find: {
_TypeHierarchy: "Defect",
State: {
$lt: "Closed"
},
Severity: {
$lte: "Major"
},
Tags: {
$ne: "Not a Stop Ship"
},
_ProjectHierarchy: ProjectOid
},
hydrate: ["Priority"],
fetch: ["_ValidFrom", "_ValidTo", "ObjectID", "Priority"]
},
calculatorType: 'My.TrendCalc',
calculatorConfig: {},
chartConfig: {
chart: {
zoomType: 'x',
type: 'line'
},
title: {
text: 'Defects over Time'
},
xAxis: {
type: 'datetime',
minTickInterval: 3
},
yAxis: {
title: {
text: 'Number of Defects'
}
}
}
});
Based on reviewing the JSON messages, I figured out the tag needed to be the ObjectId. Once I found this, I replaced "Not a Stop Ship" with the ObjectId value and the filter worked correctly.