How to update nested JSON in JavaScript? - javascript

I have a request_json which will get updated different levels.
Example:
request_json = {"filter":""};
// I was updating it like(some where in script)..
request_json.filter = {div_id_1:[1,2,3]};
// Second time updating(some another place in script) is not working
request_json.filter = {div_id_2: ['abc','def']}; //not working.
Expected output:
request_json = {"filter":{div_id_1:[1,2,3], div_id_2: ['abc','def']}}
Actual output:
request_json = {"filter":{div_id_2: ['abc','def']}}
Tried:(not working)
request_json.filter.div_id_1 = {div_id_2: ['abc','def'];'def']}; //not working.
How to update this?

JSON
First, a minor note. You will get a lot of pushback around here using "JSON" to refer to arbitrary JS objects, even if people know what you mean. JSON is a string format used to represent data objects passed between systems. It is not the object format itself. Thus I will use "request" rather than "request_json" below.
Why your code doesn't work
request = {filter: ''};
// ... later
request.filter = {div_id_1: [1, 2, 3]};
// ... later still
request.filter = {div_id_2: ['abc', 'def']};
In each step of that, you set the filter value of your object. On the first line, you set it to the empty string. On the second line you set it to the object {div_id_1: [1, 2, 3]}, and on the third line you set it to the object {div_id_2: ['abc', 'def']}. You have never told it to combine those objects into one. If that's what you want it to do, you need to tell it so.
The simplest fix
You can just change the third line to update the filter object with a new property:
request = {filter: ''};
// ... later
request.filter = {div_id_1: [1, 2, 3]};
// ... later still
request.filter.div_id_2 = ['abc', 'def'];
If you know that the lines will always run in this order, and that this is all you need to do with the filter, this will work fine.
Updating multiple times
But if your problem is more general, if, for instance, you want to be able to update the filter a number of times, but you're not sure which one might do so first, then it could help to write a function to handle the testing of whether the filter is already there and is an object.
It might look like this:
const addToFilter = (request, key, val) =>
request .filter = Object .assign (request .filter || {}, {[key]: val})
const request = {filter: ''}
// ... later
addToFilter (request, 'div_id_1', [1, 2, 3])
// ... later still
addToFilter (request, 'div_id_2', ['abc', 'def'])
console .log (request)
Updating multiple properties at once
If in a single call, you might want to add multiple properties to your filter, the code can be even simpler:
const addToFilter = (request, obj) =>
request .filter = Object .assign (request .filter || {}, obj)
const request = {filter: ''}
// ... later
addToFilter (request, {div_id_1: [1, 2, 3]})
// ... later still
addToFilter (request, {div_id_2: ['abc', 'def'], meaningOfLife: 42})
console .log (request)
Initial assignment
In each of these, I started with an empty string for the filter property. If you need that for an external system -- if perhaps the system uses an empty string to say there is no filter but an object holding values otherwise -- then this is tolerable. But it's far from ideal. I would suggest that if you can, you should start without the filter property or with an empty object for filter.
Mutation
This is still not code I would be proud of. It does the job for now. But I prefer to work with immutable data and here we continually mutate the request object and its filter property. There are probably techniques that would let you do something functionally equivalent without mutating your data, but that's probably taking us too far afield, and would take much more knowledge of your code than I currently have. But you might want to keep in mind techniques using immutable data.

Related

Is this O(N) approach the only way of avoiding a while loop when walking this linked list in Javascript?

I have a data structure that is essentially a linked list stored in state. It represents a stream of changes (patches) to a base object. It is linked by key, rather than by object reference, to allow me to trivially serialise and deserialise the state.
It looks like this:
const latest = 'id4' // They're actually UUIDs, so I can't sort on them (text here for clarity)
const changes = {
id4: {patch: {}, previous: 'id3'},
id3: {patch: {}, previous: 'id2'},
id2: {patch: {}, previous: 'id1'},
id1: {patch: {}, previous: undefined},
}
At some times, a user chooses to run an expensive calculation and results get returned into state. We do not have results corresponding to every change but only some. So results might look like:
const results = {
id3: {performance: 83.6},
id1: {performance: 49.6},
}
Given the changes array, I need to get the results closest to the tip of the changes list, in this case results.id3.
I've written a while loop to do this, and it's perfectly robust at present:
let id = latest
let referenceId = undefined
while (!!id) {
if (!!results[id]) {
referenceId = id
id = undefined
} else {
id = changes[id].previous
}
}
The approach is O(N) but that's the pathological case: I expect a long changelist but with fairly frequent results updates, such that you'd only have to walk back a few steps to find a matching result.
While loops can be vulnerable
Following the great work of Gene Krantz (read his book "Failure is not an option" to understand why NASA never use recursion!) I try to avoid using while loops in code bases: They tend to be susceptible to inadvertent mistakes.
For example, all that would be required to make an infinite loop here is to do delete changes.id1.
So, I'd like to avoid that vulnerability and instead fail to retrieve any result, because not returning a performance value can be handled; but the user's app hanging is REALLY bad!
Other approaches I tried
Sorted array O(N)
To avoid the while loop, I thought about sorting the changes object into an array ordered per the linked list, then simply looping through it.
The problem is that I have to traverse the whole changes list first to get the array in a sorted order, because I don't store an ordering key (it would violate the point of a linked list, because you could no longer do O(1) insert).
It's not a heavy operation, to push an id onto an array, but is still O(N).
The question
Is there a way of traversing this linked list without using a while loop, and without an O(N) approach to convert the linked list into a normal array?
Since you only need to append at the end and possibly remove from the end, the required structure is a stack. In JavaScript the best data structure to implement a stack is an array -- using its push and pop features.
So then you could do things like this:
const changes = [];
function addChange(id, patch) {
changes.push({id, patch});
}
function findRecentMatch(changes, constraints) {
for (let i = changes.length - 1; i >= 0; i--) {
const {id} = changes[i];
if (constraints[id]) return id;
}
}
// Demo
addChange("id1", { data: 10 });
addChange("id2", { data: 20 });
addChange("id3", { data: 30 });
addChange("id4", { data: 40 });
const results = {
id3: {performance: 83.6},
id1: {performance: 49.6},
}
const referenceId = findRecentMatch(changes, results);
console.log(referenceId); // id3
Depending on what you want to do with that referenceId you might want findRecentMatch to return the index in changes instead of the change-id itself. This gives you the possibility to still retrieve the id, but also to clip the changes list to end at that "version" (i.e. as if you popped all the entries up to that point, but then in one operation).
While writing out the question, I realised that rather than avoiding a while-loop entirely, I can add an execution count and an escape hatch which should be sufficient for the purpose.
This solution uses Object.keys() which is strictly O(N) so not technically a correct answer to the question - but it is very fast.
If I needed it faster, I could restructure changes as a map instead of a general object and access changes.size as per this answer
let id = latest
let referenceId = undefined
const maxLoops = Object.keys(changes).length
let loop = 0
while (!!id && loop < maxLoops) {
loop++
if (!!results[id]) {
referenceId = id
id = undefined
} else {
id = changes[id].previous
}
}

Prevent pushing to array if duplicate values are present

I'm mapping an array and based on data i'm pushing Option elements into an array as follows
let make_children: any | null | undefined = [];
buyerActivityResult && buyerActivityResult.simulcastMyAccount.data.map((item: { make: {} | null | undefined; }, key: any) => {
make_children.push(
<Option key={key}>{item.make}</Option>
);
});
Following data array has several objects and these objects have an attribute called model.
buyerActivityResult.simulcastMyAccount.data
I want to prevent pusing Options to my array if the attribute model has duplicate data. It only has to push once for all similar model values.
How can i do it?
I tried something like this
buyerActivityResult && buyerActivityResult.simulcastMyAccount.data.map((item: { model: {} | null | undefined; }, key: any) => {
model_children.indexOf(item.model) === -1 && model_children.push(
<Option key={key}>{item.model}</Option>
);
});
But still duplicate values are being pushed into my array.
Its difficult to tell what you are trying to achieve but it looks like a map may not be the right tool for the job.
A map returns the same sized length array as that of the original array that you are calling map on.
If my assumptions are correct, your buyerActivityResult.simulcastMyAccount.data array has duplicate values, and you want to remove these duplicates based on the model property? One way to achieve this would be to use the lodash library for this, using the uniq function:
const uniqueResults = _.uniq(buyerActivityResult.simulcastMyAccount.data, (item) => item.model);
The Array.prototype.map() method is supposed to be used for manipulating the data contained into the array performing the operation. To manipulate data from other variables I recommend to use a for-loop block.
If item.model is an object, the function Array.prototype.indexOf() always returns -1 because it compares the memory address of the objects and does not do a deep comparison of all properties values.
The usual solution to remove duplicate data from an array is converting the Array into a Set then back to an Array. Unfortunately, this works only on primary type values (string, number, boolean, etc...) and not on objects.
Starting here, I will review your source code and do some changes and explain why I would apply those changes. First of all, assuming the make_children array does not receive new attribution later in your code, I would turn it into a constant. Because of the initialization, I think the declaration is overtyped.
const make_children: any[] = [];
Then I think you try to do too much things at the same time. It makes reading of the source code difficult for your colleagues, for you too (maybe not today but what about in few weeks...) and it make testing, debugging and improvements nearly impossible. Let's break it down in at least 2 steps. First one is transforming the data. For example remove duplicate. And the second one create the Option element base on the result of the previous operation.
const data: { make: any }[] = buyerActivityResult?.simulcastMyAccount?.data || [];
let options = data.map((item) => !!item.model); // removing items without model.
// Here the hard part, removing duplicates.
// - if the models inside your items have a property with unique value (like an ID) you can implement a function to do so yourself. Take a look at: https://stackoverflow.com/questions/2218999/remove-duplicates-from-an-array-of-objects-in-javascript
// - or you can use Lodash library like suggested Rezaa91 in its answer
options = _.uniq(data, (item) => item.model);
Now you only have to create the Option elements.
for (var i = 0; i < options.length; i++) {
model_children.push(<Option key={i}>{options[i].model}</Option>);
}
// OR using the Array.prototype.map method (in this case, do not declare `model_children` at the beginning)
const model_children:[] = options.map((opt:any, i:number) => <Option key={i}>{opt.model}</Option>);
Despite the lack of context of the execution of the code you provided I hope my answer will help you to find a solution and encourage you to write clearer source code (for the sake of your colleagues and your future self).
PS: I do not know anything about ReactJs. forgive me my syntax mistakes.

Removing named properties from "decorated" array

Although my question stems from DataTables.net, I imagine it is applicable elsewhere:
I retrieve an array-like object from a DataTables-created table like this:
var data = tableInstance.data(); // tableInstance is already a DataTables table instance
But the data, while array-like, is actually an object decorated with the DataTables API, resulting in an "array" that looks something like this (reduced to a fake "brief" version):
[
0: {thing: "stuff"},
1: {thing: "nextStuff"},
$: function(){},
button: function() {},
length: 2
]
I would like to isolate just the actual array. Does anybody spot an elegant way of doing this? The "obvious" way is to just iterate X times, up to data.length. For example, using an "each" iterator, which inherently does just that:
var newData = [];
data.each(function (el, index) {
newData.push(el);
})
But I can't help wondering if there's a better way. Generating the new array (or editing in-place... no requirement for it to be new) by removing unwanted properties, rather than by pushing wanted items into a new array.
Or is this just too much of a micro-optimization (even with tens of thousands of items) to even bother with?
There is a better way. Use Array.from.
const newData = Array.from(data)

Javascript/NodeJS passing byref

I'm still learning JS. In some other languages, you can pass variables byref and then modify them elsewhere in code.
In an attempt to avoid having lots of duplicate code, I have structured a series of callbacks and parsing like so:
class MarketData {
constructor() {
//Arrays
this.OneMinuteData = [];
this.ThreeMinuteData = [];
this.initializeCandleData();
}
initializeData() {
var client = new Client();
this._initializeData(60, client, this.OneMinuteData);
this._initializeData(180, client, this.ThreeMinuteData);
}
_initializeData(granularity, client, dataStore) {
client.GetRates({ granularity: granularity }, function(err, msg, data) {
var items = data.map(item => ({
///data mapped here
}));
dataStore = dataStore.concat(items);
}
}
So essentially I have this 'private' _initializeData function with the hopes of passing in an array and having it add to the array, but since JS passes byval, I cannot achieve the desired effect (e.g. this.OneMinuteData array is not modified).
Because of this, the only way I currently know how to work around this problem is to essentially have the same function copy-pasted for each individual array, which I find incredibly sloppy. Is there a better way of doing this?
but since JS passes byval, I cannot achieve the desired effect (e.g. this.OneMinuteData array is not modified).
While JavaScript does pass by value, that value when dealing with an object (including any array) is a reference.
See the documentation for concat:
The concat() method is used to merge two or more arrays. This method does not change the existing arrays, but instead returns a new array.
So when you say dataStore = dataStore.concat(items);, you assign a new array to the local dataStore variable and discard the old one.
Outside the function, the original array is unchanged.
The reason the array assigned to OneMinuteData is not modified is because you never modify any array.
Push the values of items into dataStore instead.
dataStore.push.apply(dataStore, items);
NB: GetRates has the signature of an asynchronous function, so make sure you don't try to inspect the modifications to OneMinuteData before they are made.

empty array inside an ko.observable

I have an ko.observable with an object that contains 3 arrays like this:
self.filter({ file: [], site: [], statut: [] })`
When I try to empty them it doesn't work. I tried
array = []
to empty them. Is it a problem with the observable?
You don't need all of your observable object's arrays to be observable to be able to update the UI, although I'd certainly (like the other answerer) advice you to do so.
I would like to explain however why it doesn't work.
Say you have the following code:
var originalObject = {
myArray: [1, 2, 3]
};
var myObservable = ko.observable(originalObject);
// Resetting the array behind knockout's back:
originalObject.myArray = [1, 2, 3, 4];
The last line changes a property of the object that was used to set the observable. There's no way for knockout to know you've updated your object. If you want knockout to reconsider the observable's vale, you have to tell it something's changed:
myObservable.valueHasMutated();
Now, normally, you update an observable by passing a new or updated variable to it like so:
myObservable(newValue);
Strangely, setting the observable with the same object again also works:
myObservable(originalObject);
This is why:
Internally, knockout compares the newValue to the value it currently holds. If the values are the same, it doesn't do anything. If they're different, it sets the new value and performs the necessary UI updates.
Now, if you're working with just a boolean or number, you'll notice knockout has no problems figuring out if the new value is actually different:
var simpleObservable = ko.observable(true);
simpleObservable.subscribe(function(newValue) {
console.log("Observable changed to: " + newValue);
});
simpleObservable(true); // Doesn't log
simpleObservable(false); // Does log
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
For objects however, it behaves differently:
var myObject = { a: 1 };
var simpleObservable = ko.observable(myObject);
simpleObservable.subscribe(function(newValue) {
console.log("Observable changed to: " + JSON.stringify(newValue, null, 2));
});
simpleObservable(myObject); // Does log, although nothing changed
simpleObservable({b: 2 }); // Does log
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
The subscription is triggered, even though we've used the exact same object to reset our observable! If you dig through knockout's source code, you'll see why. It uses this method to check if the new value is different:
var primitiveTypes = { 'undefined':1, 'boolean':1, 'number':1, 'string':1 };
function valuesArePrimitiveAndEqual(a, b) {
var oldValueIsPrimitive = (a === null) || (typeof(a) in primitiveTypes);
return oldValueIsPrimitive ? (a === b) : false;
}
Simply put: if the old value isn't a primitive value, it will just assume things have changed. This means we can update our originalObject, as long as we reset the observable.
originalObject.myArray.length = 0;
myObservable(originalObject);
Or, just as easy:
myObservable(Object.assign(originalObject, { myArray: [] });
A bit of a long answer, but I believe it's nice to know why stuff doesn't work, instead of only circumventing it. Even if simply using observableArrays and letting knockout optimize its work is the better solution!
You mention "emptying" an array. Note that that's different from "assigning a new, empty array to a variable". At any rate, if you want to "empty" an array:
For observableArray check the relevant docs, because they have a removeAll() utility method.
For emptying a plain javascript array, check this duplicate question that has various solutions, one of which is simply array.length = 0.
As a final note, if you're inside the view model, you might need to do self.filter() first to get the object inside the observable. So, for example:
self.filter().file.length = 0; // plain array method
However, since file, site, and statut are plain arrays (and not observableArrays) there will be no automatic updates in your UI. If they were observable arrays, you'd do:
self.filter().file.removeAll(); // assuming `file` has been made observable

Categories

Resources