I’m creating a function to search information in a json structure, my function works well in the first level but I have problems to search in the second level. My funtion return all the object json as if the search had never been performed.
this is my code:
json:
let machine = [
{
"sku": "qweert-12",
"type": "aaaa",
"components":[
{
"unit": "mmmm",
"sku": "qwer-12"
},
{
"unit": "llll",
"sku": "qwer-14"
}
]
},
{
"sku": "qmqert-12",
"type": "bbbb",
"components":[
{
"unit": "ssss",
"sku": "qwlr-12"
},
{
"unit": "jjjj",
"sku": "qwuer-14"
}
]
},
]
function
const search= (inputUser, data, setReturninfo) => {
const input = inputUser.target.value();
const result = data.filter((data) => {
let component= data.components;
return Object.keys(component).some((key) => {
return JSON.stringify((data[key])).toLocaleLowerCase().trim.includes(input);
})
});
setReturninfo(result);
}
I appreciate any help.
var searchResults = []; //this array will be filled with results that match the searched key string
function search(jsonObject, key) {
for (let k in jsonObject) {
let innerObject = jsonObject[k];
if (k == key) {
searchResults.push(innerObject);
}
//recursively search for arrays and objects deep inside:
if (innerObject instanceof Array || typeof innerObject == "object") {
search(innerObject, key);
}
}
}
search(machine, "sku");
//searchResults will be: [ "qweert-12", "qwer-12", "qwer-14", "qmqert-12", "qwlr-12", "qwuer-14" ]
Using a recursive function to search nested levels. Hope I got the function you where searching for.
Related
I feel like this is mostly an issue with how I'm looping through the JSON, so am posting that first. This is a series of JSON responses from Promise.allSettled() posted below.
The problem I am having is with the second "status" object between content and anoObject1 as I'm looping through the JSON responses. I've shown some console.logs() below that are successful
Here is the series of JSON responses:
[
{
"status": "fulfilled",
"value": {
"content": {
"object1": {
"kv": "Y",
"kv1": "1000",
"kv2": {
"okv": "A",
"okv1": "1"
},
"kw": "A"
}
},
"retrievalDate": "2022-05-04T23:01:57.710+0000"
}
},
{
"status": "fulfilled",
"value": {
"content": [
{
"anoObject1": {
"ano": "A",
"ano1": {
"ona": "B",
"ona1": 11
},
"measureValue": "1.92",
"measureValue2": "N"
}
},
{
"anoObject2": {
"ano": "B",
"ano1": {
"ona": "Y",
"ona1": 11
},
"measureValue": "1.92",
"measureValue2": "N"
}
}
],
"retrievalDate": "2022-05-04T23:01:57.707+0000"
}
}
]
Here are the async fetch calls:
export async function allCallouts(key, value){
const BASE_URL = 'https://baseurl.com/service/'
const API_KEY = 'apikey'
const endpoint1 = 'https://url1.com/a/';
const endpoint2 = 'https://url1.com/b/';
try{
const results = await Promise.allSettled(
[
fetch(endpoint1).then((response) => response.json()),
fetch(endpoint2).then((response) => response.json()),
]
)
return results
} catch (error){
console.log(error)
}
}
Here is the function I am calling the first function from
async handleFetchCallouts() {
returnedResults;
await allCallouts(key, value)
.then(results => {
this.returnedResults = results
}).catch(err => {
console.log('this is err: ' + err);
})
let arrayLength = this.returnedResults.length
for (var i = 0; i < arrayLength; i++) {
//I am able to console.log(this.returnedResults[i].value.content)
//it returns the response and number I am expecting
//but the structure of the JSON response (above) is tripping me up
if (this.returnedResults[i].value.content['object1'] != null) {
//I can console.log() this successfully
console.log(this.returnedResults[i].value.content['object1'].kv)
}
if (this.returnedResults[i].value.content['anoObject1'] != null) {
//having trouble getting to this object and looping through each
}
}
}
Thank you for any help! If you see other design flaws with my code or an easier way to do things, please suggest.
Create a recursive function and dont use any hardcoded key. Iterate through the content and check if value is an array using Array.isArray. If so then handle it in a different function and so for if value is of type object
const arrayLength = [{
"status": "fulfilled",
"value": {
"content": {
"object1": {
"kv": "Y",
"kv1": "1000",
"kv2": {
"okv": "A",
"okv1": "1"
},
"kw": "A"
}
},
"retrievalDate": "2022-05-04T23:01:57.710+0000"
}
},
{
"status": "fulfilled",
"value": {
"content": [{
"anoObject1": {
"ano": "A",
"ano1": {
"ona": "B",
"ona1": 11
},
"measureValue": "1.92",
"measureValue2": "N"
}
},
{
"anoObject1": {
"ano": "B",
"ano1": {
"ona": "Y",
"ona1": 11
},
"measureValue": "1.92",
"measureValue2": "N"
}
}
],
"retrievalDate": "2022-05-04T23:01:57.707+0000"
}
}
]
for (let i = 0; i < arrayLength.length; i++) {
const content = arrayLength[i].value.content;
// checking if value is of type array or object
if (Array.isArray(content)) {
handleContentArray(content)
} else if (content && typeof(content) === 'object') {
handleContentObject(content)
}
}
function handleContentArray(contentArray) {
// iterate the array
contentArray.forEach(item => {
// if the content of the array is an object then call the function which handles the object
if (item && typeof item === 'object') {
handleContentObject(item)
}
})
}
function handleContentObject(contentObject) {
// iterate through the key
for (let keys in contentObject) {
// if the value of the key is an object then recursively call the same function
if (contentObject && typeof(contentObject[keys]) === 'object') {
return handleContentObject(contentObject[keys])
} else {
// log the key value pair
console.log(`KEY:- ${keys}, VALUE: - ${contentObject[keys]}`)
}
}
}
You can use Array.isArray() to ascertain if an object is an Array and customize how you handle the object accordingly.
// Same structure as in the question, but removed extraneous
// fields and compacted for the sake of brevity.
const input = `[
{"value":{"content":{"object1":{"kv":"Y"}}}},
{"value":{"content":[
{"anoObject1":{"ano":"A"}},
{"anoObject1":{"ano":"B"}}
]}}]`;
const result = JSON.parse(input);
for (const r of result) {
const content = r.value.content;
if (Array.isArray(content)) {
for (const c of content) {
console.log(`anoObject1.ano = ${c.anoObject1.ano}`);
}
} else {
console.log(`object1.kv = ${content.object1.kv}`);
}
}
For your second if statement in the for loop, you would have to iterate through all items under value.content. Replace the second if statement with this for a plug and play:
if (Array.isArray(this.returnedResults[i].value.content)) for (let i of this.returnedResults[i].value.content) {
}
Inside the new loop, i will be equivalent to
{
"anoObject1": {
"ano": "A",
"ano1": {
"ona": "B",
"ona1": 11
},
"measureValue": "1.92",
"measureValue2": "N"
}
}
The reason for this is that the second if statement was attempting to find a property/key of an array instead of each object in the array of objects.
I would also recommend reading up on the following to make your coding easier/better:
let
for...in/for...of
truthy/falsy
I have a Json data that I want to have in a different format.
My original json data is:
{
"info": {
"file1": {
"book1": {
"lines": {
"102:0": [
"102:0"
],
"105:4": [
"106:4"
],
"106:4": [
"107:1",
"108:1"
]
}
}
}
}
}
And I want to map it as following:
{
"name": "main",
"children": [
{
"name": "file1",
"children": [
{
"name": "book1",
"group": "1",
"lines": [
"102",
"102"
],
[
"105",
"106"
],
[
"106",
"107",
"108"
]
}
],
"group": 1,
}
],
"group": 0
}
But the number of books and number of files will be more. Here in the lines the 1st part (before the :) inside the "" is taken ("106:4" becomes "106"). The number from the key goes 1st and then the number(s) from the value goes and make a list (["106", "107", "108"]). The group information is new and it depends on parent-child information. 1st parent is group 0 and so on. The first name ("main") is also user defined.
I tried the following code so far:
function build(data) {
return Object.entries(data).reduce((r, [key, value], idx) => {
//const obj = {}
const obj = {
name: 'main',
children: [],
group: 0,
lines: []
}
if (key !== 'reduced control flow') {
obj.name = key;
obj.children = build(value)
if(!(key.includes(":")))
obj.group = idx + 1;
} else {
if (!obj.lines) obj.lines = [];
Object.entries(value).forEach(([k, v]) => {
obj.lines.push([k, ...v].map(e => e.split(':').shift()))
})
}
r.push(obj)
return r;
}, [])
}
const result = build(data);
console.log(result);
The group information is not generating correctly. I am trying to figure out that how to get the correct group information. I would really appreciate if you can help me to figure it out.
You could use reduce method and create recursive function to build the nested structure.
const data = {"info":{"file1":{"book1":{"lines":{"102:0":["102:0"],"105:4":["106:4"],"106:4":["107:1","108:1"]}}}}}
function build(data) {
return Object.entries(data).reduce((r, [key, value]) => {
const obj = {}
if (key !== 'lines') {
obj.name = key;
obj.children = build(value)
} else {
if (!obj.lines) obj.lines = [];
Object.entries(value).forEach(([k, v]) => {
obj.lines.push([k, ...v].map(e => e.split(':').shift()))
})
}
r.push(obj)
return r;
}, [])
}
const result = build(data);
console.log(result);
I couldn't understand the logic behind group property, so you might need to add more info for that, but for the rest, you can try these 2 functions that recursively transform the object into what you are trying to get.
var a = {"info":{"file1":{"book1":{"lines":{"102:0":["102:0"],"105:4":["106:4"],"106:4":["107:1","108:1"]}}}}};
var transform = function (o) {
return Object.keys(o)
.map((k) => {
return {"name": k, "children": (k === "lines" ? parseLines(o[k]) : transform(o[k])) }
}
)
}
var parseLines = function (lines) {
return Object.keys(lines)
.map(v => [v.split(':')[0], ...(lines[v].map(l => l.split(":")[0]))])
}
console.log(JSON.stringify(transform(a)[0], null, 2));
I have an array, something like this:
array =
[
{
"type": "apple",
"color": "red",
"id": "redApple"
},
{
"type": "grape",
"color": "green",
"id": "greenGrape",
"options": [
{
"bunchName": "bunch1",
"size": "8"
},
{
"bunchName": "bunch2",
"size": "10"
},
{
"bunchName": "bunch3",
"size": "5"
}
]
}
]
I have a function that searches for values in the array.
function findValue (index, key) {
return array[index][key];
}
var value = findValue(0, "id");
// returns redApple
Is there a way I could pass a single argument to the function if I wanted to find something deeper in the array? For example, if I wanted to find "bunchName" could I pass it something like 1, "options[0].bunchName" and get back "bunch1"?
I want a function that can handle multiple keys. In my real project sometimes I'm looking for something on the first level, sometimes I'm looking on the second level, sometimes the third level, etc.
jQuery can be used if for some reason that would help.
You could take the string, replace the brackets, split the string and reduce the path for the result. The function uses a default object for missing or not given properties.
function getValue(object, path) {
return path
.replace(/\[/g, '.')
.replace(/\]/g, '')
.split('.')
.reduce(function (o, k) { return (o || {})[k]; }, object);
}
function findValue(index, path) {
return getValue(array[index], path);
}
var array = [{ type: "apple", color: "red", id: "redApple" }, { type: "grape", color: "green", id: "greenGrape", options: [{ bunchName: "bunch1", size: "8" }, { bunchName: "bunch2", size: "10" }, { bunchName: "bunch3", size: "5" }] }];
console.log(findValue(1, "options[0].bunchName"));
From what I understand, output of findValue(object, "bunchName"); should be "bunch3", where object is array in OP's example.
var object =
[
{
"type": "apple",
"color": "red",
"id": "redApple"
},
{
"type": "grape",
"color": "green",
"id": "greenGrape",
"options": [
{
"bunchName": "bunch1",
"size": "8"
},
{
"bunchName": "bunch2",
"size": "10"
},
{
"bunchName": "bunch3",
"size": "5"
}
]
}
]
var findValue = (object, key) => {
var resultValue;
var rec = (currentObj) => {
if(currentObj && typeof currentObj === "object"){
for(let curKey in currentObj){
if (curKey === key){
resultValue = currentObj[curKey];
}else{
rec(currentObj[curKey]);
}
}
}
}
rec(object);
return resultValue;
}
console.log(findValue(object, "bunchName"));
You could add a function that takes an object and a key and returns object[key] and then split your key string into a list of individual keys by the dot. Then you could traverse the list of keys and use the function to get the value for each level in your object:
Totally untested code I just whipped up:
function valueByKey(obj, key) {
if (obj) {
return obj[key];
}
}
function findValue(index, key) {
const keys = key.split('.');
let value = array[index];
for (let i = 0; i < keys.length; i++) {
value = valueByKey(value, keys[i]);
}
return value;
}
Non-recurrent solution:
var array = [
{
'a': {
'b': 1
}
}
];
function findValue(index, key) {
var keys = key.split('.');
var tmp = array[index];
for (var i = 0; i < keys.length; i++) {
if (!tmp.hasOwnProperty(keys[i]) || typeof tmp !== 'object') {
// throw an exception, or return default value – property not found.
}
tmp = tmp[keys[i]];
}
return tmp;
}
findValue(0, 'a.b');
I have the following valid JSON. It describes a tree structure:
{
"items": [
{
"id": "d1"
},
{
"id": "2",
"children": [
{
"id": "3"
},
{
"id": "4"
},
{
"id": "5",
"children": [
{
"id": "6"
},
{
"id": "7",
"children": [
{
"id": "8"
},
{
"id": "9"
}
]
},
{
"id": "10"
}
]
},
{
"id": "11"
},
{
"id": "12"
}
]
},
{
"id": "13"
},
{
"id": "14"
}
]
}
I need to be able to get any of the "items" by id and any of the child items. For example. Initially I tried grep:
var returnedData = $.grep(obj.items, function(element, index){return element.id == "2";
});
This worked great for item with id==2 but fails completely when I try to obtain element.id=="7"
Any assistance would be appreciated. Thanks in advance.
You can make a recursive function to search in the data:
function find(source, id)
{
for (key in source)
{
var item = source[key];
if (item.id == id)
return item;
// Item not returned yet. Search its children by recursive call.
if (item.children)
{
var subresult = find(item.children, id);
// If the item was found in the subchildren, return it.
if (subresult)
return subresult;
}
}
// Nothing found yet? return null.
return null;
}
// In the root object, the array of items is called 'items', so we pass in
// data.items to look into. The root object itself doesn't seem to have an id anyway.
var result = find(data.items, 7);
// Show the name of item 7, if it had one...
alert(result.name);
Demo: http://jsfiddle.net/rj26H/
In this function I just looped over the object, so its a bit more verbose. You could probably also use $.grep to do the searching and make the code a bit smaller. Anyway, the trick is to search all children if the item is not found on the main level. Apparently grep doesn't work in a recursive fashion.
Try this:
var id = 7;
var data = {"items": [{"id": "d1"},{"id": "2","children": [{"id": "3"},{"id": "7"},{"id": "11"},{"id": "12"}]}]};
function search(values) {
$.each(values, function(i, v) {
if (v.id == id) {
console.log('found', v);
return false;
}
if (v.children) {
search(v.children);
}
});
}
search(data.items);
Demo Link
I know this have been already answered, but I wanted to show how you could leverage the new the new JavaScript 1.7 features to solve this. Please note that the same approach could have been used without support for generators, but the code would have been longer.
//Returns an iterator that knows how to walk a tree
function treeIterator(root, childGetter, childCountGetter) {
let stack = [root], node;
while (node = stack.pop()) {
yield node;
for (let i = childCountGetter(node); i--;) stack.push(childGetter(node, i));
}
}
//Our custom search function
function findNodeById(tree, id) {
let it = treeIterator(tree,
function (node, i) { return node.children[i]; },
function (node) { return node.children? node.children.length : 0; }
);
for (let node in it) if (node.id === id) return node;
return null;
}
var tree = {
id: 'root',
children: [
{ id: 'a' },
{
id: 'b',
children: [
{ id: 'b1' },
{ id: 'b2' }
]
},
{ id: 'c' }
]
};
findNodeById(tree, 'b1'); //Object { id="b1"}
Note that you can also set the __iterator__ on the data structure so that functions that needs to iterate over this data structure do not have to know implementation details.
tree.__iterator__ = treeIterator.bind(null, tree,
function (node, i) { return node.children[i]; },
function (node) { return node.children? node.children.length : 0; }
);
Then the findNodeById function can be:
function findNodeById(tree, id) {
for (let node in it) if (node.id === id) return node;
return null;
}
I wish to filter a nested javascript object by the value of the "step" key:
var data = {
"name": "Root",
"step": 1,
"id": "0.0",
"children": [
{
"name": "first level child 1",
"id": "0.1",
"step":2,
"children": [
{
"name": "second level child 1",
"id": "0.1.1",
"step": 3,
"children": [
{
"name": "third level child 1",
"id": "0.1.1.1",
"step": 4,
"children": []},
{
"name": "third level child 2",
"id": "0.1.1.2",
"step": 5,
"children": []}
]},
]}
]
};
var subdata = data.children.filter(function (d) {
return (d.step <= 2)});
This just returns the unmodified nested object, even if I put value of filter to 1.
does .filter work on nested objects or do I need to roll my own function here, advise and correct code appreciated.
cjm
Recursive filter functions are fairly easy to create. This is an example, which strips a JS object of all items defined ["depth","x","x0","y","y0","parent","size"]:
function filter(data) {
for(var i in data){
if(["depth","x","x0","y","y0","parent","size"].indexOf(i) != -1){
delete data[i];
} else if (i === "children") {
for (var j in data.children) {
data.children[j] = filter(data.children[j])
}
}
}
return data;
}
If you would like to filter by something else, just updated the 2nd line with your filter function of choice.
Here's the function to filter nested arrays:
const filter = arr => condition => {
const res = [];
for (const item of arr) {
if (condition(item)) {
if (!item.children) {
res.push({ ...item });
} else {
const children = filter(item.children)(condition);
res.push({ ...item, children })
}
}
}
return res;
}
The only thing you have to do is to wrap your root object into an array to reach self-similarity. In common, your input array should look like this:
data = [
{ <...>, children: [
{ <...>, children: [...] },
...
] },
...
]
where <...> stands for some properties (in your case those are "name", "step" and "id"), and "children" is an optional service property.
Now you can pass your wrapped object into the filter function alongside a condition callback:
filter(data)(item => item.step <= 2)
and you'll get your structure filtered.
Here are a few more functions to deal with such structures I've just coded for fun:
const map = arr => f => {
const res = [];
for (const item of arr) {
if (!item.children) {
res.push({ ...f({ ...item }) });
} else {
res.push({ ...f({ ...item }), children: map(item.children)(f) });
}
}
return res;
}
const reduce = arr => g => init => {
if (!arr) return undefined;
let res = init;
for (const item of arr) {
if (!item.children) {
res = g(res)({ ...item });
} else {
res = g(res)({ ...item });
res = reduce(item.children)(g)(res);
}
}
return res;
}
Usage examples:
map(data)(item => ({ step: item.step }))
reduce(data)($ => item => $ + item.step)(0)
Likely, the code samples aren't ideal but probably could push someone to the right direction.
Yes, filter works on one array (list), like the children of one node. You have got a tree, if you want to search the whole tree you will need to use a tree traversal algorithm or you first put all nodes into an array which you can filter. I'm sure you can write the code yourself.