How to flatten nested object to single depth object with JSONata? - jsonata

I am new to JSONata and am having some trouble to create a flatten function.
I want to turn this input:
{
"user": {
"key_value_map": {
"CreatedDate": "123424",
"Department": {
"Name": "XYZ"
}
}
}
}
Into this:
{
"user.key_value_map.CreatedDate": "123424",
"user.key_value_map.Department.Name": "XYZ"
}
Can anyone help me? Searched here and on google and couldn't find something that would lead me in the right direction.
Thanks

(
$fn := function($o, $prefix) {
$each($o, function($v, $k) {(
$name := $join([$prefix,$k], '.');
$type($v) = 'object' ? $fn($v, $name): {
$name: $v
}
)}) ~> $merge()
};
$fn($)
)
https://try.jsonata.org/WCUxC-r4_

Related

Spring Boot Mongo update nested array of documents

I'm trying to set an attribute of a document inside an array to uppercase.
This is a document example
{
"_id": ObjectId("5e786a078bc3b3333627341e"),
"test": [
{
"itemName": "alpha305102992",
"itemNumber": ""
},
{
"itemName": "beta305102630",
"itemNumber": "P5000"
},
{
"itemName": "gamma305102633 ",
"itemNumber": ""
}]
}
I already tried a lot of thing.
private void NameElementsToUpper() {
AggregationUpdate update = AggregationUpdate.update();
//This one does not work
update.set("test.itemName").toValue(StringOperators.valueOf(test.itemName).toUpper());
//This one also
update.set(SetOperation.set("test.$[].itemName").withValueOfExpression("test.#this.itemName"));
//And every variant in between these two.
// ...
Query query = new Query();
UpdateResult result = mongoTemplate.updateMulti(query, update, aClass.class);
log.info("updated {} records", result.getModifiedCount());
}
I see that Fields class in spring data is hooking into the "$" char and behaving special if you mention it. Do not seem to find the correct documentation.
EDIT: Following update seems to work but I do not seem to get it translated into spring-batch-mongo code
db.collection.update({},
[
{
$set: {
"test": {
$map: {
input: "$test",
in: {
$mergeObjects: [
"$$this",
{
itemName: {
$toUpper: "$$this.itemName"
}
}
]
}
}
}
}
}
])
Any solutions?
Thanks!
For now I'm using which does what i need. But a spring data way would be cleaner.
mongoTemplate.getDb().getCollection(mongoTemplate.getCollectionName(Application.class)).updateMany(
new BasicDBObject(),
Collections.singletonList(BasicDBObject.parse("""
{
$set: {
"test": {
$map: {
input: "$test",
in: {
$mergeObjects: [
"$$this",
{
itemName: { $toUpper: "$$this.itemName" }
}
]
}
}
}
}
}
"""))
);

Mongodb aggregation remove null values from object with nested properties

Is there a way to remove literally all null or empty string values from an object? We have an aggregation which creates an object with empty fields and empty objects should the value be null.
What we wish to do is remove all null properties and empty objects and recreate the object, in order to keep the data as small as possible.
e.g. in the following object, only 'test' and 'more-nested-data' should be taken into account, the rest can be removed
{
"test": "some",
"test2": {
},
"test3": {
"some-key": {
},
"some-other-key": {
"more-nested-data": true,
"more-nested-emtpy": null
}
}
}
which should become:
{
"test": "some",
"test3": {
"some-other-key": {
"more-nested-data": true
}
}
}
I tried a lot, but I think by using objectToArray that something could be done, but I have not found the solution yet. The required aggregation should need to recursively (or by defined levels) remove null properties and empty objects.
Use the $function operator available in 4.4 (Aug 2021) to do this recursively as you note. Given this input which is a slightly expanded version of that supplied in the question:
var dd = {
"test": "some",
"test2": { },
"test3": {
"some-key": { },
"some-other-key": {
"more-nested-data": true,
"more-nested-emtpy": null,
"emptyArr": [],
"notEmptyArr": [
"XXX",
null,
{"corn":"dog"},
{"bad":null},
{"other": {zip:null, empty:[], zap:"notNull"}}
]
}
}
}
db.foo.insert(dd);
then this pipeline:
db.foo.aggregate([
{$replaceRoot: {newRoot: {$function: {
body: function(obj) {
var process = function(holder, spot, value) {
var remove_it = false;
// test FIRST since [] instanceof Object is true!
if(Array.isArray(value)) {
// walk BACKWARDS due to potential splice() later
// that will change the length...
for(var jj = value.length - 1; jj >= 0; jj--) {
process(value, jj, value[jj]);
}
if(0 == value.length) {
remove_it = true;
}
} else if(value instanceof Object) {
walkObj(value);
if(0 == Object.keys(value).length) {
remove_it = true;
}
} else {
if(null == value) {
remove_it = true;
}
}
if(remove_it) {
if(Array.isArray(holder)) {
holder.splice(spot,1); // snip out the val
} else if(holder instanceof Object) {
delete holder[spot];
}
}
};
var walkObj = function(obj) {
Object.keys(obj).forEach(function(k) {
process(obj, k, obj[k]);
});
}
walkObj(obj); // entry point!
return obj;
},
args: [ "$$CURRENT" ],
lang: "js"
}}
}}
]);
produces this result:
{
"_id" : 0,
"test" : "some",
"test3" : {
"some-other-key" : {
"more-nested-data" : true,
"notEmptyArr" : [
"XXX",
{
"corn" : "dog"
},
{
"other" : {
"zap" : "notNull"
}
}
]
}
}
}
A convenient way to debug such complex functions is by declaring them as variables outside of the pipeline and running data through them to simulate the documents (objects) coming out the database, e.g.:
ff = function(obj) {
var process = function(holder, spot, value) {
var remove_it = false;
// test FIRST since [] instanceof Object is true!
if(Array.isArray(value)) {
...
printjson(ff(dd)); // use the same doc as above
You can put print and other debugging aids into the code and then when you are done, you can remove them and call the pipeline to process the real data as follows:
db.foo.aggregate([
{$replaceRoot: {newRoot: {$function: {
body: ff, // substitute here!
args: [ "$$CURRENT" ],
lang: "js"
}}
}}
]);
Sounds like the unwind operator would help. Checkout the unwind operator at https://docs.mongodb.com/manual/reference/operator/aggregation/unwind/

How to use jsonata to return multiple objects, distributing the first key/value, which will always be dateTime, with the remaining keys varying

How can jsonata be used to transform:
{
"data": [
{
"dateTime": "2019-10-19T12:53:54.043Z",
"Reactor3.Level": 1.51860072870498,
"Reactor3.Temp": 27.1360543141452
},
{
"dateTime": "2019-10-19T12:55:54.043Z",
"Reactor3.Press": 88.9,
"Reactor3.Temp": 24.1418981047159
}
]
}
Into a series of objects containing only two keys of {"dateTime":"2019-10-19T12:53:54.043Z", key[1]:value[1]},{"dateTime":"2019-10-19T12:53:54.043Z",key[2]:value[2]}
Such as the following:
{
"data": [
{
"dateTime": "2019-10-19T12:53:54.043Z",
"Reactor3.Level": 1.51860072870498
},
{
"dateTime": "2019-10-19T12:53:54.043Z",
"Reactor3.Temp": 27.1360543141452
},
{
"dateTime": "2019-10-19T12:55:54.043Z",
"Reactor3.Press": 88.9
},
{
"dateTime": "2019-10-19T12:55:54.043Z",
"Reactor3.Temp": 24.1418981047159
}
]
}
Where the first key will always be dateTime, the other keys will vary, and I'd like to break out all other keys/values by dateTime?
Here Reactor3.Level, Reactor3.Temp, Reactor3.Press are just examples that will change.
EDIT: In the following I am adding a more generic version of my problem.
I am essentially looking for a JSONata query to transform this input:
{
"data": [
{
"TS": "TS1",
"V1": 1,
"V2": 2
},
{
"TS": "TS2",
"V2": 9,
"V3": 8,
"V4": 7
}
]
}
where key "TS" is the first key in every set,
and the other keys will vary.
And transform it into this output:
{
"data": [
{
"TS": "TS1",
"V1": 1
},
{
"TS": "TS1",
"V2": 2
},
{
"TS": "TS2",
"V2": 9
},
{
"TS": "TS2",
"V3": 8
},
{
"TS": "TS2",
"V4": 7
}
]
}
[SOLVED] SteveR solved my problem below, I just want to add a Node-RED "payload" friendly version of the solution here:
Link: http://try.jsonata.org/HJCjoHxoS
payload modified JSONata:
payload.$ ~> | $ | { "data": data.(
$obj := $;
$key := "dateTime";
$keys($obj)[$ != $key].{
$key : $lookup($obj, $key),
$: $lookup($obj, $)
}
) } |
Thanks again to #SteveR
Interesting challenge, Jeff -- but if object properties do not have a guaranteed order, it may not be possible to do reliably... is it always the "first" property that needs to be distributed over the rest of them, or can you say for sure that it will always be called TS (using your simplified example)?
That being said, I did find an expression that seems to work, albeit only within my limited testing:
data.(
$obj := $;
$keys($obj)[[1..100]].(
$key := $keys($obj)[0];
{
$key : $lookup($obj, $key),
$: $lookup($obj, $)
}
)
)
Essentially, for each of the incoming objects, get its keys -- for each key with index between 1 - 100 (I know, it's an artificial limit), create the new object { key[0]: val[0], key[i]: val[i] }
You can try to tweak it here if you would like => http://try.jsonata.org/ryh3hqM5H
If you can say for sure that the first property is called "TS", I would use that named property to simplify the expression, like this:
data.(
$obj := $;
$key := "TS";
$keys($obj)[$ != $key].{
$key : $lookup($obj, $key),
$: $lookup($obj, $)
}
)
I'm sure there are better ways to accomplish the same transformation, but I hope this helps...
Good luck!
EDIT: I see that I lost the outer object with its data property. You can just wrap it around the expression(s) I listed, or if there are other properties that need to be maintained, use the object transform syntax to only modify that one data property:
$$ ~> | $ | { "data": data.(
$obj := $;
$key := "dateTime";
$keys($obj)[$ != $key].{
$key : $lookup($obj, $key),
$: $lookup($obj, $)
}
) } |

Accessing nested fields with Rethinkdb

So I have a result that looks like this
"data": {
"randomkeyllasdkjflk": {
"name": "John Doe"
},
"anotherrandomkeyadf": {
"name": "Mona Lee"
}
}
and I want to access the name.
This is what I tried:
r.table('users').filter(function (doc) {
return doc('data').coerceTo('array').map(function(ref) {
return ref('name')
}).contains("John")
});
But will produce an error that says:
e: Cannot perform bracket on a non-object non-sequence
"randomkeyllasdkjflk"
use this:
r.table('users').filter(function (doc) {
return doc('data').keys().map(function(ref) {
return doc('data')(ref)('name')
}).contains("John")
});

Output not rendering using a method

Here is what I want to accomplish:
<span>{{ getGenres(movie.genre_ids) }}</span>
should output:
Action, Adventure, ..etc
I've got a JSON file which contains the following structure:
[
{
"id": 28,
"name": "Action"
},
{
"id": 12,
"name": "Adventure"
}
]
Here is my method:
getGenres(genre_ids) {
Movies.getGenres(genre_ids);
}
Movies.js contains the method is calling to, which is:
getGenres(genre_ids) {
let genres_array = [];
for (let i = 0; i < genre_ids.length; i++) {
let matching_genre = genres.filter(genre => genre.id === genre_ids[i]);
genres_array.push(matching_genre[0].name);
}
return genres_array;
}
The issue here is that it doesn't output the names, but if I console.log(genres_array); it does work.
Any help would be appreciated.
I cannot find return here
getGenres(genre_ids) {
Movies.getGenres(genre_ids);
}
I trust it should be like this
getGenres(genre_ids) {
return Movies.getGenres(genre_ids).join(', ');
}

Resources