I have an object like the following :
[
{
"uid": "aaa-aaa",
"name": "foo",
"children": []
},
{
"uid": "aaa-bbb",
"name": "bar",
"children": [
{
"uid": "aaa-bbc",
"name": "baz",
"children": []
},
{
"uid": "aaa-ccc",
"name": "fooz",
"children": [
{
"uid": "aaa-bcb",
"name": "Yeah !",
"children": []
}
]
}
]
}
]
I am trying to write a function that would take that object an uid as parameters and would return a path to the element with the uid in that object (or null if it's not found).
Something like this :
> getElementPath(bigObject, 'aaa-bcb')
[1, "children", 1, "children", 0]
or
> getElementPath(bigObject, 'aaa-bcb')
[1, 1, 0]
I know the function has to be recursive since there should be no limit in nesting levels. I have tried this but it always returns null :
function getElementPath (haystack, uid, currentPath = []) {
if (haystack.uid === uid) {
return currentPath
}
if (Array.isArray(haystack.children)) {
for (let i = 0; i < haystack.children.length; i++) {
let newPath = [...currentPath, i]
let path = getElementPath(haystack.children[i], uid, newPath)
if (path !== null) {
return path
}
}
}
return null
}
I'd use flat
Flatten the object and then loop over the Object keys until you find the one that has the appropriate value. Once you find it, the key is the path.
https://www.npmjs.com/package/flat
My (naive and quick) implementation would look like this. But what I don't love about it is that it knows to look at the "children" property, it's fine if you're data structure is well defined and doesn't change very often, the flat idea will work no matter if you change your data structure or not.
getPathForUid = (uid,obj,thisPath = []) => {
if(Array.isArray(obj)) {
return obj.reduce((acc,item,idx) => getPathForUid(uid,item,thisPath.concat(idx)),[]);
}
return obj.uid === uid ? thisPath : getPathForUid(uid,obj.children,thisPath.concat('children'));
}
Try this:
function getObject(listaJson, uid) {
var object = null,
param,
type = null;
if (listaJson.uid === uid) {
return listaJson;
}
for (param in listaJson) {
type = typeof(listaJson[param]);
if (type.toString().toLowerCase() === 'object') {
object = getObject(listaJson[param], uid);
}
if (object) {
return object;
}
}
return object;
}
console.log(getObject(json, 'aaa-aaa'));
console.log(getObject(json, 'aaa-bbb'));
console.log(getObject(json, 'aaa-bbc'));
console.log(getObject(json, 'aaa-ccc'));
console.log(getObject(json, 'aaa-bcb'));
console.log(getObject(json, 'aaa-xxx')); // null
console.log(getObject(json, 'yyy-jjj')); // null
Related
I have input data like this:
[{
"name": "outField2",
"value": "something"
}, {
"name": "outField3[index].outField4",
"value": "something"
}, {
"name": "outField3[index].outField5",
"value": "something"
}, {
"name": "outField3[index].outField6.outField7",
"value": "something"
}]
I am trying to achieve an output like this based on substring '[index]' (i.e. if that substring is not present then that element should be an object instead of an array):
{
"outField2": "something",
"outField3[index]": [{
"outField4": "something",
"outField5": "something",
"outField6": {
"outField7": "something"
}
}]
}
My current code (below) is able to produce the outField3 as an object if there is no substring '[index]' but I'm unable to find a good solution to generate it as an array in the presence of the substring. Can someone help out? I've tried a few options but none gives me the desired result.
function doThis(item, index) {
let path = map[index].name.split(".");
if (path.length > 1) {
createNestedObject(mapOutput, path, map[index].value);
} else {
mapOutput[map[index].name] = map[index].value;
};
};
function createNestedObject(element, path, value) {
var lastElement = arguments.length === 3 ? path.pop() : false;
for (var i = 0; i < path.length; i++) {
if (path[i].includes('[index]')) {
/*some logic here to push the child elements
that do not contain [index] as an array into
the ones that contain [index]*/
} else {
element = element[path[i]] = element[path[i]] || {};
};
}
if (lastElement) element = element[lastElement] = value;
return element;
};
const map = [{
"name": "outField2",
"value": "something"
}, {
"name": "outField3[index].outField4",
"value": "something"
}, {
"name": "outField3[index].outField5",
"value": "something"
}, {
"name": "outField3[index].outField6.outField7",
"value": "something"
}];
let mapOutput = {};
map.forEach(doThis);
let mapOutputJSON = JSON.stringify(mapOutput, null, 2);
console.log(mapOutputJSON);
.as-console-wrapper { min-height: 100%!important; top: 0; }
you can do something like this
const data = [{
"name": "outField2",
"value": "something"
},
{
"name": "outField3[index].outField4",
"value": "something"
},
{
"name": "outField3[index].outField5",
"value": "something"
},
{
"name": "outField3[index].outField6.outField7",
"value": "something"
}
]
const buildObject = (paths, value, obj) => {
if (paths.length === 0) {
return value
}
const [path, ...rest] = paths
if(path.includes('[index]')) {
return {
...obj,
[path]: [buildObject(rest, value, (obj[path] || [])[0] || {})]
}
}
return {
...obj,
[path]: buildObject(rest, value, obj[path] || {})
}
}
const result = data.reduce((res, {
name,
value
}) => buildObject(name.split('.'), value, res), {})
console.log(result)
A possible generic approach which in my opinion also assigns the correct type of the OP's "outField3[index]" property (object type instead of an Array instance) is based on reduce where ...
the outer loop iterates the array of { name, value } items
by executing a single function accumulateObjectTypeFromPathAndValue where ...
this function does split each name-value into an array of object-path keys which then gets iterated by the inner reduce method where the passed object programmatically accumulates nested key-value pairs.
function accumulateObjectTypeFromPathAndValue(root, path, value) {
path
.split('.')
.reduce((obj, key, idx, arr) => {
if (!obj.hasOwnProperty(key)) {
Object.assign(obj, {
[ key ]: (idx === arr.length - 1)
? value
: {},
});
}
return obj[key];
}, root);
return root;
}
console.log(
[{
"name": "outField2",
"value": "something"
}, {
"name": "outField3[index].outField4",
"value": "something"
}, {
"name": "outField3[index].outField5",
"value": "something"
}, {
"name": "outField3[index].outField6.outField7",
"value": "something"
}].reduce((result, { name: path, value }) => {
return accumulateObjectTypeFromPathAndValue(result, path, value);
}, {})
);
.as-console-wrapper { min-height: 100%!important; top: 0; }
The above implementation of the 2nd reducer function then could be changed according to the OP's custom array-type requirements ...
function accumulateCustomObjectTypeFromPathAndValue(root, path, value) {
path
.split('.')
.reduce((obj, key, idx, arr) => {
if (!obj.hasOwnProperty(key)) {
Object.assign(obj, {
[ key ]: (idx === arr.length - 1)
? value
: {},
});
if (key.endsWith('[index]')) {
obj[ key ] = [obj[ key ]];
}
}
return Array.isArray(obj[ key ])
//? obj[ key ].at(-1) // last item.
? obj[ key ][obj[ key ].length - 1] // last item.
: obj[ key ];
}, root);
return root;
}
console.log(
[{
"name": "outField2",
"value": "something"
}, {
"name": "outField3[index].outField4",
"value": "something"
}, {
"name": "outField3[index].outField5",
"value": "something"
}, {
"name": "outField3[index].outField6.outField7",
"value": "something"
}].reduce((result, { name: path, value }) => {
return accumulateCustomObjectTypeFromPathAndValue(result, path, value);
}, {})
);
.as-console-wrapper { min-height: 100%!important; top: 0; }
I have an object that looks like the following:
const testObject = {
"NameA": {
"Name": {
"_text": "Peter"
}
},
"AgeA": {
"_comment": "line=2",
"age": {
"_text": "21"
}
},
"Birth": {
"_comment": "line=3",
"Birthday": {
"DateTimeSignUTCOffset": {
"Date": {
"_text": "191201"
},
"Time": {
"_text": "1123"
},
},
"Test": {
"Code": {
"_text": "1234"
}
},
}
}
}
I am trying to find any key with the key _text and get the corresponding value and the parent key.
i.e.
const result = {
"Name": "Peter",
"age": "21",
"Date": "191201",
"Time": "1123",
"Code": "1234"
};
I have tried the following by looping through the object but am unable to figure it out.
const result = {};
const find_items = (object) => {
console.log(Object.keys(object));
Object.keys(object).map((item) => {
console.log(object[item]);
if(object[item] !== '_text') {
find_items(object[item])
} else {
console.log(item)
}
});
};
find_items(testObject);
console.log(result);
Can someone could point me in the right direction?
You could take a recursive approach and check for object and if _text property exist take the value with the outer key or get the entries from the recursive call with the object.
At the end build an object from all entries.
const
flatEntries = object => Object
.entries(object)
.flatMap(([k, v]) => {
if (v && typeof v === 'object') return '_text' in v
? [[k, v._text]]
: flatEntries(v);
return [];
});
testObject = { NameA: { Name: { _text: "Peter" } }, AgeA: { _comment: "line=2", age: { _text: "21" } }, Birth: { _comment: "line=3", Birthday: { DateTimeSignUTCOffset: { Date: { _text: "191201" }, Time: { _text: "1123" } }, Test: { Code: { _text: "1234" } } } } },
result = Object.fromEntries(flatEntries(testObject));
console.log(result);
in English, what you want to do is:
create an empty object named "result", and then recursively iterate through the "object" object. each time you encounter a key linked to a sub-object which has a __text field, add that to the "result" object.
now just translate the above into JavaScript.
the keyword here is "recursively". your original code was not recursive.
your idea is pretty good, but you want to find _text keys anywhere nested inside the object. To find inner, nested, keys, you need to recurse your function if the value of some key happens to be an object.
result = {}
find_text_keys = (haystack, label) => {
Object.keys(ob).forEach(key => {
if (key === '_text') {
res[text] = ob["_text"];
} else if (typeof(ob[key]) === "object") {
find_text_keys(ob[key], key);
}
});
}
Then, calling the function with a default label f(object, "default_label") will populate the result dictionary as you desired.
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
First let me break down the data:
I have an array that contains 3 elements...
Each Element is an object with name and arrayOfJSON as keys...
Inside arrayOfJSON there could be any number of JSON strings as elements...
I need to capture the position where Alex#gmail occurs for both the array mess and arrayOfJSON
Result Should Be:
position_of_mess = [0,2]
position_of_arrayOfJSON_for_position_of_mess_0 = [0]
position_of_arrayOfJSON_for_position_of_mess_2 = [1]
What I'm trying at the moment:
For loop through mess, for loop through arrayOfJSON , and JSON.parse() for Alex#gmail.
going to take me a few mins to update.
If y'all think it can be done without a for-loop let me know.
Update: almost there
mess = [{
"name": "user1",
"arrayOfJSON": `[{"email":"Alex#gmail","hobby":"coding"},{"email":"bob#gmail","hobby":"coocking"}]`
},
{
"name": "user2",
"arrayOfJSON": `[{"email":"Chris#gmail","hobby":"coding"},{"email":"bob#gmail","hobby":"coocking"}]`
},
{
"name": "user3",
"arrayOfJSON": `[{"email":"bob#gmail","hobby":"coocking"},{"email":"Alex#gmail","hobby":"coding"}]`
}
]
console.log(mess)
for (i = 0; i < mess.length; i++) {
console.log(JSON.parse(mess[i].arrayOfJSON))
for (m = 0; m < (JSON.parse(mess[i].arrayOfJSON)).length; m++) {
console.log("almost")
console.log((JSON.parse(mess[i].arrayOfJSON))[m])
}
}
mess = [{
"name": "user1",
"arrayOfJSON": `[{"email":"Alex#gmail","hobby":"coding"},{"email":"bob#gmail","hobby":"coocking"}]`
},
{
"name": "user2",
"arrayOfJSON": `[{"email":"Chris#gmail","hobby":"coding"},{"email":"bob#gmail","hobby":"coocking"}]`
},
{
"name": "user3",
"arrayOfJSON": `[{"email":"bob#gmail","hobby":"coocking"},{"email":"Alex#gmail","hobby":"coding"}]`
}
]
console.log(mess)
holdMessPosition = []
for (i = 0; i < mess.length; i++) {
var pos = (JSON.parse(mess[i].arrayOfJSON)).map(function(e) {
return e.email;
})
.indexOf("Alex#gmail");
console.log("user position is " + pos);
if (pos !== -1) {
holdMessPosition.push(i)
}
}
console.log(holdMessPosition)
Parse your data
You want to be able to access keys inside the inner object "string"
Traverse your data
While visiting key-value pairs, build a scope thet you can later return
// Adapted from: https://gist.github.com/sphvn/dcdf9d683458f879f593
const traverse = function(o, fn, scope = []) {
for (let i in o) {
fn.apply(this, [i, o[i], scope]);
if (o[i] !== null && typeof o[i] === "object") {
traverse(o[i], fn, scope.concat(i));
}
}
}
const mess = [{
"name": "user1",
"arrayOfJSON": `[{"email":"Alex#gmail","hobby":"coding"},{"email":"bob#gmail","hobby":"coocking"}]`
}, {
"name": "user2",
"arrayOfJSON": `[{"email":"Chris#gmail","hobby":"coding"},{"email":"bob#gmail","hobby":"coocking"}]`
}, {
"name": "user3",
"arrayOfJSON": `[{"email":"bob#gmail","hobby":"coocking"},{"email":"Alex#gmail","hobby":"coding"}]`
}];
// Parse...
mess.forEach(item => {
if (item.arrayOfJSON) {
item.arrayOfJSON = JSON.parse(item.arrayOfJSON);
}
});
traverse(mess, (key, value, scope) => {
if (value === 'Alex#gmail') {
console.log(
`Position: mess[${scope.concat(key).map(k => isNaN(k) ? `'${k}'` : k).join('][')}]`
);
}
});
.as-console-wrapper {
top: 0;
max-height: 100% !important;
}
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));