Get to deeply nested object by array path of ids - json

I have this JSON data which has questions with options and questions nested in them. So the user picks an answer and the next question is chosen based on that answer and so on.... I have an array that is essentially a path (of ids) to the specific question object I'm looking for. How can I get to that object using my array of paths in my react component?
path = ["525289", "128886", "123456", "7547", "35735"]
{"question": {
"id":"525289",
"options": [
{
"id":"128886",
"optionName":{"alt":"123"},
"question": {
"id":"123456",
"title": "soidjfs",
"options": [
{
"id":"7547",
"optionName":{"alt":"new"},
"question": {
"id":"35735",
"title": "soidjfs",
"options": [
]
}
},
{
"id":"1234",
"optionName":{"alt":"new"},
"question": {
"id":"25825",
"title": "soidjfs",
"options": [
]
}
}
]
}
},
{
"id":"1234569999",
"optionName":{"alt":"123"},
"question": {
"id":"3457",
"title": "soidjfs",
"options": [
{
"id":"999998",
"optionName":{"alt":"new"},
"question": {
"id":"2134678",
"title": "soidjfs",
"options": [
]
}
},
{
"id":"55555",
"optionName":{"alt":"new"},
"question": {
"id":"123456159",
"title": "soidjfs",
"options": [
]
}
}
]
}
}
]
}
}

This could be done with a path similar (although not too similar) to what you provide, using the reduce function although there are some inconsistencies in your examples. For example, question seems to have a 1:1 relationship with option, so it doesn't seem like it needs filtering on traversal?
Alternatively as Mitya stated, you can use a JSON querying library. These are much more general purpose and so will be more verbose than a bespoke solution with contextual knowledge of your data structures.
I've used a much more simple, recursive parent-children model here:
let data = [
{
id: "123",
children: [
{
id: "456A",
title: "xyz",
children: [
{
id: "789AA",
message: "Foo"
},
{
id: "789AB",
message: "Bar"
}
]
},
{
id: "456B",
title: "abc",
children: [
{
id: "789BA",
message: "Hello"
},
{
id: "789BB",
message: "World!"
}
]
}
]
}
];
let path1 = ["123", "456A", "789AA"];
let path2 = ["123", "456C", "789CA"]; // Invalid as 456C isn't a valid item at this point in the tree
let path3 = ["123", "456B", "789BB"];
function select(path, data) {
return path.reduce((acc, v) => acc && acc.children.find(c => c.id === v), {children: data});
}
console.log(select(path1, data));
console.log(select(path2, data));
console.log(select(path3, data));
Output:
{ id: '789AA', message: 'Foo' }
undefined
{ id: '789BB', message: 'World!' }

If you're open to using a (small) library, you could use something like very own J-Path, which allows you to query and traverse an object (first argument) using XPath (second), which is normally used to query XML.
let item = jpath(object, 'question[id="525289"]/options/item[id="128886"]/question[id="123456"]/options/item[id="7547"]/question[id="35735"]');
Or, more simply, we could go straight to the target using the final ID:
let item = jpath(object, '//question[id="35735"]');
If you don't want a library, you'll need to step through the IDs manually and use a while loop until you find the data item you need.

I think your approach to this question is wrong. I think a better way would be to define each question separately as their own object and then have arrays of nested objects.
var questionId1 = {}
var questionId2 = {}
var questionId3 = {}
var questionId4 = {}
var questionId5 = {}
questionId1.options = [questionId2, questionId3]
questionId2.options = [questionId4]
questionId4.options = [questionId5]
Traversing things that way can make the idea of the path obsolete. You'd just need the name of the current question to display the options. When the next question is selected, the parent is no longer relevant, only its children matter.
Still, if you're stuck using this method for one reason or another, you can use the . notation to access nested elements inside of objects. In this case, it sounds like you're looking for each element's id and wanting the element's options object. Assuming you want the option that traverses the user's path and returns the options at the end of the path, you're going to need a lot of nested ifs to make sure that certain properties are there and that they're not empty and loops to go deeper into arrays.
Here's a fiddle that shows that method.

Related

Filter a list with multiple conditions using WITH in Cypher,Neo4j

I'm new to Neo4j. I'm kind of got stuck for loading a JSON file into Neo4j database.
I have a list of tags for many transactions. Each transaction tag can have one or two tags: third-party and category.
Here is an example JSON.
{
"tags": [
[
{
"thirdParty": "Supermarkets"
},
{
"category": "Groceries"
}
],
[
{
"category": "Rent"
}
],
[
{
"thirdParty": "Education"
},
{
"category": "Education"
}
]
.....
]
}
I only want to find objects that have both category and thirdParty(some objects only have one tag).
And here is my code, the list is just a list of categories I want.
CALL apoc.load.json("file:///full.json")
YIELD value
with ['Gambling','Subscription TV','Telecommunications'] as list
UNWIND value.tags as tags
WITH [tag in tags where tag.category IN list AND tag.thirdParty IS NOT NULL] AS temp
RETURN temp
The weird thing is this always return me a list null. But with only one condition then it will work, like only find objects in the list or only thirdParty is not null.
But combine the 2 conditions together, it will always return a list of null.
Does anyone know how to fix this?
Thanks
Your UNWIND seems to return a list of maps, not a map. But the good thing is that with apoc, you can convert it easily.
this works:
WITH [
[
{
thirdParty: "Supermarkets"
},
{
category: "Groceries"
}
],
[
{
category: "Rent"
}
],
[
{
thirdParty: "Education"
},
{
category: "Education"
}
]
]
AS tagsList
WITH tagsList, ['Groceries','Subscription TV','Telecommunications'] as list
UNWIND tagsList as tags
WITH list,
apoc.map.fromPairs(
REDUCE(arr=[],tag IN tags |
arr+[[keys(tag)[0],tag[keys(tag)[0]]]]
)
) AS tagMap
WHERE tagMap.category IN list AND tagMap.thirdParty IS NOT NULL
RETURN tagMap
returns
{
"thirdParty": "Supermarkets",
"category": "Groceries"
}

How to add a new array of keys to a JSON object in Angular (typescript)?

I have tried looking for a solution, but the closest I cold find were with adding a simple key with a value...
So, I have this Json object:
{
"answers": [
{
"phrase": "Why?",
"nextIntentId": "4a10fd2b-bc6b-4563-8d97-34ef6dec1ba9"
},
{
"phrase": "Faster recovery?",
"nextIntentId": "4f9eeecf-6477-435b-9f1b-43304748a4e5"
}
],
"intentId": "19f458e8-ce1f-43eb-beb8-b8ef4e7ff4c7"
}
And I want to add an array to this object:
"messages" : []
The idea is, that afterwards I can intent.messages.push(object) objects to this array, just like in the "answers" : [] array
{
"answers": [
{
"phrase": "Why?",
"nextIntentId": "4a10fd2b-bc6b-4563-8d97-34ef6dec1ba9"
},
{
"phrase": "Faster recovery?",
"nextIntentId": "4f9eeecf-6477-435b-9f1b-43304748a4e5"
}
],
"intentId": "19f458e8-ce1f-43eb-beb8-b8ef4e7ff4c7",
"messages" : [] <<------------ I want to add this aray
}
I have also tried object.push('messages[]') and got Error: this.intent.push is not a function
Thankyou for any kind of help in advance!
You can set the value directly to a blank array:
this.intent.messages = [];

Retrieve Partial Array Properties

We are storing documents like this:
{
"FullName": "Jim",
"Children": [
{
"Name": "Sue",
"Hobbies": [
{
"Title": "Stamps",
"EnthusiasmLevel": 1
},
{
"Title": "Baseball",
"EnthusiasmLevel": 5
}
]
},
{
"Name": "Frank",
"Hobbies": [
{
"Title": "Dance",
"EnthusiasmLevel": 3
},
{
"Title": "Juggling",
"EnthusiasmLevel": 2
}
]
}
]
}
Usually when we are retrieving this "Jim" record, we'd want the full details on him and his children, but in certain circumstances we are going to want his name and just each child's name and the title of each of their hobbies.
Is there a straight-forward (or not) way of going about retrieving just parts of these documents while retaining (or rebuilding on the fly) their structure?
If I try something like:
SELECT p.FullName, [{"Name": child.Name}] AS Children
FROM People AS p
JOIN child in p.Children
I can construct an array, but I (obviously, per the join) get a record per child instead of one. If I instead remove the join and try to access these properties via the parent collection, I can't get at them.
What I want to get back is:
{
"FullName": "Jim",
"Children": [
{
"Name": "Sue",
"Hobbies": [
{"Title": "Stamps"},
{"Title": "Baseball"}
]
},
{
"Name": "Frank",
"Hobbies": [
{"Title": "Dance"},
{"Title": "Juggling"}
]
}
]
}
Even if I had to lose the structure, I'd still want to get back a single record representing "Jim" that contains his children's names and hobbies, but right now I'm just retrieving everything and doing the filtering on the client side, which is less than ideal.
Is what I'm after possible?
Based on your situation, I suggest you using Stored Procedure to process your data on the server side. I test sample code for you and it matches your requirements.
Sample Code:
function sample() {
var collection = getContext().getCollection();
var isAccepted = collection.queryDocuments(
collection.getSelfLink(),
'SELECT p.FullName, p.Children FROM People AS p',
function (err, feed, options) {
if (err) throw err;
if (!feed || !feed.length) {
var response = getContext().getResponse();
response.setBody('no docs found');
}
else {
var response = getContext().getResponse();
var returnResult = [];
for(var i = 0;i<feed.length;i++){
var peopleObj = feed[i];
ChildrenArray = [];
for(var j = 0;j<peopleObj.Children.length;j++){
console.log(j)
var childObj = peopleObj.Children[j];
HobbiesArray = [];
for(var k = 0; k < childObj.Hobbies.length;k++){
var hobbyObj = childObj.Hobbies[k];
map ={};
map["Title"] = hobbyObj.Title;
HobbiesArray.push(map);
}
childObj.Hobbies = HobbiesArray;
}
ChildrenArray.push(childObj);
}
returnResult.push(peopleObj);
getContext().getResponse().setBody(returnResult);
}
});
if (!isAccepted) throw new Error('The query was not accepted by the server.');
}
Output :
[
{
"FullName": "Jim",
"Children": [
{
"Name": "Sue",
"Hobbies": [
{
"Title": "Stamps"
},
{
"Title": "Baseball"
}
]
},
{
"Name": "Frank",
"Hobbies": [
{
"Title": "Dance"
},
{
"Title": "Juggling"
}
]
}
]
}
]
Any concern , please feel free to let me know.
I would suggest a simpler query-only solution. A la:
select jim.FullName,
ARRAY(
select child.Name,
ARRAY(
select hobby.Title from hobby in child.Hobbies
) as "Hobbies"
from jim join child in jim.Children
) as "Children"
from jim
While SP does have its uses, in this case I would consider this solution preferred over the SP because:
it is a lot simpler
it is more maintainable as it would keep your query logic in single place, query could be ran on any DB, without requiring any special permissions or preparation on server
It is better to keep application logic out of data layer. I.e. having the logic in app makes it possible to concurrently use multiple versions of this query.
haven't tested, but I'm pretty sure it is cheaper by RU.

How to check JSON request body for REST Api, for object attributes and structure?

I am writing my first api (express/node) and one of the endpoints receives json data in the body like:
{
"text": "some comment here...",
"tags": [
{"id": 0, "tag": "some tag 1"},
{"id": 123, "tag": "some tag 2"}
],
"date": "1452305028289",
}
Is there some way you can check that all the properties exist on the object and that they have values? Or do you have to write a custom function checking for each required property and values?
You can use one of these packages for validating data with NodeJS:
https://github.com/hapijs/joi
https://github.com/mafintosh/is-my-json-valid
https://github.com/ctavan/express-validator
A simple solution would be this function that takes an object and a list of strings as the properties of that object:
var checkProperties = function (obj, props) {
return props
.map(function(prop) { return obj.hasOwnProperty(prop); })
.reduce(function (p, q) { return p && q; });
}
use like this
checkProperties({ prop1: someValue, prop2: someOtherValue }, ["prop1", "prop2"]); // true

Restangular - custom search - search within an array

Lets assume I have an mongodb items collection looking like this (MongoLab):
{
"_id": {
"$oid": "531d8dd2e4b0373ae7e8f505"
},
"tags": [
"node",
"json"
],
"anotherField": "datahere"
}
{
"_id": {
"$oid": "531d8dd2e4b0373ae7e8f505"
},
"tags": [
"ajax",
"json"
],
"anotherField": "datahere"
}
I would like to get all items where a node is within the tags array.
I've tried the below, but no success - it is returning all items - no search performed?
Plunker demo : http://plnkr.co/edit/BYj09TOGyCTFKhhBXpIO?p=preview
// $route.current.params.id = "node" - should give me only 1 record with this tag
Restangular.all("items").customGET("", { "tags": $route.current.params.id });
Full example, return same record for both cases:
var all = db.all('items');
// GET ALL
all.getList().then(function(data) {
$scope.all = data;
console.log(data);
});
// SEARCH for record where "tags" has got "node"
all.customGET('', { "tags": "node"}).then(function(data) {
$scope.search = data;
console.log(data);
});
Any suggestion would be much appreciated.
According to Mongolab REST API Documentation you have to pass the query object with the q parameter. In your case it is q={"tags":"node"}.
Using Restangular it will be like this:
Restangular.all("items").customGET('', { q: {"tags": "node"}})