Related
I have a need to get data out of a NiFi flow file with somewhat complex JSON content. I'm planning to use a NiFi ExecuteScript processor because I don't think it can be done with EvaluateJSONPath. The content looks like this (snippet)
...
"segments": [
{
"INS01": "Y",
"INS03": "001",
"INS02": "18",
"INS05": "A",
"id": "INS",
"INS04": "AI",
"INS08": "FT"
},
{
"REF02": "1041558xxxxx",
"REF01": "0F",
"id": "REF"
},
{
"REF02": "ABD",
"REF01": "1L",
"id": "REF"
},
{
"REF02": "106835xxxxx",
"REF01": "23",
"id": "REF"
}
],
...
I want to extract the REF02 property value from the segments array element that has REF01 === '0F'. The array element does not necessarily have a REF02 property. So in the above case, I should get 1041558xxxxx.
Here's my current script:
var flowFile = session.get()
if (flowFile != null) {
var InputStreamCallback = Java.type('org.apache.nifi.processor.io.InputStreamCallback')
var IOUtils = Java.type('org.apache.commons.io.IOUtils')
var StandardCharsets = Java.type('java.nio.charset.StandardCharsets')
try {
var subscriber = null
session.read(flowFile,
new InputStreamCallback(function (inputStream) {
var data = JSON.parse(IOUtils.toString(inputStream, StandardCharsets.UTF_8))
var segment = data.segments.find(function (s) { return s.hasOwnProperty('REF01') && s.REF01 === '0F' })
subscriber = segment ? segment.REF02 : null
}));
session.putAttribute(flowFile, 'subscriber', subscriber ? subscriber : '')
session.transfer(flowFile, REL_SUCCESS)
} catch (e) {
log.error('script failed', e)
session.transfer(flowFile, REL_FAILURE)
}
}
When I execute the above, I get a java.lang.NoSuchMethodException. Also, are anonymous 'arrow' functions allow?
I've tried using an old-school for loop to no avail.
Thanks for your help.
You can add a JoltTransformJSON processor with specification
[
{
"operation": "shift",
"spec": {
"segments": {
"*": {
"REF01": {
"0F": {// conditional to match "REF01" with "0F"
"#2,REF02": ""// go two levels up the three to reach the level of the attributes REF01 or REF02
}
}
}
}
}
}
]
in order to return the result
"1041558xxxxx"
You can use below JSONPath with EvaluateJSONPath processor:
$.segments[?(#.REF01<="0F")]#.REF02
Note: Returned result is in the array, So you can use SplitJSON after that to get your string.
Groovy script:
import org.apache.commons.io.IOUtils
import java.nio.charset.StandardCharsets
import groovy.json.JsonSlurper
flowFile = session.get()
if(!flowFile) return
def jsonSlurper = new JsonSlurper()
def subscriber = ""
flowFile = session.write(flowFile, {inputStream, outputStream ->
input = IOUtils.toString(inputStream, StandardCharsets.UTF_8)
json = jsonSlurper.parseText(input)
segment = json.segments.find{ segment ->
if (segment.keySet().contains('REF01')) {
if (segment.REF01 == '0F') {
return true
} else {
return false
}
} else {
return false
}
}
if (segment) {
subscriber = segment.REF02
}
outputStream.write(input.getBytes(StandardCharsets.UTF_8))
} as StreamCallback)
session.putAttribute(flowFile, 'subscriber', subscriber)
session.transfer(flowFile, REL_SUCCESS)
input:
{
"test": "best",
"segments": [
{
"INS01": "Y",
"INS03": "001",
"INS02": "18",
"INS05": "A",
"id": "INS",
"INS04": "AI",
"INS08": "FT"
},
{
"REF02": "1041558xxxxx",
"REF01": "0F",
"id": "REF"
},
{
"REF02": "ABD",
"REF01": "1L",
"id": "REF"
},
{
"REF02": "106835xxxxx",
"REF01": "23",
"id": "REF"
}
]
}
output (with attribute subscriber: 1041558xxxxx):
{
"test": "best",
"segments": [
{
"INS01": "Y",
"INS03": "001",
"INS02": "18",
"INS05": "A",
"id": "INS",
"INS04": "AI",
"INS08": "FT"
},
{
"REF02": "1041558xxxxx",
"REF01": "0F",
"id": "REF"
},
{
"REF02": "ABD",
"REF01": "1L",
"id": "REF"
},
{
"REF02": "106835xxxxx",
"REF01": "23",
"id": "REF"
}
]
}
I need to filter and map both, array items and each item's child array items while checking whether the to be filtered items have certain properties which meet certain criteria.
What are good array method based approaches for filtering and mapping nested array items?
Required Properties
"productName", "productCategory", "content", "id"
AND also if status inside productImages is not Linked or Existing
Sample Data
[
{
"productName": null,
"productCategory": null,
"productImages": [
{
"content": "1",
"id": null,
"data": null,
"file": null,
"url": null,
"status": "Existing"
},
{
"content": "",
"id": "234",
"data": "test data",
"file": "test file",
"url": null,
"status": "Existing"
}
]
},
{
"productName": null,
"productCategory": "hello category",
"productImages": [
{
"content": "1",
"id": null,
"data": null,
"file": null,
"url": null,
"status": "Existing"
},
{
"content": "",
"id": "234",
"data": "test data",
"file": "test file",
"url": null,
"status": "Existing"
}
]
},
{
"productName": "test product",
"productCategory": "test category",
"productImages": [
{
"content": "1",
"id": "123",
"data": "test data",
"file": "test file",
"url": null,
"status": "Linked"
},
{
"content": "2",
"id": "234",
"data": "test data",
"file": "test file",
"url": "test",
"status": "Linked"
}
]
},
{
"productName": "new product",
"productCategory": "new category",
"productImages": [
{
"content": "2",
"id": "32332",
"data": "test data",
"file": "test file",
"url": "test",
"status": "new"
}
]
}
]
Expected Output
[
{
"productIndex": 3,
"name": "new product",
"category": "new category",
"filteredImages": [
{
"newcontent": "2",
"id": "32332",
},
]
},
]
Code
const filteredProducts = products.filter((element) => element.productName && element.productCategory);
You can do it with one for-of loop:
const incomplete_items = [];
const new_data = []
for (const [index, item] of data.entries()) {
if(item.productName && item.productCategory && item.productImages.every((image) => image.id && image.content && !['Existing', 'Linked'].includes(image.status))){
new_data.push({
productIndex: index,
name: item.productName,
category: item.productCategory,
filteredImages: item.productImages.map((image) => ({ newcontent: image.content, id: image.id, }))
})
} else {
incomplete_items.push(index);
}
}
Working example
const data = [
{
"productName": null,
"productCategory": null,
"productImages": [
{ "content": "1", "id": null, "data": null, "file": null, "url": null, "status": "Existing" },
{ "content": "","id": "234","data": "test data", "file": "test file", "url": null, "status": "Existing" }
]
},
{
"productName": null,
"productCategory": "hello category",
"productImages": [
{ "content": "1", "id": null,"data": null, "file": null, "url": null, "status": "Existing"},
{ "content": "", "id": "234","data": "test data","file": "test file", "url": null, "status": "Existing" }
]
},
{
"productName": "test product",
"productCategory": "test category",
"productImages": [
{ "content": "1", "id": "123", "data": "test data", "file": "test file", "url": null, "status": "Linked" },
{ "content": "2", "id": "234", "data": "test data", "file": "test file", "url": "test","status": "Linked" }
]
},
{
"productName": "new product",
"productCategory": "new category",
"productImages": [
{ "content": "2","id": "32332", "data": "test data", "file": "test file", "url": "test", "status": "new" }
]
}
]
const incomplete_items = [];
const new_data = []
for (const [index, item] of data.entries()) {
if(item.productName && item.productCategory && item.productImages.every((image) => image.id && image.content && !['Existing', 'Linked'].includes(image.status))){
new_data.push({
productIndex: index,
name: item.productName,
category: item.productCategory,
filteredImages: item.productImages.map((image) => ({ newcontent: image.content, id: image.id, }))
})
} else {
incomplete_items.push(index);
}
}
if(incomplete_items.length) console.log(`Items ${incomplete_items} were missing required properties.`);
console.log(new_data);
You can use reduce for this.
Thank you Peter Seliger for pointing me to the double reduce. In terms of readability, do you think it's preferred to use 2 reducers instead of a filter().map() inside the outer reducer?
const sampleData = [{productName:null,productCategory:null,productImages:[{content:"1",id:null,data:null,file:null,url:null,status:"Existing"},{content:"",id:"234",data:"test data",file:"test file",url:null,status:"Existing"}]},{productName:null,productCategory:"hello category",productImages:[{content:"1",id:null,data:null,file:null,url:null,status:"Existing"},{content:"",id:"234",data:"test data",file:"test file",url:null,status:"Existing"}]},{productName:"test product",productCategory:"test category",productImages:[{content:"1",id:"123",data:"test data",file:"test file",url:null,status:"Linked"},{content:"2",id:"234",data:"test data",file:"test file",url:"test",status:"Linked"}]},{productName:"new product",productCategory:"new category",productImages:[{content:"2",id:"32332",data:"test data",file:"test file",url:"test",status:"new"}]}];
const expectedData = sampleData.reduce((acc, val, i) => {
// check if productName and productCategory are available
if (val.productName && val.productCategory) {
// get images that match the criteria and return object in desired format
const filteredImages = val.productImages.reduce(
(matchingImages, { content, id, status }) => {
if (content && id && status !== "Existing" && status !== "Linked") {
matchingImages.push({ newcontent: content, id });
}
return matchingImages;
},
[]
);
// if images are available, all conditions are met
if (filteredImages.length > 0) {
acc.push({
productIndex: i,
name: val.productName,
category: val.productCategory,
filteredImages,
});
}
}
return acc;
}, []);
console.log(expectedData);
.as-console-wrapper { min-height: 100%!important; top: 0; }
A straightforward approach would be based on just two reducer functions, each being properly named according to its implemented purpose, where the outer reduce task iterates the products and the inner task iterates each product's related images.
Thus one achieves both within each reduce task, the filtering and the property mapping for the expected result items.
const input = [{productName:null,productCategory:null,productImages:[{content:"1",id:null,data:null,file:null,url:null,status:"Existing"},{content:"",id:"234",data:"test data",file:"test file",url:null,status:"Existing"}]},{productName:null,productCategory:"hello category",productImages:[{content:"1",id:null,data:null,file:null,url:null,status:"Existing"},{content:"",id:"234",data:"test data",file:"test file",url:null,status:"Existing"}]},{productName:"test product",productCategory:"test category",productImages:[{content:"1",id:"123",data:"test data",file:"test file",url:null,status:"Linked"},{content:"2",id:"234",data:"test data",file:"test file",url:"test",status:"Linked"}]},{productName:"new product",productCategory:"new category",productImages:[{content:"2",id:"32332",data:"test data",file:"test file",url:"test",status:"new"}]}];
function collectNonMatchingImageItem(result, image) {
const { id = null, content = null, status = null } = image;
if (
// loose truthyness comparison ... 1st stage filter (valid image) ...
String(id ?? '').trim() &&
String(content ?? '').trim() &&
String(status ?? '').trim() &&
// ... 2nd stage filter (non matching valid image item) ...
!(/^linked|existing$/i).test(status)
) {
// ... and mapping at once.
result.push({
id,
newcontent: content,
});
}
return result;
}
function collectValidProductWithNonMatchingImages(result, product, idx) {
const {
productName = null,
productCategory = null,
productImages = [],
} = product;
// loose truthyness comparison ... 1st stage filter (valid product) ...
if (
String(productName ?? '').trim() &&
String(productCategory ?? '').trim()
) {
const nonMatchingItems = productImages
.reduce(collectNonMatchingImageItem, []);
// 2nd stage filter (valid product with non matching valid images) ...
if (nonMatchingItems.length >= 1) {
// ... and mapping at once.
result.push({
productIndex: idx,
name: productName,
category: productCategory,
filteredImages: nonMatchingItems,
});
}
}
return result;
}
const result = input
.reduce(collectValidProductWithNonMatchingImages, []);
console.log({ result });
.as-console-wrapper { min-height: 100%!important; top: 0; }
Edit
"... How do you compare the old and new array. I wanted to output a warning if it isn't complete" – Joseph
The above approach shows its flexibility if it comes to the integration of the new requirement ...
const input = [{productName:null,productCategory:null,productImages:[{content:"1",id:null,data:null,file:null,url:null,status:"Existing"},{content:"",id:"234",data:"test data",file:"test file",url:null,status:"Existing"}]},{productName:null,productCategory:"hello category",productImages:[{content:"1",id:null,data:null,file:null,url:null,status:"Existing"},{content:"",id:"234",data:"test data",file:"test file",url:null,status:"Existing"}]},{productName:"test product",productCategory:"test category",productImages:[{content:"1",id:"123",data:"test data",file:"test file",url:null,status:"Linked"},{content:"2",id:"234",data:"test data",file:"test file",url:"test",status:"Linked"}]},{productName:"new product",productCategory:"new category",productImages:[{content:"2",id:"32332",data:"test data",file:"test file",url:"test",status:"new"}]}];
function collectNonMatchingImageItem(collector, image, idx) {
const { id = null, content = null, status = null } = image;
// loose truthyness comparison ... 1st stage filter (valid image) ...
if (
String(id ?? '').trim() &&
String(content ?? '').trim() &&
String(status ?? '').trim()
) {
// ... 2nd stage filter (non matching valid image item) ...
if (
!(/^linked|existing$/i).test(status)
) {
// ... and mapping at once.
collector.result.push({
id,
newcontent: content,
});
collector.nonMatchingAt.push(idx);
}
} else {
collector.invalidAt.push(idx);
}
return collector;
}
function collectValidProductWithNonMatchingImages(collector, product, idx) {
const {
productName = null,
productCategory = null,
productImages = [],
} = product;
// loose truthyness comparison ... 1st stage filter (valid product) ...
if (
String(productName ?? '').trim() &&
String(productCategory ?? '').trim()
) {
const {
result: nonMatchingItems,
nonMatchingAt,
invalidAt,
} = productImages.reduce(
collectNonMatchingImageItem,
{ result: [], nonMatchingAt: [], invalidAt: [] },
);
// report related variables.
let isPlural, itemPhrase, indexPhrase, imageReport;
// 2nd stage filter (valid product with non matching valid images) ...
if (nonMatchingItems.length >= 1) {
// ... and mapping at once.
collector.result.push({
productIndex: idx,
name: productName,
category: productCategory,
filteredImages: nonMatchingItems,
});
// create and collect report item.
isPlural = (nonMatchingAt.length >= 2);
itemPhrase = `item${ isPlural ? 's' : '' }`;
indexPhrase = `${ isPlural ? 'indices' : 'index' }`;
imageReport = `with non matching valid image ${ itemPhrase } at ${ indexPhrase } ${ nonMatchingAt }`;
collector.report.push(
`Valid product item at index ${ idx } ${ imageReport }.`
);
}
if (invalidAt.length >= 1) {
// create and collect report item.
isPlural = (invalidAt.length >= 2);
itemPhrase = `item${ isPlural ? 's' : '' }`;
indexPhrase = `${ isPlural ? 'indices' : 'index' }`;
imageReport = `with invalid image ${ itemPhrase } at ${ indexPhrase } ${ invalidAt }`;
collector.report.push(
`Valid product item at index ${ idx } ${ imageReport }.`
);
}
} else {
// create and collect report item.
collector.report.push(
`Invalid product item at index ${ idx }.`
);
}
return collector;
}
const {
result,
report,
} = input.reduce(
collectValidProductWithNonMatchingImages,
{ result: [], report: [] },
);
console.log({ result, report });
.as-console-wrapper { min-height: 100%!important; top: 0; }
I have a data set of the following form
let data = [
{
"id": {
"primary": "A1"
},
"msg": 1
}, {
"id": {
"primary": "A1"
},
"msg": 2
}, {
"id": {
"primary": "B2"
},
"msg": 3
}
]
I would like to transform it to
newData = [
{
"id": {
"primary": "A1"
},
"items": [
{ "msg": 1 },
{ "msg": 2 }
]
},
{
"id": {
"primary": "B2"
},
"items": [
{ "msg": 3 }
]
}
]
I think the method is something like the following, but am not sure how to check against undefined values in this case.
let newData = [];
for (let i = 0; i < data.length; i++) {
if (newData[i]['id']['primary'] === data[i]['id']) newData.push(data[i]['id'])
else newData[i]['items'].push(data[i]['msg'])
}
How can I transform the original data set to merge entries with a matching primary id?
One option would be to use .reduce() to create a new array from the existing.
I've added comments to clarify.
let data = [ { "id": { "primary": "A1" }, "msg": 1 }, { "id": { "primary": "A1" }, "msg": 2 }, { "id": { "primary": "B2" }, "msg": 3 } ];
let result = data.reduce((out,item) => {
let {id, ...items} = item; //Separate the "id" and "everything else"
let existing = out.find(({id}) => id.primary == item.id.primary);
existing //have we seen this ID already?
? existing.items.push(items) //yes - add the items to it
: out.push({ id: {...id}, items: [items]}); //no - create it
return out;
}, []);
console.log(result);
A couple notes:
You may notice that I've set the ID using id: {...id}, despite the id already being an object. This is because using the existing id object would create a reference, whereas {...id} creates a shallow copy.
I haven't specified the msg property anywhere. Instead, any properties that aren't id will be added to the items list (example below).
let data = [ { "id": { "primary": "A1" }, "msg": 1, "otherStuff": "Hello World!" }, { "id": { "primary": "A1" }, "msg": 2, "AnotherThing": true }, { "id": { "primary": "B2" }, "msg": 3, "someOtherProperty": false } ];
let result = data.reduce((out,item) => {
let {id, ...items} = item;
let existing = out.find(({id}) => id.primary == item.id.primary);
existing
? existing.items.push(items)
: out.push({ id: {...id}, items: [items]});
return out;
}, []);
console.log(result);
That said, if you start to nest objects (other than ID), they will likely be included as references; ...items is only a shallow copy.
If such a case, consider something like JSON.parse(JSON.stringify(...)) for a deep copy. Be sure to read the link though; there are caveats.
You could also solve this in a concise way via the Array.reduce and ES6 destructuring:
let data = [ { "id": { "primary": "A1" }, "msg": 1 }, { "id": { "primary": "A1" }, "msg": 2 }, { "id": { "primary": "B2" }, "msg": 3 } ]
let result = data.reduce((r, {id, msg}) =>
((r[id.primary] = r[id.primary] || { id, items: [] }).items.push({msg}), r), {})
console.log(Object.values(result))
In more readable format it is:
let data = [ { "id": { "primary": "A1" }, "msg": 1 }, { "id": { "primary": "A1" }, "msg": 2 }, { "id": { "primary": "B2" }, "msg": 3 } ]
let result = data.reduce((r, {id, msg}) => {
r[id.primary] = (r[id.primary] || { id, items: [] })
r[id.primary].items.push({msg})
return r
}, {})
console.log(Object.values(result))
The idea is to group by the id.primary and then once the grouping is done simply get the values via Object.values
Notice that this is one pass solution where you do not have to per each iteration do an Array.find against the current accumulator.
I am trying to create a category tree using the array of json objects below.
I want to set a category as a child of another category if its parent equals the id of the other, and I want the posts also to be a children of that category instead of having a separate field for posts, I'll add a flag field that if it is a category or not isParent.
It looks like its working alright, but as you may see, if a category has both category and post as child, it'll only show the categories. Another problem with that is if the post has a null value on its array, it will still push them as children.
What are the mistakes in my code, or is there a simpler or better solution to this?
var tree = unflatten(getData());
var pre = document.createElement('pre');
console.log(tree);
pre.innerText = JSON.stringify(tree, null, 4);
document.body.appendChild(pre);
function unflatten(array, parent, tree) {
tree = typeof tree !== 'undefined' ? tree : [];
parent = typeof parent !== 'undefined' ? parent : {
id: 0
};
_.map(array, function(arr) {
_.set(arr, 'isParent', true);
});
var children = _.filter(array, function(child) {
return child.parent == parent.id;
});
if (!_.isEmpty(children)) {
if (parent.id == 0) {
tree = children;
} else {
parent['children'] = children;
}
_.each(children, function(child) {
var posts = _.map(child.posts, function(post) {
return _.set(post, 'isParent', false);
});
child['children'] = posts;
delete child.posts;
unflatten(array, child);
});
}
return tree;
}
function getData() {
return [{
"id": "c1",
"parent": "",
"name": "foo",
"posts": [{
"id": "p1"
}]
}, {
"id": "c2",
"parent": "1",
"name": "bar",
"posts": [{
"id": "p2"
}]
}, {
"id": "c3",
"parent": "",
"name": "bazz",
"posts": [
null
]
}, {
"id": "c4",
"parent": "3",
"name": "sna",
"posts": [{
"id": "p3"
}]
}, {
"id": "c5",
"parent": "3",
"name": "ney",
"posts": [{
"id": "p4"
}]
}, {
"id": "c6",
"parent": "5",
"name": "tol",
"posts": [{
"id": "p5"
}, {
"id": "p6"
}]
}, {
"id": "c7",
"parent": "5",
"name": "zap",
"posts": [{
"id": "p7"
}, {
"id": "p8"
}, {
"id": "p9"
}]
}, {
"id": "c8",
"parent": "",
"name": "quz",
"posts": [
null
]
}, {
"id": "c9",
"parent": "8",
"name": "meh",
"posts": [{
"id": "p10"
}, {
"id": "p11"
}]
}, {
"id": "c10",
"parent": "8",
"name": "ror",
"posts": [{
"id": "p12"
}, {
"id": "p13"
}]
}, {
"id": "c11",
"parent": "",
"name": "gig",
"posts": [{
"id": "p14"
}]
}, {
"id": "c12",
"name": "xylo",
"parent": "",
"posts": [{
"id": "p15"
}]
}, {
"id": "c13",
"parent": "",
"name": "grr",
"posts": [{
"id": "p16"
}, {
"id": "p17"
}, {
"id": "p14"
}, {
"id": "p18"
}, {
"id": "p19"
}, {
"id": "p20"
}]
}]
}
<script src="//cdn.jsdelivr.net/lodash/3.10.1/lodash.min.js"></script>
Expected Output
So the expected output will be more like:
[
{
id: 'c1',
isParent: true,
children: [
{
id: 'c2',
isParent: true,
children: []
},
{
id: 'p1'
isParent: false
}
]
}
]
And so on..
Your code is very imperative. Try focusing on the "big picture" of data flow instead of writing code by trial-and-error. It's harder, but you get better results (and, in fact, usually it's faster) :)
My idea is to first group the categories by their parents. This is the first line of my solution and it actually becomes much easier after that.
_.groupBy and _.keyBy help a lot here:
function makeCatTree(data) {
var groupedByParents = _.groupBy(data, 'parent');
var catsById = _.keyBy(data, 'id');
_.each(_.omit(groupedByParents, ''), function(children, parentId) {
catsById['c' + parentId].children = children;
});
_.each(catsById, function(cat) {
// isParent will be true when there are subcategories (this is not really a good name, btw.)
cat.isParent = !_.isEmpty(cat.children);
// _.compact below is just for removing null posts
cat.children = _.compact(_.union(cat.children, cat.posts));
// optionally, you can also delete cat.posts here.
});
return groupedByParents[''];
}
I recommend trying each part in the developer console, then it becomes easy to understand.
I have made a small fidde that I think that is what you want.
http://jsfiddle.net/tx3uwhke/
var tree = buildTree(getData());
var pre = document.getElementById('a');
var jsonString = JSON.stringify(tree, null, 4);
console.log(jsonString);
pre.innerHTML = jsonString;
document.body.appendChild(pre);
function buildTree(data, parent){
var result = [];
parent = typeof parent !== 'undefined' ? parent : {id:""};
children = _.filter(data, function(value){
return value.parent === parent.id;
});
if(!_.isEmpty(children)){
_.each(children, function(child){
if (child != null){
result.push(child);
if(!_.isEmpty(child.posts)){
var posts = _.filter(child.posts, function(post){
return post !== null && typeof post !== 'undefined';
});
if(!_.isEmpty(posts)){
_.forEach(posts, function(post){
post.isParent = false;
});
}
result = _.union(result, posts);
delete child.posts;
}
ownChildren = buildTree(data, child);
if(!_.isEmpty(ownChildren)){
child.isParent = true;
child.children = ownChildren;
}else{
child.isParent = false;
}
}
});
}
return result;
}
EDIT: made a new fiddle to contain the isParent part you can find it here
While this problem looks simple, I can remember to have struggled achieving it in a simple way. I therefore created a generic util to do so
You only have to write maximum 3 custom callbacks methods.
Here is an example:
import { flattenTreeItemDeep, treeItemFromList } from './tree.util';
import { sortBy } from 'lodash';
const listItems: Array<ListItem> = [
// ordered list arrival
{ id: 1, isFolder: true, parent: null },
{ id: 2, isFolder: true, parent: 1 },
{ id: 3, isFolder: false, parent: 2 },
// unordered arrival
{ id: 4, isFolder: false, parent: 5 },
{ id: 5, isFolder: true, parent: 1 },
// empty main level folder
{ id: 6, isFolder: true, parent: null },
// orphan main level file
{ id: 7, isFolder: false, parent: null },
];
const trees = treeItemFromList(
listItems,
(listItem) => listItem.isFolder, // return true if the listItem contains items
(parent, leafChildren) => parent.id === leafChildren.parent, // return true if the leaf children is contained in the parent
(parent, folderChildren) => parent.id === folderChildren.parent // return true if the children is contained in the parent
);
console.log(trees);
/*
[
{
children: [
{
children: [{ data: { id: 3, isFolder: false, parent: 2 }, isLeaf: true }],
data: { id: 2, isFolder: true, parent: 1 },
isLeaf: false,
},
{
children: [{ data: { id: 4, isFolder: false, parent: 5 }, isLeaf: true }],
data: { id: 5, isFolder: true, parent: 1 },
isLeaf: false,
},
],
data: { id: 1, isFolder: true, parent: null },
isLeaf: false,
},
{ children: [], data: { id: 6, isFolder: true, parent: null }, isLeaf: false },
{
data: {
id: 7,
isFolder: false,
parent: null,
},
isLeaf: true,
},
]
*/
I did not check with your example as all cases are different, you however need to implement only 3 methods to let the algorithm build the tree for you:
If the item is a folder or a leaf (in your case just check if the children contain any non falsy item) i.e. listItem.posts.some((value)=>!!value)
if a parent contains the leaf child, (parent, child) => !!parent.posts.filter((val)=>!!val).find(({id})=>child.id === id)
if a parent contains the folder: optional if this is the same logic as for a leaf child.
This is my JSON output:
[
{
"Business": [
{
"id": "5739"
},
{
"userid": ""
},
{
"name": "Ben Electric"
},
{
"description": ""
},
{
"address": ""
},
{
"email": "*****#gmail.com"
},
{
"phone2": "050*****88"
},
{
"phone3": ""
},
{
"mobile": "050****88"
},
{
"opentimes": ""
},
{
"services": ""
},
{
"places": ""
},
{
"logo": null
},
{
"image": null
},
{
"video": ""
},
{
"owner_name": "Ben Brant"
},
{
"owners": "1"
},
{
"userpic": "http://graph.facebook.com/****/picture"
},
{
"circle": "3"
},
{
"fc": "0"
},
{
"rating_friends": ""
},
{
"rating_global": "3.3333"
},
{
"advice": ""
},
{
"subscription": "none"
}
]
},
{
"Business": [
{
"id": "5850"
},
{
"userid": ""
},
{
"name": "Bla Bla"
},
{
"description": ""
},
{
"address": ""
},
{
"email": "*****#gmail.com"
},
{
"phone2": ""
},
{
"phone3": ""
},
{
"mobile": "0*****995"
},
{
"opentimes": ""
},
{
"services": ""
},
{
"places": ""
},
{
"logo": null
},
{
"image": null
},
{
"video": ""
},
{
"owner_name": "Ben VBlooo"
},
{
"owners": "1"
},
{
"userpic": "http://graph.facebook.com/******/picture"
},
{
"circle": "3"
},
{
"fc": "0"
},
{
"rating_friends": ""
},
{
"rating_global": "2.0000"
},
{
"advice": ""
},
{
"subscription": "none"
}
]
},
{
"Info": {
"message": "No user for the business"
}
},
{
"OK": {
"message": "By Circle"
}
}
]
I'm trying to get the objects in javascript in this way but it doesnt work, should i loop through each Business object?? is there a way to access the real data objects directly?
Here's what I'm trying:
$.ajax({
type: 'POST',
url: 'BLABLA',
data: { BLABLA },
dataType: 'json',
success: function( resp ) {
if(resp.length == 0) {
$('.searchol').append('<li>No results found.</li>');
return;
}
$.each(resp, function(index, element) {
$('.searchol').append('Users Picture: '+element.Business.userpic);
But I cant seem to get to the object?
I just tried this code using your sample json like that
$.each(resp, function(index,element){
$.each(element, function(ind,ele){
if(ele.length){
$.each(ele,function(ind,ele){
if(ele.userpic)
console.log(ele.userpic)
})
}
})
})
"Business" is referring to an array (square bracket), so element.Business.userpic does not exist (element.Business[0].userpic exists though). Depending on what you want to achieve, you'll either have to loop through Business or access userpic of a particular array item.
Your business object is a array of object
"Business": [
{
"id": "5850"
},
Check this JSFiddle script on how to read that
Sample output
Picture: undefined (index):192
Picture: http://graph.facebook.com/****/picture
This will help you out
$.each(resp, function(index, element) {
$('.searchol').append('Users Picture: '+element.Business["userpic"]);
Your JSON is weird. Instead of :
Business : [
{ id : 'id1' }
{ name : 'name1' }
]
Business[0].id // access id
Business[1].name // access name
Where you have to remember where each attribute is in the array (or loop over the array to find it), you should have:
Business : {
id : 'id1',
name : 'name1'
}
Business.id // access id
Business.name // access name
If you can't change the JSON, you can use the following 2 methods to quickly get a property of Business:
var propMap = {
id : 0,
userid : 1,
name : 2 // etc
}
function getBusinessProp(business, prop) {
return business[propMap[prop]][prop];
}
// usage :
$('.searchol').append('Users Picture: '+ getBusinessProp(element.Business, 'userpic'));
If your array can be missing some items or the items can be in a different order for each business, then you need to iterate to find the property you're interested in:
function getBusinessProp(business, prop) {
for (var i=0; i<business.length; i++) {
if (business[i].hasOwnProperty(prop)) {
return business[i][prop];
}
}
}
// same usage, no need for the map anymore
The second method is probably better because it won't break if you change the order of the array or add new items in the array, etc and the performance boost given by using the map is probably not enough to justify the added maintenance cost.