Trouble iterating through JSON data [duplicate] - javascript

Is there a better way other than looping to find data in JSON? It's for edit and delete.
for(var k in objJsonResp) {
if (objJsonResp[k].txtId == id) {
if (action == 'delete') {
objJsonResp.splice(k,1);
} else {
objJsonResp[k] = newVal;
}
break;
}
}
The data is arranged as list of maps.
Like:
[
{id:value, pId:value, cId:value,...},
{id:value, pId:value, cId:value,...},
...
]

(You're not searching through "JSON", you're searching through an array -- the JSON string has already been deserialized into an object graph, in this case an array.)
Some options:
Use an Object Instead of an Array
If you're in control of the generation of this thing, does it have to be an array? Because if not, there's a much simpler way.
Say this is your original data:
[
{"id": "one", "pId": "foo1", "cId": "bar1"},
{"id": "two", "pId": "foo2", "cId": "bar2"},
{"id": "three", "pId": "foo3", "cId": "bar3"}
]
Could you do the following instead?
{
"one": {"pId": "foo1", "cId": "bar1"},
"two": {"pId": "foo2", "cId": "bar2"},
"three": {"pId": "foo3", "cId": "bar3"}
}
Then finding the relevant entry by ID is trivial:
id = "one"; // Or whatever
var entry = objJsonResp[id];
...as is updating it:
objJsonResp[id] = /* New value */;
...and removing it:
delete objJsonResp[id];
This takes advantage of the fact that in JavaScript, you can index into an object using a property name as a string -- and that string can be a literal, or it can come from a variable as with id above.
Putting in an ID-to-Index Map
(Dumb idea, predates the above. Kept for historical reasons.)
It looks like you need this to be an array, in which case there isn't really a better way than searching through the array unless you want to put a map on it, which you could do if you have control of the generation of the object. E.g., say you have this originally:
[
{"id": "one", "pId": "foo1", "cId": "bar1"},
{"id": "two", "pId": "foo2", "cId": "bar2"},
{"id": "three", "pId": "foo3", "cId": "bar3"}
]
The generating code could provide an id-to-index map:
{
"index": {
"one": 0, "two": 1, "three": 2
},
"data": [
{"id": "one", "pId": "foo1", "cId": "bar1"},
{"id": "two", "pId": "foo2", "cId": "bar2"},
{"id": "three", "pId": "foo3", "cId": "bar3"}
]
}
Then getting an entry for the id in the variable id is trivial:
var index = objJsonResp.index[id];
var obj = objJsonResp.data[index];
This takes advantage of the fact you can index into objects using property names.
Of course, if you do that, you have to update the map when you modify the array, which could become a maintenance problem.
But if you're not in control of the generation of the object, or updating the map of ids-to-indexes is too much code and/ora maintenance issue, then you'll have to do a brute force search.
Brute Force Search (corrected)
Somewhat OT (although you did ask if there was a better way :-) ), but your code for looping through an array is incorrect. Details here, but you can't use for..in to loop through array indexes (or rather, if you do, you have to take special pains to do so); for..in loops through the properties of an object, not the indexes of an array. Your best bet with a non-sparse array (and yours is non-sparse) is a standard old-fashioned loop:
var k;
for (k = 0; k < someArray.length; ++k) { /* ... */ }
or
var k;
for (k = someArray.length - 1; k >= 0; --k) { /* ... */ }
Whichever you prefer (the latter is not always faster in all implementations, which is counter-intuitive to me, but there we are). (With a sparse array, you might use for..in but again taking special pains to avoid pitfalls; more in the article linked above.)
Using for..in on an array seems to work in simple cases because arrays have properties for each of their indexes, and their only other default properties (length and their methods) are marked as non-enumerable. But it breaks as soon as you set (or a framework sets) any other properties on the array object (which is perfectly valid; arrays are just objects with a bit of special handling around the length property).

I had come across this issue for a complex model with several nested objects. A good example of what I was looking at doing would be this: Lets say you have a polaroid of yourself. And that picture is then put into a trunk of a car. The car is inside of a large crate. The crate is in the hold of a large ship with many other crates. I had to search the hold, look in the crates, check the trunk, and then look for an existing picture of me.
I could not find any good solutions online to use, and using .filter() only works on arrays. Most solutions suggested just checking to see if model["yourpicture"] existed. This was very undesirable because, from the example, that would only search the hold of the ship and I needed a way to get them from farther down the rabbit hole.
This is the recursive solution I made. In comments, I confirmed from T.J. Crowder that a recursive version would be required. I thought I would share it in case anyone came across a similar complex situation.
function ContainsKeyValue( obj, key, value ){
if( obj[key] === value ) return true;
for( all in obj )
{
if( obj[all] != null && obj[all][key] === value ){
return true;
}
if( typeof obj[all] == "object" && obj[all]!= null ){
var found = ContainsKeyValue( obj[all], key, value );
if( found == true ) return true;
}
}
return false;
}
This will start from a given object inside of the graph, and recurse down any objects found. I use it like this:
var liveData = [];
for( var items in viewmodel.Crates )
{
if( ContainsKeyValue( viewmodel.Crates[items], "PictureId", 6 ) === true )
{
liveData.push( viewmodel.Crates[items] );
}
}
Which will produce an array of the Crates which contained my picture.

Zapping - you can use this javascript lib; DefiantJS. There is no need to restructure JSON data into objects to ease searching. Instead, you can search the JSON structure with an XPath expression like this:
var data = [
{
"id": "one",
"pId": "foo1",
"cId": "bar1"
},
{
"id": "two",
"pId": "foo2",
"cId": "bar2"
},
{
"id": "three",
"pId": "foo3",
"cId": "bar3"
}
],
res = JSON.search( data, '//*[id="one"]' );
console.log( res[0].cId );
// 'bar1'
DefiantJS extends the global object JSON with a new method; "search" which returns array with the matches (empty array if none were found). You can try it out yourself by pasting your JSON data and testing different XPath queries here:
http://www.defiantjs.com/#xpath_evaluator
XPath is, as you know, a standardised query language.

If the JSON data in your array is sorted in some way, there are a variety of searches you could implement. However, if you're not dealing with a lot of data then you're probably going to be fine with an O(n) operation here (as you have). Anything else would probably be overkill.

Ok. So, I know this is an old post, but perhaps this can help someone else. This is not backwards compatible, but that's almost irrelevant since Internet Explorer is being made redundant.
Easiest way to do exactly what is wanted:
function findInJson(objJsonResp, key, value, aType){
if(aType=="edit"){
return objJsonResp.find(x=> x[key] == value);
}else{//delete
var a =objJsonResp.find(x=> x[key] == value);
objJsonResp.splice(objJsonResp.indexOf(a),1);
}
}
It will return the item you want to edit if you supply 'edit' as the type. Supply anything else, or nothing, and it assumes delete. You can flip the conditionals if you'd prefer.

General Solution
We use object-scan for a lot of data processing. It has some nice properties, especially traversing in delete safe order. Here is how one could implement find, delete and replace for your question.
// const objectScan = require('object-scan');
const tool = (() => {
const scanner = objectScan(['[*]'], {
abort: true,
rtn: 'bool',
filterFn: ({
value, parent, property, context
}) => {
if (value.id === context.id) {
context.fn({ value, parent, property });
return true;
}
return false;
}
});
return {
add: (data, id, obj) => scanner(data, { id, fn: ({ parent, property }) => parent.splice(property + 1, 0, obj) }),
del: (data, id) => scanner(data, { id, fn: ({ parent, property }) => parent.splice(property, 1) }),
mod: (data, id, prop, v = undefined) => scanner(data, {
id,
fn: ({ value }) => {
if (value !== undefined) {
value[prop] = v;
} else {
delete value[prop];
}
}
})
};
})();
// -------------------------------
const data = [ { id: 'one', pId: 'foo1', cId: 'bar1' }, { id: 'three', pId: 'foo3', cId: 'bar3' } ];
const toAdd = { id: 'two', pId: 'foo2', cId: 'bar2' };
const exec = (fn) => {
console.log('---------------');
console.log(fn.toString());
console.log(fn());
console.log(data);
};
exec(() => tool.add(data, 'one', toAdd));
exec(() => tool.mod(data, 'one', 'pId', 'zzz'));
exec(() => tool.mod(data, 'one', 'other', 'test'));
exec(() => tool.mod(data, 'one', 'gone', 'delete me'));
exec(() => tool.mod(data, 'one', 'gone'));
exec(() => tool.del(data, 'three'));
// => ---------------
// => () => tool.add(data, 'one', toAdd)
// => true
// => [ { id: 'one', pId: 'foo1', cId: 'bar1' }, { id: 'two', pId: 'foo2', cId: 'bar2' }, { id: 'three', pId: 'foo3', cId: 'bar3' } ]
// => ---------------
// => () => tool.mod(data, 'one', 'pId', 'zzz')
// => true
// => [ { id: 'one', pId: 'zzz', cId: 'bar1' }, { id: 'two', pId: 'foo2', cId: 'bar2' }, { id: 'three', pId: 'foo3', cId: 'bar3' } ]
// => ---------------
// => () => tool.mod(data, 'one', 'other', 'test')
// => true
// => [ { id: 'one', pId: 'zzz', cId: 'bar1', other: 'test' }, { id: 'two', pId: 'foo2', cId: 'bar2' }, { id: 'three', pId: 'foo3', cId: 'bar3' } ]
// => ---------------
// => () => tool.mod(data, 'one', 'gone', 'delete me')
// => true
// => [ { id: 'one', pId: 'zzz', cId: 'bar1', other: 'test', gone: 'delete me' }, { id: 'two', pId: 'foo2', cId: 'bar2' }, { id: 'three', pId: 'foo3', cId: 'bar3' } ]
// => ---------------
// => () => tool.mod(data, 'one', 'gone')
// => true
// => [ { id: 'one', pId: 'zzz', cId: 'bar1', other: 'test', gone: undefined }, { id: 'two', pId: 'foo2', cId: 'bar2' }, { id: 'three', pId: 'foo3', cId: 'bar3' } ]
// => ---------------
// => () => tool.del(data, 'three')
// => true
// => [ { id: 'one', pId: 'zzz', cId: 'bar1', other: 'test', gone: undefined }, { id: 'two', pId: 'foo2', cId: 'bar2' } ]
.as-console-wrapper {max-height: 100% !important; top: 0}
<script src="https://bundle.run/object-scan#13.7.1"></script>
Disclaimer: I'm the author of object-scan

If you are doing this in more than one place in your application it would make sense to use a client-side JSON database because creating custom search functions is messy and less maintainable than the alternative.
Check out ForerunnerDB which provides you with a very powerful client-side JSON database system and includes a very simple query language to help you do exactly what you are looking for:
// Create a new instance of ForerunnerDB and then ask for a database
var fdb = new ForerunnerDB(),
db = fdb.db('myTestDatabase'),
coll;
// Create our new collection (like a MySQL table) and change the default
// primary key from "_id" to "id"
coll = db.collection('myCollection', {primaryKey: 'id'});
// Insert our records into the collection
coll.insert([
{"name":"my Name","id":12,"type":"car owner"},
{"name":"my Name2","id":13,"type":"car owner2"},
{"name":"my Name4","id":14,"type":"car owner3"},
{"name":"my Name4","id":15,"type":"car owner5"}
]);
// Search the collection for the string "my nam" as a case insensitive
// regular expression - this search will match all records because every
// name field has the text "my Nam" in it
var searchResultArray = coll.find({
name: /my nam/i
});
console.log(searchResultArray);
/* Outputs
[
{"name":"my Name","id":12,"type":"car owner"},
{"name":"my Name2","id":13,"type":"car owner2"},
{"name":"my Name4","id":14,"type":"car owner3"},
{"name":"my Name4","id":15,"type":"car owner5"}
]
*/
Disclaimer: I am the developer of ForerunnerDB.

Related

How to map or assign an entry to an array-item based on some of this item's conditions?

I have array of objects,
if the name is xx then push xitems to that object and
if the name is yy then push yitems to that object
Below is the code tried , and also should not use spread operator
const result = [];
var ss=arrobj.forEach(function(e){
if(e.name === 'xx'){
result.push({id: e.id, name: e.name, country:e.country, others: xitems})
}
if(e.name === 'yy'){
result.push({id: e.id, name: e.name, country:e.country, others: yitems})
}
return result;
});
var arrobj =[
{id:1, name: "xx", country: "IN"},
{id:2, name: "yy", country: "MY"},
]
xitems =[
{title: "Finance", valid: true}
]
yitems =[
{title: "Sales", valid: true}
]
Expected Output
[
{id:1, name: "xx", country: "IN",
others:[
{title: "Finance", valid: true}
]
},
{id:2, name: "yy", country: "MY",
others: [
{title: "Sales", valid: true}
]
},
]
You should use .map for this.
const arrobj = [
{ id: 1, name: "xx", country: "IN" },
{ id: 2, name: "yy", country: "MY" },
];
const xitems = [{ title: "Finance", valid: true }];
const yitems = [{ title: "Sales", valid: true }];
const result = arrobj.map((item) => {
if (item.name === "xx") {
item.others = xitems;
} else if (item.name === "yy") {
item.others = yitems;
}
return item;
});
console.log(result);
Your code works, the only issue that I identified are.
There is no need to assign var ss with arrobj.forEach. Because Array.forEach donot return a value.
No need of return result; inside Array.forEach.
Also as an improvement you can simply assign the object with key others like Object.assign({}, e, { others: xitems }), rather than returning individual key value.
Working Fiddle
const arrobj = [
{ id: 1, name: "xx", country: "IN" },
{ id: 2, name: "yy", country: "MY" },
]
const xitems = [
{ title: "Finance", valid: true }
]
const yitems = [
{ title: "Sales", valid: true }
]
const result = [];
arrobj.forEach(function (e) {
if (e.name === 'xx') {
result.push(Object.assign({}, e, { others: xitems }))
}
if (e.name === 'yy') {
result.push(Object.assign({}, e, { others: yitems }))
}
});
console.log(result)
Variables are references to an object that has a value, variables do not store values. It is pointless to try to use a variable in that manner unless you have specific parameters. If you insist on a condition then you need to identify xitems and yitems by the objects values and/or properties or by the order they came in. If you have dynamic data how would you know what xitems or yitems really is?
The example below has been made reusable as long as you meet these requirements:
Must have an array of objects as a primary parameter.
Must have at least one array of objects for each object in the primary array. If there's more the rest will be ignored.
The secondary array of objects must be in the order you want then to end up as.
The second parameter is a rest parameter (not a spread operator, although I have no idea why OP does not want to use it). This will allow us to stuff in as many object arrays as we want.
const distOther = (main, ...oAs) => {...
Next we create an array of pairs from all of the secondary arrays
let others = oAs.map(sub => ['others', sub]);
// [['others', [{...}]], [['others', [{...}]], ...]
Then we turn our attention to the primary array. We'll work our way from the inside out. .map() each object as an array of pairs by Object.entries():
main.map((obj, idx) =>
// ...
Object.entries(obj)
// ...
// [{A: 1, B: 2}, {...}] => [[['A', 1], ['B', 2]], [[...], [...]]]
Then .concat() (a spead operator would be more succinct) each array of pairs with that of the secondary array of pairs corresponding to the current index (you'll need to wrap each secondary array in another array, so the return will level off correctly):
// main.map((obj, idx) =>
// ...
// Object.entries(obj)
.concat([others[idx]])));
// [[['A', 1], ['B', 2], ['others', [{...}]], [[...], [...], ['others', [{...}]]]
Finally we'll use Object.fromEntries() to convert each array of pairs into an object.
// main.map((obj, idx) =>
Object.fromEntries(
// Object.entries(obj)
// .concat([others[idx]])));
// [{'A': 1, 'B': 2, 'others': [{...}]},...]
const objArr =[
{id:1, name: "xx", country: "IN"},
{id:2, name: "yy", country: "MY"},
];
const x =[
{title: "Finance", valid: true}
]
const y =[
{title: "Sales", valid: true}
]
const distOther = (main, ...oAs) => {
let others = oAs.map(sub => ['others', sub]);
return main.map((obj, idx) =>
Object.fromEntries(
Object.entries(obj)
.concat([others[idx]])));
};
console.log(distOther(objArr, x, y));
I would choose a map based approach as well but without the if clauses which explicitly check for expected values of the mapped item's name property.
The approach instead utilizes map's 2nd thisArg parameter which gets applied as the mapper functions this context. Such an additional object can be provided as a map/index of custom key value pairs where key equals a mapped item's name.
Thus the mapper implementation features generic code, and due to the this binding it will be provided as function statement which makes it also re-usable and, if properly named, readable / comprehensible / maintainable too.
function assignBoundNamedValueAsOthers(item) {
// the bound key value pairs.
const index = this;
// create new object and assign, according to
// `item.name`, bound named value as `others`.
return Object.assign(
{},
item,
{ others: index[item.name] ?? [] },
);
}
const arrobj = [
{ id: 1, name: "xx", country: "IN" },
{ id: 2, name: "yy", country: "MY" },
];
const xitems = [{ title: "Finance", valid: true }];
const yitems = [{ title: "Sales", valid: true }];
const result = arrobj
.map(assignBoundNamedValueAsOthers, {
// each `key` equals an expected item's `name`.
xx: xitems,
yy: yitems,
});
console.log({
result,
arrobj,
xitems,
yitems,
});
.as-console-wrapper { min-height: 100%!important; top: 0; }
As one can see, the above implementation via Object.assign creates a new object from each mapped arrobj item. Thus the original item-references remains untouched / non mutated. It does not apply for the items of xitems and yitems since both array references are directly assigned each to its newly created others property. The above log does reflect this.
In case the goal was an entirely reference free data structure one needs to slightly change the Object.assign part of assignBoundNamedValueAsOthers ...
function assignBoundNamedValueAsOthers(item) {
// the bound key value pairs.
const index = this;
// create new object and assign, according to
// `item.name`, bound named value as `others`.
return Object.assign(
{},
item, {
others: (index[item.name] ?? [])
// dereference the `others` items as well.
.map(othersItem =>
Object.assign({}, othersItem)
)
},
);
}
const arrobj = [
{ id: 1, name: "xx", country: "IN" },
{ id: 2, name: "yy", country: "MY" },
];
const xitems = [{ title: "Finance", valid: true }];
const yitems = [{ title: "Sales", valid: true }];
const result = arrobj
.map(assignBoundNamedValueAsOthers, {
// each `key` equals an expected item's `name`.
xx: xitems,
yy: yitems,
});
console.log({
result,
arrobj,
xitems,
yitems,
});
.as-console-wrapper { min-height: 100%!important; top: 0; }
In case the OP does not need to care about immutability, the entire process then changes from a map task to a forEach task, where assignBoundNamedValueAsOthers does directly change/mutate each currently processed item of arrobj, thus forEach does not return any data but always the undefined value ...
function assignBoundNamedValueAsOthers(item) {
// the bound key value pairs.
const index = this;
// mutate the original reference of the currently
// processed `item` by directly assigning, according
// to `item.name`, the bound named value as `others`.
Object.assign(
item,
{ others: index[item.name] ?? [] },
);
// no explicit return value due to
// going to be used as a `forEach` task.
}
const arrobj = [
{ id: 1, name: "xx", country: "IN" },
{ id: 2, name: "yy", country: "MY" },
];
const xitems = [{ title: "Finance", valid: true }];
const yitems = [{ title: "Sales", valid: true }];
// mutates each item of `arrobj`.
arrobj.forEach(assignBoundNamedValueAsOthers, {
// each `key` equals an expected item's `name`.
xx: xitems,
yy: yitems,
});
console.log({
arrobj,
xitems,
yitems,
});
.as-console-wrapper { min-height: 100%!important; top: 0; }

Find duplicate names within an array of different files

A bit of a different use case from the ones I was suggested above.
I need to loop through and check each file name within an array of files and push the files that have the same name into a new array so that I can upload them later separately.
This is my code so far, and surely I have a problem with my conditional checking, can somebody see what I am doing wrong?
filesForStorage = [
{id: 12323, name: 'name', ...},
{id: 3123, name: 'abc', ...},
{id: 3213, name: 'name', ...},
...
]
filesForStorage.map((image, index) => {
for (let i = 0; i < filesForStorage.length; i++) {
for (let j = 0; j < filesForStorage.length; j++) {
if (
filesForStorage[i].name.split(".", 1) ===. //.split('.', 1) is to not keep in consideration the file extension
filesForStorage[j].name.split(".", 1)
) {
console.log(
"----FILES HAVE THE SAME NAME " +
filesForStorage[i] +
" " +
filesForStorage[j]
);
}
}
}
Using map without returning anything makes it near on pointless. You could use forEach but that is equally pointless when you're using a double loop within - it means you would be looping once in the foreach (or map in your case) and then twice more within making for eye-wateringly bad performance.
What you're really trying to do is group your items by name and then pick any group with more than 1 element
const filesForStorage = [
{id: 12323, name: 'name'},
{id: 3123, name: 'abc'},
{id: 3213, name: 'name'}
]
const grouped = Object.values(
filesForStorage.reduce( (a,i) => {
a[i.name] = a[i.name] || [];
a[i.name].push(i);
return a;
},{})
);
console.log(grouped.filter(x => x.length>1).flat());
JavaScript has several functions which perform "hidden" iteration.
Object.values will iterate through an object of key-value pairs and collect all values in an array
Array.prototype.reduce will iterate through an array and perform a computation for each element and finally return a single value
Array.prototype.filter will iterate through an array and collect all elements that return true for a specified test
Array.prototype.flat will iterate through an array, concatenating each element to the next, to create a new flattened array
All of these methods are wasteful as you can compute a collection of duplicates using a single pass over the input array. Furthermore, array methods offer O(n) performance at best, compared to O(1) performance of Set or Map, making the choice of arrays for this kind of computation eye-wateringly bad -
function* duplicates (files) {
const seen = new Set()
for (const f of files) {
if (seen.has(f.name))
yield f
else
seen.add(f.name, f)
}
}
const filesForStorage = [
{id: 12323, name: 'foo'},
{id: 3123, name: 'abc'},
{id: 3213, name: 'foo'},
{id: 4432, name: 'bar'},
{id: 5213, name: 'qux'},
{id: 5512, name: 'bar'},
]
for (const d of duplicates(filesForStorage))
console.log("duplicate name found", d)
duplicate name found {
"id": 3213,
"name": "foo"
}
duplicate name found {
"id": 5512,
"name": "bar"
}
A nested loop can be very expensive on performance, especially if your array will have a lot of values. Something like this would be much better.
filesForStorage = [
{ id: 12323, name: 'name' },
{ id: 3123, name: 'abc' },
{ id: 3213, name: 'name' },
{ id: 3123, name: 'abc' },
{ id: 3213, name: 'name' },
{ id: 3123, name: 'random' },
{ id: 3213, name: 'nothing' },
]
function sameName() {
let checkerObj = {};
let newArray = [];
filesForStorage.forEach(file => {
checkerObj[file.name] = (checkerObj[file.name] || 0) + 1;
});
Object.entries(checkerObj).forEach(([key, value]) => {
if (value > 1) {
newArray.push(key);
}
});
console.log(newArray);
}
sameName();

How to convert an object with keys representing a number list ("1.1.1") into a multidimensional array

Let's consider I have the following plain object:
var numberedList = {
"1": "Text of 1",
"1.1": ["array of 1.1"],
"1.2": {
key: "object of 1.2"
},
"1.1.1": 999,
"1.2.1": true,
"1.2.2": function () {
return "function of 1.2.2";
},
"1.3": null,
"2": 2
}
I want to accomplish the following multidimensional array:
To easy explain myself:
[
["1",
[
[
[
"1.1",
[
"1.1.1"
]
]
],
[
[
"1.2",
[
"1.2.1",
"1.2.2"
]
],
],
"1.3"
]
],
"2"
]
Final array:
[
[
"Text of 1",
[
[
[
["array of 1.1"],
[
999
]
]
],
[
[
{
key: "object of 1.2"
},
[
true,
function()
{
return "function of 1.2.2";
}
]
],
],
null
]
],
2
]
Note that the deepest elements are not wrapped into an array.
How can I do a generic recursion function to accomplish this in pure vanilla js?
The main objective is to perform a tree of console.group() calls such as the console.groupEnd()would be also called on the end of each tree branch. It would be a bonus to help me to accomplish this also – given the object on the top execute these console calls.
Here is my solution,
var numberedList = { "1": "Text of 1", "1.1": ["array of 1.1"], "1.2": { key: "object of 1.2" }, "1.1.1": 999, "1.2.1": true, "1.2.2": function () { return "function of 1.2.2"; }, "1.3": null, "2": 2 }
function gen(numberedList) {
let result = Object.keys(numberedList).reduce((res, key) => {
let indexs = key.split(".");
let lastindex = indexs.pop()
indexs.reduce((res, i) => res[i], res)[lastindex] = [key]
return res
}, []);
result.shift(); // Removing first item, Becouse index count from 0;
return result
}
console.log(gen(numberedList))
You need to create a trimer function, to trim this result, for removing brackets ([])
First of all, in the future, please include your attempted code, or at least one relevant version of it, in the question. It's not a problem to refer to JSFiddle or the like for additional context, but the question should be complete enough without it.
Here I use a generic helpers, setPath, which takes a path such as ['foo', 0, 'bar', 'baz'], a new result, say, 'new result', and creates an updated copy of a nested object/array with the value at that path set to the new value:
setPath
(['foo', 1, 'bar', 'baz'])
("new result")
({foo: [{bar: {qux: 3}}, {bar: {qux: 5}}], corge: 8})
//=> {foo: [{bar: {qux: 3}}, {bar: {qux: 5, baz: "new result"}}], corge: 8}
// ^^^^^^^^^^^^^^^^^
(Here integers represent array indices and strings represent object keys.)
Then it's a simple matter of converting your input structure into an array of path-value objects, and reduce-ing that by calling setPath with each of them on a target array.
const setPath = ([p, ...ps]) => (v) => (o) =>
p == undefined ? v : Object .assign (
Array .isArray (o) || Number .isInteger (p) ? [] : {},
{...o, [p]: setPath (ps) (v) ((o || {}) [p])}
)
const nest = (o) =>
Object .entries (o) .map (
([k, v]) => [k .split ('.') .map (Number), v]
) .reduce ((a, [p, v]) => setPath (p) ([[v]]) (a), []) .slice (1)
const numberedList = {"1": "Text of 1", "1.1": ["array of 1.1"], "1.2": {key: "object of 1.2"}, "1.1.1": 999, "1.2.1": true, "1.2.2": function () {return "function of 1.2.2";}, "1.3": null, "2": 2}
console .log (nest (numberedList))
.as-console-wrapper {max-height: 100% !important; top: 0}
This isn't precisely the output you requested. But I don't make much sense out of that output. You might have to tweak a bit to get it exactly how you want.

Determine if array element represents an object or a value

From a JSON object (containing stock data), I want to add certain elements to an array (in Google Sheets script editor):
var quote = JSON.parse(response.getContentText());
// Example of what quote object looks like:
{
"quoteSummary": {
"result": [
{
"Profile": {
"sector": "Technology",
"website": "www.test.com"
},
"Owners": [
{
"name": "Eric",
"age": "28"
},
{
"name": "Susan",
"age": "44"
}
],
"Profit": 100,
"Assets": 7000
}
]
}
}
Here is my current approach to read only some specific values:
var arr = [];
arr.push(quote.quoteSummary.result[0].Profile.sector); // Technology
arr.push(quote.quoteSummary.result[0].Owners[1].name); // Susan
arr.push(quote.quoteSummary.result[0].Profit); // 100
But since there are many specific properties to read, I'd like to use a loop:
var quote = JSON.parse(response.getContentText());
var arr = [];
var el = [
['Profile', 'sector'],
['Owners[1]', 'name'],
['Profit']
];
for (i = 0; i < el.length; i++)
{
if (quote.quoteSummary.result[0][el[i][0]][el[i][1]] !== undefined)
{
arr.push(quote.quoteSummary.result[0][el[i][0]][el[i][1]].value);
}
}
/*
Expected output (if I would loop through arr):
Technology
Susan
100
*/
The point is that different stocks, will have different properties. So el might define some non-existing elements or properties. Assume (in a bit of an other way of defining el -- as I wrote, I'm plexible here.. perhaps the paths are the easiest):
var el = [
'Profile.website',
'Profile.name',
'Assets'
]
/*
Expected output:
www.test.com
<----- "name" doesn't exist!
7000
Notice that in this example, there is no property "name" in Profile,
so I'd like to add an empty element to arr
*/
But this does not work. What is a generic loop that accomplishes what I'm trying to do here? The array defining what I want can also be constructed differently if that helps. But the point is that I don't end up with a script like:
arr.push(quote.quoteSummary.result[0].Profile.something);
arr.push(quote.quoteSummary.result[0].Profile.something);
arr.push(quote.quoteSummary.result[0].Profile.something);
arr.push(quote.quoteSummary.result[0].Profile.something);
arr.push(quote.quoteSummary.result[0].Profile.something);
arr.push(quote.quoteSummary.result[0].Profile.something);
arr.push(quote.quoteSummary.result[0].Profile.something);
arr.push(quote.quoteSummary.result[0].Profile.something);
arr.push(quote.quoteSummary.result[0].Profile.something);
arr.push(quote.quoteSummary.result[0].Profile.something);
arr.push(quote.quoteSummary.result[0].Profile.something);
I recommend you use variable-length chains of property names. Each name in a given chain represents a deeper property. You can "dive" into an object through an arbitrary number of property names with code like this:
let dive = (obj, propertyNames) => {
for (let pn of propertyNames) obj = obj[pn];
return obj;
};
Now you can say:
let dive = (obj, propertyNames) => {
for (let pn of propertyNames) obj = obj[pn];
return obj;
};
let quote = {
quoteSummary: {
result: [
{
Profile: {
sector: 'Technology',
website: 'www.test.com'
},
Owners: [
{
name: 'Eric',
age: '28'
},
{
name: 'Susan',
age: '44'
}
],
Profit: 100,
Assets: 7000
}
]
}
};
// Here are the "variable-length property chains":
let el = [
[ 'Profile', 'sector' ],
[ 'Owners', 1, 'name' ],
[ 'Profit' ]
];
// Here's how to combine `el`, `dive`, and your `quote` data to get a result:
let arr = el.map(propertyNames => dive(quote.quoteSummary.result[0], propertyNames));
console.log(arr);
You could even replace dive with Array.prototype.reduce, if you'd like to stay functional but avoid the function definition:
dive(someObj, propertyNames);
is equivalent to
propertyNames.reduce((obj, propName) => obj[propName], someObj);
Note the above code all assumes that a property exists for each term in the property chain (except the final property name, which may resolve to undefined without causing any errors). If some cases may have the, e.g., Profile key undefined or null you'll need to write some kind of if (propertyDoesntExist) / else statement which describes how to deal with missing properties.
For example you could modify dive to handle non-existent properties:
let dive = (obj, propertyNames, valueIfMissing=null) => {
for (let pn of propertyNames) {
// Intentional use of loose `==` operator!
if (obj == null) return valueIfMissing;
obj = obj[pn];
}
return obj;
};
This means that:
dive({ a: 1, b: 2, c: 3 }, [ 'd' ]) === null;
But we can substitute any default return value:
dive({ a: 1, b: 2, c: 3 }, [ 'd' ], 'ddd') === 'ddd';
Note this works at any depth:
dive({ a: { a: 1 }, b: { b: 2 }, c: { c: 3 }, d: null }, [ 'd', 'd' ]) === null;
dive({ a: { a: 1 }, b: { b: 2 }, c: { c: 3 }, d: null }, [ 'd', 'd' ], 'ddd') === 'ddd';
You should be able to do this:
if(y.value){
arr.push(y.value);
} else {
arr.push(y);
}
However, this will break if y.value happens to be something like 0, false, etc.
If this is the case you can do this:
if(y.hasOwnProperty("value")){
arr.push(y.value);
} else {
arr.push(y);
}
Based on the comment:
if (quote.quoteSummary.result[0][el[i][0]].hasOwnProperty("value")) {
arr.push(quote.quoteSummary.result[0][el[i][0]].value);
} else {
arr.push(quote.quoteSummary.result[0][el[i][0]]);
}

Search and extract from a string (Javascript) [duplicate]

Is there a better way other than looping to find data in JSON? It's for edit and delete.
for(var k in objJsonResp) {
if (objJsonResp[k].txtId == id) {
if (action == 'delete') {
objJsonResp.splice(k,1);
} else {
objJsonResp[k] = newVal;
}
break;
}
}
The data is arranged as list of maps.
Like:
[
{id:value, pId:value, cId:value,...},
{id:value, pId:value, cId:value,...},
...
]
(You're not searching through "JSON", you're searching through an array -- the JSON string has already been deserialized into an object graph, in this case an array.)
Some options:
Use an Object Instead of an Array
If you're in control of the generation of this thing, does it have to be an array? Because if not, there's a much simpler way.
Say this is your original data:
[
{"id": "one", "pId": "foo1", "cId": "bar1"},
{"id": "two", "pId": "foo2", "cId": "bar2"},
{"id": "three", "pId": "foo3", "cId": "bar3"}
]
Could you do the following instead?
{
"one": {"pId": "foo1", "cId": "bar1"},
"two": {"pId": "foo2", "cId": "bar2"},
"three": {"pId": "foo3", "cId": "bar3"}
}
Then finding the relevant entry by ID is trivial:
id = "one"; // Or whatever
var entry = objJsonResp[id];
...as is updating it:
objJsonResp[id] = /* New value */;
...and removing it:
delete objJsonResp[id];
This takes advantage of the fact that in JavaScript, you can index into an object using a property name as a string -- and that string can be a literal, or it can come from a variable as with id above.
Putting in an ID-to-Index Map
(Dumb idea, predates the above. Kept for historical reasons.)
It looks like you need this to be an array, in which case there isn't really a better way than searching through the array unless you want to put a map on it, which you could do if you have control of the generation of the object. E.g., say you have this originally:
[
{"id": "one", "pId": "foo1", "cId": "bar1"},
{"id": "two", "pId": "foo2", "cId": "bar2"},
{"id": "three", "pId": "foo3", "cId": "bar3"}
]
The generating code could provide an id-to-index map:
{
"index": {
"one": 0, "two": 1, "three": 2
},
"data": [
{"id": "one", "pId": "foo1", "cId": "bar1"},
{"id": "two", "pId": "foo2", "cId": "bar2"},
{"id": "three", "pId": "foo3", "cId": "bar3"}
]
}
Then getting an entry for the id in the variable id is trivial:
var index = objJsonResp.index[id];
var obj = objJsonResp.data[index];
This takes advantage of the fact you can index into objects using property names.
Of course, if you do that, you have to update the map when you modify the array, which could become a maintenance problem.
But if you're not in control of the generation of the object, or updating the map of ids-to-indexes is too much code and/ora maintenance issue, then you'll have to do a brute force search.
Brute Force Search (corrected)
Somewhat OT (although you did ask if there was a better way :-) ), but your code for looping through an array is incorrect. Details here, but you can't use for..in to loop through array indexes (or rather, if you do, you have to take special pains to do so); for..in loops through the properties of an object, not the indexes of an array. Your best bet with a non-sparse array (and yours is non-sparse) is a standard old-fashioned loop:
var k;
for (k = 0; k < someArray.length; ++k) { /* ... */ }
or
var k;
for (k = someArray.length - 1; k >= 0; --k) { /* ... */ }
Whichever you prefer (the latter is not always faster in all implementations, which is counter-intuitive to me, but there we are). (With a sparse array, you might use for..in but again taking special pains to avoid pitfalls; more in the article linked above.)
Using for..in on an array seems to work in simple cases because arrays have properties for each of their indexes, and their only other default properties (length and their methods) are marked as non-enumerable. But it breaks as soon as you set (or a framework sets) any other properties on the array object (which is perfectly valid; arrays are just objects with a bit of special handling around the length property).
I had come across this issue for a complex model with several nested objects. A good example of what I was looking at doing would be this: Lets say you have a polaroid of yourself. And that picture is then put into a trunk of a car. The car is inside of a large crate. The crate is in the hold of a large ship with many other crates. I had to search the hold, look in the crates, check the trunk, and then look for an existing picture of me.
I could not find any good solutions online to use, and using .filter() only works on arrays. Most solutions suggested just checking to see if model["yourpicture"] existed. This was very undesirable because, from the example, that would only search the hold of the ship and I needed a way to get them from farther down the rabbit hole.
This is the recursive solution I made. In comments, I confirmed from T.J. Crowder that a recursive version would be required. I thought I would share it in case anyone came across a similar complex situation.
function ContainsKeyValue( obj, key, value ){
if( obj[key] === value ) return true;
for( all in obj )
{
if( obj[all] != null && obj[all][key] === value ){
return true;
}
if( typeof obj[all] == "object" && obj[all]!= null ){
var found = ContainsKeyValue( obj[all], key, value );
if( found == true ) return true;
}
}
return false;
}
This will start from a given object inside of the graph, and recurse down any objects found. I use it like this:
var liveData = [];
for( var items in viewmodel.Crates )
{
if( ContainsKeyValue( viewmodel.Crates[items], "PictureId", 6 ) === true )
{
liveData.push( viewmodel.Crates[items] );
}
}
Which will produce an array of the Crates which contained my picture.
Zapping - you can use this javascript lib; DefiantJS. There is no need to restructure JSON data into objects to ease searching. Instead, you can search the JSON structure with an XPath expression like this:
var data = [
{
"id": "one",
"pId": "foo1",
"cId": "bar1"
},
{
"id": "two",
"pId": "foo2",
"cId": "bar2"
},
{
"id": "three",
"pId": "foo3",
"cId": "bar3"
}
],
res = JSON.search( data, '//*[id="one"]' );
console.log( res[0].cId );
// 'bar1'
DefiantJS extends the global object JSON with a new method; "search" which returns array with the matches (empty array if none were found). You can try it out yourself by pasting your JSON data and testing different XPath queries here:
http://www.defiantjs.com/#xpath_evaluator
XPath is, as you know, a standardised query language.
If the JSON data in your array is sorted in some way, there are a variety of searches you could implement. However, if you're not dealing with a lot of data then you're probably going to be fine with an O(n) operation here (as you have). Anything else would probably be overkill.
Ok. So, I know this is an old post, but perhaps this can help someone else. This is not backwards compatible, but that's almost irrelevant since Internet Explorer is being made redundant.
Easiest way to do exactly what is wanted:
function findInJson(objJsonResp, key, value, aType){
if(aType=="edit"){
return objJsonResp.find(x=> x[key] == value);
}else{//delete
var a =objJsonResp.find(x=> x[key] == value);
objJsonResp.splice(objJsonResp.indexOf(a),1);
}
}
It will return the item you want to edit if you supply 'edit' as the type. Supply anything else, or nothing, and it assumes delete. You can flip the conditionals if you'd prefer.
General Solution
We use object-scan for a lot of data processing. It has some nice properties, especially traversing in delete safe order. Here is how one could implement find, delete and replace for your question.
// const objectScan = require('object-scan');
const tool = (() => {
const scanner = objectScan(['[*]'], {
abort: true,
rtn: 'bool',
filterFn: ({
value, parent, property, context
}) => {
if (value.id === context.id) {
context.fn({ value, parent, property });
return true;
}
return false;
}
});
return {
add: (data, id, obj) => scanner(data, { id, fn: ({ parent, property }) => parent.splice(property + 1, 0, obj) }),
del: (data, id) => scanner(data, { id, fn: ({ parent, property }) => parent.splice(property, 1) }),
mod: (data, id, prop, v = undefined) => scanner(data, {
id,
fn: ({ value }) => {
if (value !== undefined) {
value[prop] = v;
} else {
delete value[prop];
}
}
})
};
})();
// -------------------------------
const data = [ { id: 'one', pId: 'foo1', cId: 'bar1' }, { id: 'three', pId: 'foo3', cId: 'bar3' } ];
const toAdd = { id: 'two', pId: 'foo2', cId: 'bar2' };
const exec = (fn) => {
console.log('---------------');
console.log(fn.toString());
console.log(fn());
console.log(data);
};
exec(() => tool.add(data, 'one', toAdd));
exec(() => tool.mod(data, 'one', 'pId', 'zzz'));
exec(() => tool.mod(data, 'one', 'other', 'test'));
exec(() => tool.mod(data, 'one', 'gone', 'delete me'));
exec(() => tool.mod(data, 'one', 'gone'));
exec(() => tool.del(data, 'three'));
// => ---------------
// => () => tool.add(data, 'one', toAdd)
// => true
// => [ { id: 'one', pId: 'foo1', cId: 'bar1' }, { id: 'two', pId: 'foo2', cId: 'bar2' }, { id: 'three', pId: 'foo3', cId: 'bar3' } ]
// => ---------------
// => () => tool.mod(data, 'one', 'pId', 'zzz')
// => true
// => [ { id: 'one', pId: 'zzz', cId: 'bar1' }, { id: 'two', pId: 'foo2', cId: 'bar2' }, { id: 'three', pId: 'foo3', cId: 'bar3' } ]
// => ---------------
// => () => tool.mod(data, 'one', 'other', 'test')
// => true
// => [ { id: 'one', pId: 'zzz', cId: 'bar1', other: 'test' }, { id: 'two', pId: 'foo2', cId: 'bar2' }, { id: 'three', pId: 'foo3', cId: 'bar3' } ]
// => ---------------
// => () => tool.mod(data, 'one', 'gone', 'delete me')
// => true
// => [ { id: 'one', pId: 'zzz', cId: 'bar1', other: 'test', gone: 'delete me' }, { id: 'two', pId: 'foo2', cId: 'bar2' }, { id: 'three', pId: 'foo3', cId: 'bar3' } ]
// => ---------------
// => () => tool.mod(data, 'one', 'gone')
// => true
// => [ { id: 'one', pId: 'zzz', cId: 'bar1', other: 'test', gone: undefined }, { id: 'two', pId: 'foo2', cId: 'bar2' }, { id: 'three', pId: 'foo3', cId: 'bar3' } ]
// => ---------------
// => () => tool.del(data, 'three')
// => true
// => [ { id: 'one', pId: 'zzz', cId: 'bar1', other: 'test', gone: undefined }, { id: 'two', pId: 'foo2', cId: 'bar2' } ]
.as-console-wrapper {max-height: 100% !important; top: 0}
<script src="https://bundle.run/object-scan#13.7.1"></script>
Disclaimer: I'm the author of object-scan
If you are doing this in more than one place in your application it would make sense to use a client-side JSON database because creating custom search functions is messy and less maintainable than the alternative.
Check out ForerunnerDB which provides you with a very powerful client-side JSON database system and includes a very simple query language to help you do exactly what you are looking for:
// Create a new instance of ForerunnerDB and then ask for a database
var fdb = new ForerunnerDB(),
db = fdb.db('myTestDatabase'),
coll;
// Create our new collection (like a MySQL table) and change the default
// primary key from "_id" to "id"
coll = db.collection('myCollection', {primaryKey: 'id'});
// Insert our records into the collection
coll.insert([
{"name":"my Name","id":12,"type":"car owner"},
{"name":"my Name2","id":13,"type":"car owner2"},
{"name":"my Name4","id":14,"type":"car owner3"},
{"name":"my Name4","id":15,"type":"car owner5"}
]);
// Search the collection for the string "my nam" as a case insensitive
// regular expression - this search will match all records because every
// name field has the text "my Nam" in it
var searchResultArray = coll.find({
name: /my nam/i
});
console.log(searchResultArray);
/* Outputs
[
{"name":"my Name","id":12,"type":"car owner"},
{"name":"my Name2","id":13,"type":"car owner2"},
{"name":"my Name4","id":14,"type":"car owner3"},
{"name":"my Name4","id":15,"type":"car owner5"}
]
*/
Disclaimer: I am the developer of ForerunnerDB.

Categories

Resources