I have an array of objects formatted like this:
{
"_id": "590cbcd9bf2b9b18ab3c3112",
"title": "",
"content": "Create Notes Webapp",
"checked": true,
"listID": "590cbc61bf2b9b18ab3c3110"
},
{
"_id": "590cfe5a86fe0908c560c2b0",
"title": "A Note 01",
"content": "My first note.",
"checked": false,
"listID": "590cbe15bf2b9b18ab3c3114"
}
Here is the code I have to update each item:
onTextChange = (key, note, value) => {
clearTimeout(timeoutID);
switch (key) {
default:
break;
case 'title':
note.title = value;
break;
case 'checked':
note.checked = value;
break;
case 'content':
note.content = value;
break;
}
var notes = this.state.notes;
var id;
for (var i in notes) {
if (notes[i]._id === note._id) {
notes[i] = note;
id = i;
break;
}
}
this.setState({ notes }, () => { timeoutID = setTimeout(() => this.updateNote(this.state.notes[id]), 3000); });
}
This is called like this:
onChange={(e, value) => this.onTextChange('title', note, value)}
Is there a better way than using that switch statement to update the specified item in the object? Also, is there a simpler method of scanning the array for the id than the for loop?
Is there a better way than using that switch statement to update the specified item in the object?
You can use this syntax to update the note object. This also makes sure it does not insert new properties into note.
if (note.hasOwnProperty(key) {
note[key] = value
}
For a cleaner syntax to update notes you could do
var newNotes = this.state.notes.map(n => n._id === note._id ? note : n);
this.setState({notes: newNotes});
This will create a newNotes array which is identical to the current state except it will replace the one where the passed in ._id is equal to the one in the array.
You would also have to adjust the updateNote call because you no longer save the index but you could probably just use the note variable?
instead of switch you can do this but you have to check if it exists in the object.
onTextChange = (key, note, value) => {
clearTimeout(timeoutID);
if(note[key]){
note[key] = value;
}
var notes = this.state.notes;
var id;
}
As for looping it is preferred by most standard like airbnb and google.
if you want a better way you can do several things with that array of objects depending on the situation and if you're using es5, es6, but your options are:
1. Make array of objects into an object with key (property name) be your id (they can be any strings).
2. if es6 is used you can convert into map and it makes it so much lighter and faster to get needed object.
hope that helps.
Just as noveyak said, you can access properties in Javascript by using the bracket syntax, where the key is a string containing the name of the property (as you have it now).
Javascript has several different ways of accessing object properties:
note[key] = value;
// if key is 'title', it will be the same as:
note.title = value;
// also the same as:
note['title'] = value;
For your second question, instead of looping over an array, you can store those objects in another object with the id as the property value (essentially using it as a map). This way you can directly access the note entries by id. For example:
allNotes = {}; // create a new object
someNotes = {
'title': '',
'content': 'Create Notes Webapp',
'checked': true,
'listID': '590cbc61bf2b9b18ab3c3110'
};
allNotes[idOfNote] = someNote; // where idOfNote holds the actual id of the note as a string
You can read more about Javascript property accessors on Mozilla's reference website here.
Javascript also has proper maps you can use instead of an object, which is safer and faster if you're allowed to use ES2015. You can learn about it here (also Mozilla docs).
Related
Sample JSON data:
{
"assignments": [{
"date": "2022-04-01",
"lName": "lastname",
"uId": "12345",
"uCode": "LName1",
"fName": "FName1 ",
"aName": "AsignmentName1",
"aId": "998"
}]
}
I'd like to filter the following data to get a specific element's contents based on searching for an assignment name.
For instance in SQL like terms
Select * FROM assignments WHERE `aName` = 'AssignmentName1'
I'm sure this is simple but having trouble with methods for how to accomplish it.
Thanks
I am new here, but if you have access to modern day JavaScript, I would do something like:
const data = JSON.parse('{"assignments":[{"date":"2022-04-01","lName":"lastname","uId":"12345","uCode":"LName1","fName":"FName1 ","aName":"AsignmentName1","aId":"998"}]}';
const yourMatch = data.assignments.find(c => c.aName === 'AssignmentName1');
Since data.assignments is an array, you can call the find() function on it. This functions takes a 'search'-function/lambda as argument.
This search function basically takes an element and decides, whether it is the one you search for, or not aka it returns a boolean.
In my example the arrow function is c => c.aName === 'AssignmentName1', which is shorter and easier to read than a normal function definition. (You can call c whatever you want, it's just cleaner this way.)
You can exchange find() with filter(), if you accept multiple results and not just the first one.
You first have to parse the JSON string:
const parsedJSON = JSON.parse(jsonString);
The object returned is contains all the data from your JSON string. To access the assignments array you can use dot notation.
const assignments = parsedJSON.assignments;
If you don't need to support old browsers, ES6 has a handy function for finding the value in an object. Use the "find"-function and pass a function that returns true for the item you are looking for:
const selectedAssignment = assignments.find( (assignment)=> {
return assignment.aName=="AssignmentName2";
});
If you don't want to use ES6 you can use a for loop.
var assignments = JSON.parse(jsonString).assignments;
function getAssignmentWithName(name) {
for (var i = 0; i < assignments.length; i++) {
if (assignments[i].aName == name) {
return assignments[i];
}
}
return false;
}
var selectedAssignment = getAssignmentWithName("AssignmentName1");
I am learning functional programming in Javascript and using Ramda. I have this object
var fieldvalues = { name: "hello there", mobile: "1234",
meta: {status: "new"},
comments: [ {user: "john", comment: "hi"},
{user:"ram", comment: "hello"}]
};
to be converted like this:
{
comments.0.comment: "hi",
comments.0.user: "john",
comments.1.comment: "hello",
comments.1.user: "ram",
meta.status: "new",
mobile: "1234",
name: "hello there"
}
I have tried this Ramda source, which works.
var _toDotted = function(acc, obj) {
var key = obj[0], val = obj[1];
if(typeof(val) != "object") { // Matching name, mobile etc
acc[key] = val;
return acc;
}
if(!Array.isArray(val)) { // Matching meta
for(var k in val)
acc[key + "." + k] = val[k];
return acc;
}
// Matching comments
for(var idx in val) {
for(var k2 in val[idx]) {
acc[key + "." + idx + "." + k2] = val[idx][k2];
}
}
return acc;
};
// var toDotted = R.pipe(R.toPairs, R.reduce(_toDotted, {}));
var toDotted = R.pipe(R.toPairs, R.curry( function(obj) {
return R.reduce(_toDotted, {}, obj);
}));
console.log(toDotted(fieldvalues));
However, I am not sure if this is close to Functional programming methods. It just seems to be wrapped around some functional code.
Any ideas or pointers, where I can make this more functional way of writing this code.
The code snippet available here.
UPDATE 1
Updated the code to solve a problem, where the old data was getting tagged along.
Thanks
A functional approach would
use recursion to deal with arbitrarily shaped data
use multiple tiny functions as building blocks
use pattern matching on the data to choose the computation on a case-by-case basis
Whether you pass through a mutable object as an accumulator (for performance) or copy properties around (for purity) doesn't really matter, as long as the end result (on your public API) is immutable. Actually there's a nice third way that you already used: association lists (key-value pairs), which will simplify dealing with the object structure in Ramda.
const primitive = (keys, val) => [R.pair(keys.join("."), val)];
const array = (keys, arr) => R.addIndex(R.chain)((v, i) => dot(R.append(keys, i), v), arr);
const object = (keys, obj) => R.chain(([v, k]) => dot(R.append(keys, k), v), R.toPairs(obj));
const dot = (keys, val) =>
(Object(val) !== val
? primitive
: Array.isArray(val)
? array
: object
)(keys, val);
const toDotted = x => R.fromPairs(dot([], x))
Alternatively to concatenating the keys and passing them as arguments, you can also map R.prepend(key) over the result of each dot call.
Your solution is hard-coded to have inherent knowledge of the data structure (the nested for loops). A better solution would know nothing about the input data and still give you the expected result.
Either way, this is a pretty weird problem, but I was particularly bored so I figured I'd give it a shot. I mostly find this a completely pointless exercise because I cannot picture a scenario where the expected output could ever be better than the input.
This isn't a Rambda solution because there's no reason for it to be. You should understand the solution as a simple recursive procedure. If you can understand it, converting it to a sugary Rambda solution is trivial.
// determine if input is object
const isObject = x=> Object(x) === x
// flatten object
const oflatten = (data) => {
let loop = (namespace, acc, data) => {
if (Array.isArray(data))
data.forEach((v,k)=>
loop(namespace.concat([k]), acc, v))
else if (isObject(data))
Object.keys(data).forEach(k=>
loop(namespace.concat([k]), acc, data[k]))
else
Object.assign(acc, {[namespace.join('.')]: data})
return acc
}
return loop([], {}, data)
}
// example data
var fieldvalues = {
name: "hello there",
mobile: "1234",
meta: {status: "new"},
comments: [
{user: "john", comment: "hi"},
{user: "ram", comment: "hello"}
]
}
// show me the money ...
console.log(oflatten(fieldvalues))
Total function
oflatten is reasonably robust and will work on any input. Even when the input is an array, a primitive value, or undefined. You can be certain you will always get an object as output.
// array input example
console.log(oflatten(['a', 'b', 'c']))
// {
// "0": "a",
// "1": "b",
// "2": "c"
// }
// primitive value example
console.log(oflatten(5))
// {
// "": 5
// }
// undefined example
console.log(oflatten())
// {
// "": undefined
// }
How it works …
It takes an input of any kind, then …
It starts the loop with two state variables: namespace and acc . acc is your return value and is always initialized with an empty object {}. And namespace keeps track of the nesting keys and is always initialized with an empty array, []
notice I don't use a String to namespace the key because a root namespace of '' prepended to any key will always be .somekey. That is not the case when you use a root namespace of [].
Using the same example, [].concat(['somekey']).join('.') will give you the proper key, 'somekey'.
Similarly, ['meta'].concat(['status']).join('.') will give you 'meta.status'. See? Using an array for the key computation will make this a lot easier.
The loop has a third parameter, data, the current value we are processing. The first loop iteration will always be the original input
We do a simple case analysis on data's type. This is necessary because JavaScript doesn't have pattern matching. Just because were using a if/else doesn't mean it's not functional paradigm.
If data is an Array, we want to iterate through the array, and recursively call loop on each of the child values. We pass along the value's key as namespace.concat([k]) which will become the new namespace for the nested call. Notice, that nothing gets assigned to acc at this point. We only want to assign to acc when we have reached a value and until then, we're just building up the namespace.
If the data is an Object, we iterate through it just like we did with an Array. There's a separate case analysis for this because the looping syntax for objects is slightly different than arrays. Otherwise, it's doing the exact same thing.
If the data is neither an Array or an Object, we've reached a value. At this point we can assign the data value to the acc using the built up namespace as the key. Because we're done building the namespace for this key, all we have to do compute the final key is namespace.join('.') and everything works out.
The resulting object will always have as many pairs as values that were found in the original object.
I get from the server a list of objects
[{name:'test01', age:10},{name:'test02', age:20},{name:'test03', age:30}]
I load them into html controls for the user to edit.
Then there is a button to bulk save the entire list back to the database.
Instead of sending the whole list I only want to send the subset of objects that were changed.
It can be any number of items in the array. I want to do something similar to frameworks like Angular that mark an object property like "pristine" when no change has been done to it. Then use that flag to only post to the server the items that are not "pristine", the ones that were modified.
Here is a function down below that will return an array/object of changed objects when supplied with an old array/object of objects and a new array of objects:
// intended to compare objects of identical shape; ideally static.
//
// any top-level key with a primitive value which exists in `previous` but not
// in `current` returns `undefined` while vice versa yields a diff.
//
// in general, the input type determines the output type. that is if `previous`
// and `current` are objects then an object is returned. if arrays then an array
// is returned, etc.
const getChanges = (previous, current) => {
if (isPrimitive(previous) && isPrimitive(current)) {
if (previous === current) {
return "";
}
return current;
}
if (isObject(previous) && isObject(current)) {
const diff = getChanges(Object.entries(previous), Object.entries(current));
return diff.reduce((merged, [key, value]) => {
return {
...merged,
[key]: value
}
}, {});
}
const changes = [];
if (JSON.stringify(previous) === JSON.stringify(current)) {
return changes;
}
for (let i = 0; i < current.length; i++) {
const item = current[i];
if (JSON.stringify(item) !== JSON.stringify(previous[i])) {
changes.push(item);
}
}
return changes;
};
For Example:
const arr1 = [1, 2, 3, 4]
const arr2 = [4, 4, 2, 4]
console.log(getChanges(arr1, arr2)) // [4,4,2]
const obj1 = {
foo: "bar",
baz: [
1, 2, 3
],
qux: {
hello: "world"
},
bingo: "name-o",
}
const obj2 = {
foo: "barx",
baz: [
1, 2, 3, 4
],
qux: {
hello: null
},
bingo: "name-o",
}
console.log(getChanges(obj1.foo, obj2.foo)) // barx
console.log(getChanges(obj1.bingo, obj2.bingo)) // ""
console.log(getChanges(obj1.baz, obj2.baz)) // [4]
console.log(getChanges(obj1, obj2)) // {foo:'barx',baz:[1,2,3,4],qux:{hello:null}}
const obj3 = [{ name: 'test01', age: 10 }, { name: 'test02', age: 20 }, { name: 'test03', age: 30 }]
const obj4 = [{ name: 'test01', age: 10 }, { name: 'test02', age: 20 }, { name: 'test03', age: 20 }]
console.log(getChanges(obj3, obj4)) // [{name:'test03', age:20}]
Utility functions used:
// not required for this example but aid readability of the main function
const typeOf = o => Object.prototype.toString.call(o);
const isObject = o => o !== null && !Array.isArray(o) && typeOf(o).split(" ")[1].slice(0, -1) === "Object";
const isPrimitive = o => {
switch (typeof o) {
case "object": {
return false;
}
case "function": {
return false;
}
default: {
return true;
}
}
};
You would simply have to export the full list of edited values client side, compare it with the old list, and then send the list of changes off to the server.
Hope this helps!
Here are a few ideas.
Use a framework. You spoke of Angular.
Use Proxies, though Internet Explorer has no support for it.
Instead of using classic properties, maybe use Object.defineProperty's set/get to achieve some kind of change tracking.
Use getter/setting functions to store data instead of properties: getName() and setName() for example. Though this the older way of doing what defineProperty now does.
Whenever you bind your data to your form elements, set a special property that indicates if the property has changed. Something like __hasChanged. Set to true if any property on the object changes.
The old school bruteforce way: keep your original list of data that came from the server, deep copy it into another list, bind your form controls to the new list, then when the user clicks submit, compare the objects in the original list to the objects in the new list, plucking out the changed ones as you go. Probably the easiest, but not necessarily the cleanest.
A different take on #6: Attach a special property to each object that always returns the original version of the object:
var myData = [{name: "Larry", age: 47}];
var dataWithCopyOfSelf = myData.map(function(data) {
Object.assign({}, data, { original: data });
});
// now bind your form to dataWithCopyOfSelf.
Of course, this solution assumes a few things: (1) that your objects are flat and simple since Object.assign() doesn't deep copy, (2) that your original data set will never be changed, and (3) that nothing ever touches the contents of original.
There are a multitude of solutions out there.
With ES6 we can use Proxy
to accomplish this task: intercept an Object write, and mark it as dirty.
Proxy allows to create a handler Object that can trap, manipulate, and than forward changes to the original target Object, basically allowing to reconfigure its behavior.
The trap we're going to adopt to intercept Object writes is the handler set().
At this point we can add a non-enumerable property flag like i.e: _isDirty using Object.defineProperty() to mark our Object as modified, dirty.
When using traps (in our case the handler's set()) no changes are applied nor reflected to the Objects, therefore we need to forward the argument values to the target Object using Reflect.set().
Finally, to retrieve the modified objects, filter() the Array with our proxy Objects in search of those having its own Property "_isDirty".
// From server:
const dataOrg = [
{id:1, name:'a', age:10},
{id:2, name:'b', age:20},
{id:3, name:'c', age:30}
];
// Mirror data from server to observable Proxies:
const data = dataOrg.map(ob => new Proxy(ob, {
set() {
Object.defineProperty(ob, "_isDirty", {value: true}); // Flag
return Reflect.set(...arguments); // Forward trapped args to ob
}
}));
// From now on, use proxied data. Let's change some values:
data[0].name = "Lorem";
data[0].age = 42;
data[2].age = 31;
// Collect modified data
const dataMod = data.filter(ob => ob.hasOwnProperty("_isDirty"));
// Test what we're about to send back to server:
console.log(JSON.stringify(dataMod, null, 2));
Without using .defineProperty()
If for some reason you don't feel comfortable into tapping into the original object adding extra properties as flags, you could instead populate immediately
the dataMod (array with modified Objects) with references:
const dataOrg = [
{id:1, name:'a', age:10},
{id:2, name:'b', age:20},
{id:3, name:'c', age:30}
];
// Prepare array to hold references to the modified Objects
const dataMod = [];
const data = dataOrg.map(ob => new Proxy(ob, {
set() {
if (dataMod.indexOf(ob) < 0) dataMod.push(ob); // Push reference
return Reflect.set(...arguments);
}
}));
data[0].name = "Lorem";
data[0].age = 42;
data[2].age = 31;
console.log(JSON.stringify(dataMod, null, 2));
Can I Use - Proxy (IE)
Proxy - handler.set()
Global Objects - Reflect
Reflect.set()
Object.defineProperty()
Object.hasOwnProperty()
Without having to get fancy with prototype properties you could simply store them in another array whenever your form control element detects a change
Something along the lines of:
var modified = [];
data.forEach(function(item){
var domNode = // whatever you use to match data to form control element
domNode.addEventListener('input',function(){
if(modified.indexOf(item) === -1){
modified.push(item);
}
});
});
Then send the modified array to server when it's time to save
Why not use Ember.js observable properties ? You can use the Ember.observer function to get and set changes in your data.
Ember.Object.extend({
valueObserver: Ember.observer('value', function(sender, key, value, rev) {
// Executes whenever the "value" property changes
// See the addObserver method for more information about the callback arguments
})
});
The Ember.object actually does a lot of heavy lifting for you.
Once you define your object, add an observer like so:
object.addObserver('propertyKey', targetObject, targetAction)
My idea is to sort object keys and convert object to be string to compare:
// use this function to sort keys, and save key=>value in an array
function objectSerilize(obj) {
let keys = Object.keys(obj)
let results = []
keys.sort((a, b) => a > b ? -1 : a < b ? 1 : 0)
keys.forEach(key => {
let value = obj[key]
if (typeof value === 'object') {
value = objectSerilize(value)
}
results.push({
key,
value,
})
})
return results
}
// use this function to compare
function compareObject(a, b) {
let aStr = JSON.stringify(objectSerilize(a))
let bStr = JSON.stringify(objectSerilize(b))
return aStr === bStr
}
This is what I think up.
It would be cleanest, I’d think to have the object emit an event when a property is added or removed or modified.
A simplistic implementation could involve an array with the object keys; whenever a setter or heck the constructor returns this, it first calls a static function returning a promise; resolving: map with changed values in the array: things added, things removed, or neither. So one could get(‘changed’) or so forth; returning an array.
Similarly every setter can emit an event with arguments for initial value and new value.
Assuming classes are used, you could easily have a static method in a parent generic class that can be called through its constructor and so really you could simplify most of this by passing the object either to itself, or to the parent through super(checkMeProperty).
I am a newbie in JavaScript and AngularJS. I was trying out looping a object, get its key-value pair and then use it to build an array of new objects.
var actorMovie = {
"Leonardo DiCaprio" : "The Revenant",
"Christian Bale" : "The Dark Knight Rises",
"Sylvester Stallone" : "Rocky"
};
if(actorMovie){
var actorMovieArray = [];
angular.forEach(actorMovie, function(value, key) {
actorMovieArray.push ({key: {
"Movies": {
"Best Movie": value
}
}});
});
}
console.log(actorMovieArray);
This console log prints out the right values, but the key remains as 'key' and never updated to the actor's name as expected.
What am I doing wrong here? I tried searching for an answer but did not find any solution. Am I missing something?
I would do something like
angular.forEach(actorMovie, function(value, key) {
actorMovieArray[key]= {
"Movies": {
"Best Movie": value
}
};
});
In your code, javascript does not know that you want to evaluate the key variable to assign the property, and considers the key to be the key string.
As #Hugues pointed out, there is no way for JavaScript to know if you mean the key as literal or as variable, so the literal value is used.
Please be aware that the answer does not behave the same way as you wanted to in your question. Using the key as an array identifier has two drawbacks:
the order of the items when iterating over the keys cannot be retained
if there are two items having the same key (here the actor name), you will only get one in the result as you are overwriting some previously added value. (this is not really the case as your input already is an object literal so that duplicate keys are no concern, but could be a problem when switching to some other input, e.g. an array of items)
This could be okay for you as long as order doesn't matter and you know your keys are unique. If you want the structure as defined in your question, please consider the following snippet:
function buildItem(value, key) {
var res = {};
res[key] = {
"Movies": {
"Best Movie": value
}
};
return res;
}
if(actorMovie){
var actorMovieArray = [];
angular.forEach(actorMovie, function(value, key) {
actorMovieArray.push(buildItem(value, key));
});
}
Try out this jsbin: http://jsbin.com/luborejini/edit?js,console
I would use Object.keys and Array.forEach on the resulting array. And I would also embrace Javascript's functional nature. That way you could easily pull out the creator function into factory mapping libraries for your api json data.
if(actorMovie){
var actorMovieArray = [];
Object.keys(actorMovie).forEach(function(actor){
actorMovieArray[actor] = function(){
return {
Movies: {
BestMovie: actorMovie[actor]
}
};
}();
});
}
I would also recommend not using the actor name as the key in the array. I would rather map it to a model structure, it will make your views / controllers cleaner and easier to understand:
if(actorMovie){
var actorMovieArray = [];
Object.keys(actorMovie).forEach(function(actor) {
actorMovieArray.push(function(){
return {
Actor: actor,
Movies: {
BestMovie: actorMovie[actor]
}
};
}());
});
}
This will drive you into more concise view models, and set you up for easy refactoring once your structure is in place. It will also make testing easier, at least in my opinion.
Is there a method or a chain of methods to check if an array of keys exists in an object available in lodash, rather than using the following?
var params = {...}
var isCompleteForm = true;
var requiredKeys = ['firstname', 'lastname', 'email']
for (var i in requiredKeys) {
if (_.has(params, requiredKeys[i]) == false) {
isCompleteForm = false;
break;
}
}
if (isCompleteForm) {
// do something fun
}
UPDATE
Thanks everyone for the awesome solutions! If you're interested, here's the jsPerf of the different solutions.
http://jsperf.com/check-array-of-keys-for-object
I know the question is about lodash, but this can be done with vanilla JS, and it is much faster:
requiredKeys.every(function(k) { return k in params; })
and even cleaner in ES2015:
requiredKeys.every(k => k in params)
You can totally go functional, with every, has and partialfunctions, like this
var requiredKeys = ['firstname', 'lastname', 'email'],
params = {
"firstname": "thefourtheye",
"lastname": "thefourtheye",
"email": "NONE"
};
console.log(_.every(requiredKeys, _.partial(_.has, params)));
// true
We pass a partial function object to _.every, which is actually _.has partially applied to params object. _.every will iterate requiredKeys array and pass the current value to the partial object, which will apply the current value to the partial _.has function and will return true or false. _.every will return true only if all the elements in the array returns true when passed to the function object. In the example I have shown above, since all the keys are in params, it returns true. Even if a single element is not present in that, it will return false.
_(requiredKeys).difference(_(params).keys().value()).empty()
I believe. The key step is getting everything into arrays then working with sets.
or
_requiredKeys.map(_.pluck(params).bind(_)).compact().empty()
Might work.
Assuming that params can have more properties than is required...
var keys = _.keys(params);
var isCompleteForm = requiredKeys.every(function (key) {
return keys.indexOf(key) != -1;
});
Should do the trick.