Given the following schema:
{
"$schema": "http://json-schema.org/draft-04/schema#",
"definitions": {
"worktype": {
"blue": {
"enum": [1, 2, 3]
},
"red": {
"enum": [4, 5, 6]
}
}
}
}
I can validate an array to be either consisting of all "blue" items or "red" items like this:
{
"type": "array",
"items": {
"$ref": "#/definitions/worktype/red" // or worktype/blue
}
}
But how can I validate an array to have only either "blue" or "red" worktypes, but not mixed ones?
For example:
[1, 2]: Valid (only blue)
[5]: Valid (only red)
[1, 6]: Invalid (mixed blue and red)
My first idea was using oneOf:
{
"type": "array",
"items": {
"oneOf": [
{"$ref": "#/definitions/worktype/red"},
{"$ref": "#/definitions/worktype/blue"},
]
}
}
But this checks each entry for itself against the schemas, so for example [1, 6] is valid, too. (Afaik in this case the oneOf does not mean "all items must be valid against oneOf together" but "each item must be valid against oneOf for itself").
How can I write a schema for an array that either has only "blue" or "red" worktypes, but not both? Is this possible?
I got it, my first idea was on the right way, but oneOf was simply nested one level too deep. This works like I want:
{
"type": "array",
"oneOf": [
{
"items": {
"$ref": "#/definitions/worktype/red"
}
},
{
"items": {
"$ref": "#/definitions/worktype/blue"
}
}
]
}
Related
I'm very new to MongoDB and I need help figuring out how to perform aggregation on a key in MongoDB and use that result to return matches.
For example, if I have a collection called Fruits with the following documents:
{
"id": 1,
"name": "apple",
"type": [
"Granny smith",
"Fuji"
]
}, {
"id": 2,
"name": "grape",
"type": [
"green",
"black"
]
}, {
"id": 3,
"name": "orange",
"type": [
"navel"
]
}
How do I write a query that will return the names of the fruits with 2 types, ie apple and grape?
Thanks!
Demo - https://mongoplayground.net/p/ke3VJIErhvb
use $size to get records with 2 number of type
https://docs.mongodb.com/manual/reference/method/db.collection.find/#mongodb-method-db.collection.find
The $size operator matches any array with the number of elements specified by the argument. For example:
db.collection.find({
type: { "$size": 2 } // match document with type having size 2
},
{ name: 1 } // projection to get name and _id only
)
To get the length of the array you should use $size operator in $project pipeline stage
So the pipeline $project stage should look like this
{
"$project": {
"name": "$name",
type: {
"$size": "$type"
}
}
}
Here is an working example of the same ⇒ https://mongoplayground.net/p/BmS9BGhqsFg
Let's say I have three document structured like so :
{
"_id": 1,
"conditions": [
["Apple", "Orange"],
["Lemon"],
["Strawberry"]
]
},
{
"_id": 2,
"conditions": [
["Apple"],
["Strawberry"]
]
},
{
"_id": 3,
"conditions": [
["Apple", "Lime"]
]
}
And I have an array, I'll call it ARC for this example :
ARC = [
"Apple",
"Lime",
"Banana",
"Avocado",
"Cherry"
]
I would like to return all document in which all conditions subarray values can't be found in the ARC array.
For example, with the data above, the first document should be returned because :
The Apple AND Orange combination is not in the ARC array
Lemon is not in the ARC array
Strawberry is not in the ARC array
The second document shouldn't be returned because :
Apple is in the ARC array
And the third document shouldn't be returned because :
The Apple AND Lime combination is in the ARC array
I've tried
db.example.find({"conditions": {$not: {$elemMatch: {$all: [ARC]}}}})
But it seems way too simple.. So, as expected, it doesn't work.
I know mongoDB is pretty powerful with all the aggregation and stuff but I'm a bit lost.
Do you know if it's possible with a query alone and if so, what should I look for ?
The query below should solve your problem.
var ARC = [
"Apple",
"Lime",
"Banana",
"Avocado",
"Cherry"
];
db.test.find(
{ $expr: {
$eq: [
{ $filter: { input: "$conditions", as: "c", cond: { $setIsSubset: [ "$$c", ARC] } } },
[ ]
]
}
}
)
It's made up of lots of parts so I'll try to break it down a bit, The first part is $expr within a find (or can be used within a $match in an aggregation) this allows us aggregation expressions within the query. So this allows us to use a $filter.
The $filter expression allows us to filter down the arrays in the condition field to check if any are a subset of the array ARC passed in.
We can actually take that filter an execute it on its own using an aggregation query:
db.test.aggregate([
{ $project: {
"example" : { $filter: { input: "$conditions", as: "c", cond: { $setIsSubset: [ "$$c", ARC] } } }
} }])
{ "_id" : 1, "example" : [ ] }
{ "_id" : 2, "example" : [ [ "Apple" ] ] }
{ "_id" : 3, "example" : [ [ "Apple", "Lime" ] ] }
The last part of the query is the $eq which is taking the value that is created with the filter and then matching it against an empty array [ ].
This is an aggregation approach. You should use $setIsSubset.
Below should be helpful:
db.collection.aggregate([
{
$match: {
$expr: {
$eq: [
true,
{
$allElementsTrue: {
$map: {
input: "$conditions",
as: "c",
in: {
$not: {
$setIsSubset: [
"$$c",
[
"Apple",
"Lime",
"Banana",
"Avocado",
"Cherry"
]
]
}
}
}
}
}
]
}
}
}
])
MongoPlayGroundLink
I need to create a JSON schema for data that comes as an array directly within the root object, unnamed. An MWE for this kind of JSON would be:
{
[
{
"veggieName": "potato",
"veggieLike": true
},
{
"veggieName": "broccoli",
"veggieLike": false
}
]
}
I have seen examples for schemas which validate such an array which is not nested in an object. I have also seen examples which work when the array is named, for example
{
vegetables : [
{
"veggieName": "potato",
"veggieLike": true
},
{
"veggieName": "broccoli",
"veggieLike": false
}
]
}
This second example can be validated by the schema
{
"$id": "https://example.com/arrays.schema.json",
"$schema": "http://json-schema.org/draft-07/schema#",
"description": "A representation of a person, company, organization, or place",
"type": "object",
"properties": {
"vegetables": {
"type": "array",
"items": { "$ref": "#/definitions/veggie" }
}
},
"definitions": {
"veggie": {
"type": "object",
"required": [ "veggieName", "veggieLike" ],
"properties": {
"veggieName": {
"type": "string",
"description": "The name of the vegetable."
},
"veggieLike": {
"type": "boolean",
"description": "Do I like this vegetable?"
}
}
}
}
}
But the problem is, as soon as the name "vegetables" is removed, I was not able to find a way to define a valid schema. How do I properly represent my data structure in a schema?
(MWEs derived from http://json-schema.org/learn/miscellaneous-examples.html).
The schema you are looking for is the following:
{
"$id":"https://example.com/arrays.schema.json",
"$schema":"http://json-schema.org/draft-07/schema#",
"description":"A representation of a person, company, organization, or place",
"type":"array",
"items":{
"type":"object",
"required":[
"veggieName",
"veggieLike"
],
"properties":{
"veggieName":{
"type":"string",
"description":"The name of the vegetable."
},
"veggieLike":{
"type":"boolean",
"description":"Do I like this vegetable?"
}
}
}
}
You also need to modify your base array instance, your original one (the "unnamed" array) was not valid JSON:
[
{
"veggieName":"potato",
"veggieLike":true
},
{
"veggieName":"broccoli",
"veggieLike":false
}
]
Unlike XML, where you are allowed a single root node per document only, in JSON you can have either a type or an array as a root type.
I have a collection in MongoDB which has a field called "geometry" with latitude and longitude like this :
{
"abc":"xyz",
"geometry" : [
{
"lat" : 45.0,
"lng" : 25.0
},
{
"lat" : 46.0,
"lng" : 26.0
}
]
}
I want to convert the field geometry into something like this, to be compliant with the GeoJSON format:
{
"abc":"xyz",
"geometry": {
"type": "LineString",
"coordinates": [
[
25.0,
45.0
],
[
26.0,
46.0
]
]
}
}
The operation essentially involves taking an array of objects with two K/V pairs and pick only the values and store them as array of arrays(with the order reversed- so value of "lng" comes first).
My failed attempts:
I tried using an aggregate and tried to project the following:
"geometry": {"type":"LineString", "coordinates":["$points.lng","$points.lat"] }
which gave me a result similar to:
"geometry": {
"type": "LineString",
"coordinates": [
[
25.0,
26.0
],
[
45.0,
46.0
]
]
}
I've tried working with this and modifying data record by record, but the results are not consistent. And, I'm trying to avoid going through every record and changing the structure one by one. Is there a way to do this efficiently ?
You would think that the following code should theoretically work:
db.collection.aggregate({
$project: {
"abc": 1, // include the "abc" field in the output
"geometry": { // add a new geometry sub-document
"type": "LineString", // with the hardcoded "type" field
"coordinates": {
$map: {
"input": "$geometry", // transform each item in the "geometry" array
"as": "this",
"in": [ "$$this.lng", "$$this.lat" ] // into an array of values only, ith "lng" first, "lat" second
}
}
}
}
}, {
$out: "result" // creates a new collection called "result" with the transformed documents in it
})
However, the way MongoDB works at this stage as per SERVER-37635 is that the above query results in a surprising output where the coordinates field contains the desired result several times. So the following query can be used to generate the desired output instead:
db.collection.aggregate({
$addFields: {
"abc": 1,
"geometry2": {
"type": "LineString",
"coordinates": {
$map: {
"input": "$geometry",
"as": "this",
"in": [ "$$this.lng", "$$this.lat" ]
}
}
}
}
}, {
$project: {
"abc": 1,
"geometry": "$geometry2"
}
}, {
$out: "result"
})
In the comments section of the JIRA ticket mentioned above, Charlie Swanson mentions another workaround which uses $let to "trick" MongoDB into interpreting the query in the desired way. I re-post it here (note that it's missing the $out part):
db.collection.aggregate([
{
$project: {
"abc": 1,
"geometry": {
$let: {
vars: {
ret: {
"type": "LineString",
"coordinates": {
$map: {
"input": "$geometry",
"as": "this",
"in": [
"$$this.lng",
"$$this.lat"
]
}
}
}
},
in: "$$ret"
}
}
}
}
])
I've a JSON Schema and a sample input. I need to write a generic schema which can handle the array regardless the length of the array. Currently, I need to write schema for each of the index in the array.
JSON Schema
{
"title":"Example",
"$schema":"http://json-schema.org/draft-04/schema#",
"type":"array",
"items":[
{
"oneOf":[
{
"multipleOf": 3
}
]
},
{
"oneOf":[
{
"multipleOf": 3
},
{
"multipleOf": 5
}
]
}
]
}
Sample Input
[
3,
5
]
I need a schema which can validate [1,3,5,6,3,5,4,......] (regardless the length)
If you put a schema directly in items, instead of using an array, then it applies to all array items:
{
"type": "array",
"items": {
"oneOf": [
{"multipleOf": 3},
{"multipleOf": 5}
]
}
}
If you want to describe an initial set of items with specific schemas, and all the following ones with a generic one, then use an array with items, and a schema in additionalItems:
{
"type": "array",
"items": [
{"multipleOf": 3},
...
],
"additionalItems": {
"oneOf": [
{"multipleOf": 3},
{"multipleOf": 5}
]
}
}