How to use nested JSON in schema - joi

Trying to setup a nested schema as follows. I want be able to reject the schema if bb.c is present when aa.a is present.
I've tried without as well as xor
{
Joi.object().keys({
aa: Joi.object().keys({
a: Joi.string(),
b: Joi.string()
}).unknown(true).with("a", "b"),
bb: Joi.object().keys({
c: Joi.string()
}).unknown(true)
}).xor( "aa.a" , ["bb.c"])
}
With the below object xor fails with ValidationError: "value" must contain at least one of [aa.a, bb.c] yet aa.a exists in the supplied values
{
"aa": {
"a": "fg",
"b": "fg"
},
"bb": {
"c": "l"
}
}
If I try
.without( "aa.a" , ["bb.c"])
then the schema passes although in my mind it should not pass as without should fail when bb.c is present along with aa.a
Is it because the two things are nested in other objects perhaps?
Can we not specify deeply linked stuff like this?
Thanks in advance

You can to use Joi.when() and create a schema like this:
Joi.object({
aa: Joi.object().keys({
a: Joi.string(),
b: Joi.string()
}).unknown(true).with('a', 'b'),
bb: Joi.object().keys({
c: Joi.string()
}).unknown(true)
.when('aa.a', {
is: Joi.exist(),
then: Joi.object({ c: Joi.forbidden() })
})
});
Basically what this does is that if aa.a is present then the bb.c is not allowed and the schema will fail it's validation. With this schema your example will fail as you expect.

Related

What is the difference between the + operator and std.mergePatch in Jsonnet?

Jsonnet's std.mergePatch implements RFC7396, but in my naive testing I didn't find a different between the way it behaved and the + operator; e.g. the + operator respects x+ syntax. std.mergePatch is implemented in Jsonnet itself, which seems to imply that it is different than the + operator, which I'm assuming is a builtin.
What is different about the semantics of these two ways of merging?
Jsonnet's + and std.mergePatch are completely different operations. The + operator operates only on a single level and std.mergePatch traverses the object recursively and merges the nested objects. It's easiest to explain with an example:
local foo = { a: {b1: {c1: 42}}},
bar = { a: {b2: {c2: 2}}};
foo + bar
Output:
{
"a": {
"b2": {
"c2": 2
}
}
}
Note that the bar.a completely replaced foo.a. With + all fields in the second object override the fields in the first object. Compare that with the result of using std.mergePatch(foo, bar).
{
"a": {
"b1": {
"c1": 42
},
"b2": {
"c2": 2
}
}
}
Since both foo and bar have a field a, is is merged and the final results contains both b1 and b2.
So to reiterate, + is a "flat" operation which overrides the fields of the first object with the fields of the second object.
This is not the end of the story, though. You mentioned field+: value syntax and I will try to explain what it really does. In Jsonnet + is not just overwriting, but inheritance in OO sense. It creates an object which is the result of the second object inheriting from the first one. It's a bit exotic to have an operator for that – in all mainstream languages such relationships are statically defined. In Jsonnet, when you do foo + bar, the bar object has access to stuff from foo through super:
{ a: 2 } + { a_plus_1: super.a + 1}
This results in:
{
"a": 2,
"a_plus_1": 3
}
You can use this feature to merge the fields deeper inside:
{ a: {b: {c1: 1}, d: 1}} +
{ a: super.a + {b: {c2: 2} } }
Resulting in:
{
"a": {
"b": {
"c2": 2
},
"d": 1
}
}
This is a bit repetitive, though (it would be annoying if the field name was longer). So we have a nice syntax sugar for that:
{ a: {b: {c1: 1} , d: 1}} +
{ a+: {b: {c2: 2}} }
Please note that in these examples we only did the merging for one particular field we chose. We still replaced the value of a.b. This is much more flexible, because in many cases you can't just naively merge all stuff inside (sometimes a nested object is "atomic" and should be replaced completely).
The version in +: works in the same way as the version with super. The subtle difference is that +: actually translates to something like if field in super then super.field + val else val, so it also returns the same value when super is not provided at all or doesn't have this particular field. For example {a +: {b: 42}} evaluates just fine to {a: { b: 42 }}.
Mandatory sermon: while + is very powerful, please don't abuse it. Consider using using functions instead of inheritance when you need to parameterize something.

Merge nested json using play scala

I have two JSON object (with the same structure):
The first one is
json1 = {a: {b: [{c: "x" , d: val1}, {c: "y" , d: val2}]} }
and the second is
json2 = {a: {b: [{c: "x" , d: val3}, {c: "y" , d: val4}]} }
is there any way to merge these two object to have one object (if c value is same then sum d values):
result = {a: { b: [{c: "x", d: (val1+val3) } , {c: "y", d: (val2+val4) }] } }
if
json2 = {a: {b: [{c: "y" , d: val3}, {c: "z" , d: val4}]} }
result = {a: { b: [{c: "x" , d: val1} , {c: "y", d: (val2+val4+val3)},{c: "z" , d: val4}] } }
Is there any built in method to do this trick. Thanks.
If you know your JSON structure, one way of doing this would be to turn them into case classes and compare them. Here's a way I found (which is by no means optimised):
//Going by your JSON structure of {"a": {"b": [{"c": String , "d": Int}]}}
import play.api.libs.json.{Json, OFormat}
case class A(a: B)
object A{implicit val format: OFormat[A] = Json.format[A]}
case class B(b: Seq[C])
object B{implicit val format: OFormat[B] = Json.format[B]}
case class C(c: Option[String], d: Option[Int])
object C{implicit val format: OFormat[C] = Json.format[C]}
val json1 = Json.parse("""{"a": {"b": [{"c": "x" , "d": 1}, {"c": "y" , "d": 2}]}}""").as[A]
val json2 = Json.parse("""{"a": {"b": [{"c": "x" , "d": 3}, {"c": "y" , "d": 4}]}}""").as[A]
val cSeq: Seq[C] = {
(json1.a.b zip json2.a.b) map {
// List((C(Some(x),Some(1)),C(Some(x),Some(3))), (C(Some(y),Some(2)),C(Some(y),Some(4))))
c =>
val (c1, c2) = c
// assign a value to each element of the pairs
val BLANK_C = C(None, None)
if (c1.c.get == c2.c.get) C(c1.c, Some(c1.d.get + c2.d.get)) else BLANK_C
// if the "c" keys match, add the "d" keys. If not, return an empty C model
// will need to handle if get fails (ie None instead of Some(whatever))
}
}
val json3 = Json.toJson(A(B(cSeq)))
println(json3)
// {"a":{"b":[{"c":"x","d":4},{"c":"y","d":6}]}}
Currently, if the parts don't match then it returns an empty object. You didn't specify what you want to happen when they don't match so I'll leave that up to you to sort out.

Joi: Require exactly two of three fields to be non-empty

Here is a simple version of my schema.
var schema = Joi.object().keys({
a: Joi.string(),
b: Joi.string(),
c: Joi.string()
});
I want a, b, c to be exactly 2 out of 3 non-empty. I.e.:
if a, b are not empty, c should not be set
idem with circular permutation of a,b,c
of course, if 2 or more are empty, throw an error too
Tried using .or() but obviously doesn't do the trick. Looked into .alternatives() but didn't get it working.
It's tricky to find an elegant way to handle this without stumbling into circular dependency issues. I've managed to get something working using .alternatives() and .try().
The solution in its raw form would be this:
Joi.alternatives().try(
Joi.object().keys({
a: Joi.string().required(),
b: Joi.string().required(),
c: Joi.string().required().valid('')
}),
Joi.object().keys({
a: Joi.string().required().valid(''),
b: Joi.string().required(),
c: Joi.string().required()
}),
Joi.object().keys({
a: Joi.string().required(),
b: Joi.string().required().valid(''),
c: Joi.string().required()
})
);
It's certainly not pretty and could get pretty bloated if any more dependencies are introduced.
In an attempt to reduce the amount of repetition, the following would also work:
var base = {
a: Joi.string().required(),
b: Joi.string().required(),
c: Joi.string().required()
};
Joi.alternatives().try(
Joi.object().keys(Object.assign({}, base,
{
a: base.a.valid('')
})),
Joi.object().keys(Object.assign({}, base,
{
b: base.b.valid('')
})),
Joi.object().keys(Object.assign({}, base,
{
c: base.c.valid('')
}))
);

Extending an object with properties in Coffeescript?

I have an array of string literals and I want to loop over them, parse them as JSON and add a property to the resulting object. I want to assign the result of this to a variable.
And I want it to look pretty :)
Right now I am doing this:
strings = ['{"a": "A", "b": "B"}', '{"c": "C", "d": "D"}']
objects = for string in strings
obj = JSON.parse string
obj.e = 'E'
obj
this gives me an array looking like this:
[{ a: 'A', b: 'B', e:'E' }, { c: 'C', d: 'D', e:'E' }]
Now this works but it looks a bit ugly. I guess what I'd like is something like http://documentcloud.github.com/underscore/#extend (but I don't want to include underscore just for that one method)
I found this issue: https://github.com/jashkenas/coffee-script/issues/880
and this pullrequest: https://github.com/jashkenas/coffee-script/pull/2177
but the pullrequest is open and the issue is closed so I assume there's at least no operator for doing this?
But when writing that code I can't help thinking that there's got to be a better way, so any tips would be welcome.
Here is some reference: http://coffeescript.org/documentation/docs/helpers.html
extend = exports.extend = (object, properties) ->
for key, val of properties
object[key] = val
object
strings = ['{"a": "A", "b": "B"}', '{"c": "C", "d": "D"}']
objects = for string in strings
extend JSON.parse(string), e: 'E'

Dedupe MongoDB Collection

I'm new to NoSQL, so sorry if this is very basic. Let's say I have the following collection:
{
a: 1,
b: 2,
c: 'x'
},
{
a: 1,
b: 2,
c: 'y'
},
{
a: 1,
b: 1,
c: 'y'
}
I would like to run a "Dedupe" query on anything that matches:
{
a: 1,
b: 2
... (any other properties are ignored) ...
},
So after the query is run, either of the following remaining in the collection would be fine:
{
a: 1,
b: 2,
c: 'y'
},
{
a: 1,
b: 1,
c: 'y'
}
OR
{
a: 1,
b: 2,
c: 'x'
},
{
a: 1,
b: 1,
c: 'y'
}
Just so long as there's only one document with a==1 and b==2 remaining.
If you always want to ensure that only one document has any given a, b combination, you can use a unique index on a and b. When creating the index, you can give the dropDups option, which will remove all but one duplicate:
db.collection.ensureIndex({a: 1, b: 1}, {unique: true, dropDups: true})
This answer hasn't been updated in a while. It took me a while to figure this out. First, using the Mongo CLI, connect to the database and create an index on the field which you want to be unique. Here is an example for users with a unique email address:
db.users.createIndex({ "email": 1 }, { unique: true })
The 1 creates the index, along with the existing _id index automatically created.
Now when you run create or save on an object, if that email exists, Mongoose will through a duplication error.
I don't know of any commands that will update your collection in-place, but you can certainly do it via temp storage.
Group your documents by your criteria (fields a and b)
For each group pick any document from it. Save it to temp collection tmp. Discard the rest of the group.
Overwrite original collection with documents from tmp.
You can do this with MapReduce or upcoming Aggregation Framework (currently in unstable branch).
I decided not to write code here as it would take the joy of learning away from you. :)