Not quite grasping what's going on here. Given the array (arr):
[
{
"first_name": "Dan",
"last_name": "Woodson",
"id": 1
},
{
"first_name": "Jen",
"last_name": "Woodson",
"id": 2
},
{
"first_name": "Yoshi",
"last_name": "Woodson",
"id": 3
}
]
And the object (obj):
{
"first_name": "Yoshi",
"last_name": "Woodson",
"id": 3
}
Why would arr.indexOf(obj) return -1 (especially since I retrieved the object from the array using it's 'id' parameter earlier in the function)?
Array.indexOf() will only work on objects if the supplied object is exactly the same object you put in.
An exact copy is insufficient, it has to be the exact same object, i.e. there must be some object in the array such that:
arr[i] === obj
You need to show how you retrieved the object.
I would like to see the retrieve function, but most likely you are not using the same reference. Because the following is true:
var a = {id: 3};
var b = [a];
b.indexOf(a); // 0
a.id = "not three";
b.indexOf(a); // still 0
However, the following will break:
var a = {id: 3};
var b = [{id: 3}];
b.indexOf(a); // -1 not the same object
Related
I'm trying to create a function that when called will update a specific object in json file. However, it updates the object as well as creating a new one.
I've tried many different methods in trying to get this to work, but all have failed. The closest I've got to it working is the code shown below, but it still doesn't do what is required.
This is my function:
var fs = require('fs');
var _ = require("underscore");
module.exports = {
personalUpdate: function (id, forename, surname, dob, gender, callback) {
let rawdata = fs.readFileSync('data.json');
let data = JSON.parse(rawdata);
let filtered = _.where(data['students'], { id: id });
let all = filtered[0];
all.forename = forename;
all.surname = surname;
all.dob = dob;
all.gender = gender;
data["students"].push(all);
fs.writeFileSync('data.json', JSON.stringify(data, null, 2), (err) => {
if (err) throw err;
});
callback("success");
}
}
And this is the JSON file that I want to update:
{
"teachers": [
{
"name": "",
"email": "",
"password": "",
"formGroup": "",
"id": ""
}
],
"students": [
{
"surname": "test",
"forename": "test",
"dob": "",
"homeAddress": "",
"homePhone": "",
"gender": "",
"tutorGroup": "",
"schoolEmail": "",
"grades": [
{
"french": 8,
"maths": 7
}
],
"id": ""
},
{
"surname": "test2",
"forename": "test2",
"dob": "",
"homeAddress": "test2",
"homePhone": "",
"gender": "",
"tutorGroup": "",
"schoolEmail": "",
"grades": [
{
"french": 9,
"maths": 8
}
],
"id": ""
}
]
}
I had to remove and change the objects and info inside them, as it contained confidential information.
When running this function, it finds the object that is specified in the parameter. It then updates that object, but it then creates another object at the bottom of the original JSON object, which it is not supposed to.
Also, is there a better way to update the specific objects in the JSON file?
tl;dr
The result set is duplicating because you are pushing it into the array
The change is being applied due to the variables holding the same object reference, so they are being mirrored across objects that share the same pointer.
Explanation
It creates a new one due to the data["students"].push(all); instruction.
When you manipulate objects in javascript you need to be aware of how the reference between them work, so you can avoid bugs and use them in your benefit.
For example, take this set of instructions:
let a = {"x": 1};
let b = a;
b.x = 3;
console.log(a) // it will output {"x": 3}
Notice that we:
Create an object with the prop x equal 1 and assign it to the variable a
Initialize a variable b with the value of a
Change the property x on the variable/object b
Then we can observe that the change was also reflected in the variable a, due to the object reference.
So, basically this is exactly what is happening with your instructions when you do all.forename = forename; it changes the variable all, but also the original object which it derives from.
Here is a nice reference that explains this concept more in-depth
#EDIT
I strongly advise you not using the sync version of functions like readFileSync since this blocks the event loop. Here is the official guidelines about it
So lets say I have some JSON:
{
"users": [{
"name": "bob",
"age": 16,
"likes": ["cats", "kayaking", "knitting"]
}, {
"name": "kyle",
"age": 19,
"likes": ["dogs", "soccer", "baseball"]
}, {
"name": "mike",
"age": 18,
"likes": ["cats", "cars", "kayaking"]
}]
}
and I want to go through that and return all user objects with the likes that include "cats" and "kayaking". I'm using lodash and there doesn't seem to be a lodash method to do that. I only see _.findKey and _.includes. Is there a method or group of methods that I can use that'll do this or am I better off just using vanilla javascript?
You are looking for _.filter():
var output = _.filter(input.users, function(item) {
return _.includes(item.likes, 'cats')
&& _.includes(item.likes, 'kayaking');
});
It filters the array using the filter function you specify. If this filter function returns true for a given item of the array, this item will be included in the resulting array.
If you want to use Vanilla JS:
You can use indexOf along with a comparator array for comparison.
Using this comparator array, you can filter the results to return only the objects that match the criteria.
Additionally use a reduce method to return true or false depending on whether or not one of the strings in the array matches your comparator array.
var comparator = ['dogs', 'baseball'];
var dogsAndBaseball = obj.users.filter(function(obj) {
return obj.reduce(function(acc, cur) {
return acc || comparator.indexOf(cur) > -1;
}, false);
});
This will return
[{
"name": "kyle",
"age": 19,
"likes": ["dogs", "soccer", "baseball"]
}]
I have the following context:
https://jsfiddle.net/eqntaqbt/2/
obj.forEach(function(user, index){
var userName = user.name;
console.log(index, userName);
if(index === 5 || index === 2){
obj.splice(index, 1);
}
});
I am using a forEach loop and splice to remove the item in position 5 and 2 on the obj array. But for some reason its not working properly.
What am I doing wrong?
Your code is splicing while looping. Spliced elements are accessed even they are no more existing. That leads to undefined elements.
You may consider Array#filter
var obj = [{ "index": 0, "name": "Odonnell Noble", "gender": "male", "company": "DIGIQUE", "eail": "odonnellnoble#digique.com" }, { "index": 1, "name": "Marie Oneal", "gender": "female", "company": "CANOPOLY", "email": "marieoneal#canopoly.com" }, { "index": 2, "name": "Adrienne Marsh", "gender": "female", "company": "XOGGLE", "email": "adriennemarsh#xoggle.com" }, { "index": 3, "name": "Goff Mullins", "gender": "male", "company": "ENDIPIN", "email": "goffmullins#endipin.com" }, { "index": 4, "name": "Lucile Finley", "gender": "female", "company": "AQUASSEUR", "email": "lucilefinley#aquasseur.com" }, { "index": 5, "name": "Pitts Mcpherson", "gender": "male", "company": "QUARX", "email": "pittsmcpherson#quarx.com" }];
obj = obj.filter(function (user, index) {
return (user.index !== 5 && user.index !== 2);
});
document.write('<pre>' + JSON.stringify(obj, 0, 4) + '</pre>');
From Array#forEach
The range of elements processed by forEach() is set before the first
invocation of callback. Elements that are appended to the array after
the call to forEach() begins will not be visited by callback. If the
values of existing elements of the array are changed, the value passed
to callback will be the value at the time forEach() visits them;
elements that are deleted before being visited are not visited.
obj.forEach(function(user, index){
var userName = user.name;
//console.log(index, userName);
if(user.index === 5 || user.index === 2){
this.splice(index, 1);
}
}.bind(obj));
Here is the working fiddle
forEach is rather meant for so-called side-effects.
The problem with your code is, that you are changing the array while you are iterating over it. So if you remove one item, all the other indices of the array are reassigned immediately. That's why after removing one item, further removals don't do the desired thing (at the desired position).
So forEach is good for effecting things that are outside the actual array, that is iterated.
This would be a perfect usecase for a function called filter, since that's actually, what you are doing with your list: you want to filter out some items.
array = array.filter(function(item, index) {
return (index !== 5 && index !== 2)
}
Filter excepts a function as an argument, which itself will be called for each item in the array. If the function returns true for an item, it's kept - otherwise removed. That's why the logical expression must be slightly changed here: It reads like: Keep the items, which are not of index 5 and not of index 2. These sort of true-or-false returning functionas are called predicates.
What if you want to filter out some more indices? The expression using locical operators becomes quickly quite long.
You can instead use the array method indexOf on a list of indices, passing each time the current index of your array to it. Either this will return the a position or -1 if it's not in it. In the later case you want to keep the item in the array.
array = array.filter(function(item, current_index) {
return ([2, 5].indexOf(current_index) === -1)
}
Also, you could wrap that in a function:
function removeIndices(array, indices) {
return array.filter(function(item, current_index) {
return (indices.indexOf(current_index) === -1)
})
}
Finally:
array = removeIndices(array, [2, 5]);
I am trying to get the same results as pythons json.dumps() with sort by keys enabled. This is preformed as a pre-request script for Postman to generate the request hash. The output needs to be sorted valid json which is used as input for hashing. I am new to javascript and see many old answers claiming that objects in javascript cannot be sorted. However there must be a solution to generate the hash given the criteria.
The object structure cannot be changed.
It only needs to support Chrome.
I can use libraries.
requestParams can contain nested objects and arrays which need to be sorted at any depth.
This is my current code. In the Chrome console the object preview for sortedResult is unsorted, however when I expand the object and sub-objects the Chrome console shows sortedResult as sorted, exactly the way it should be. This gives me the impression the sortObject is working. However requestOrdered returns the valid json object but it is not sorted. My initial thoughts are that maybe JSON.stringify() is unsorting it.
const requestRebuilt = {"username": user, "password": password, "sTime": time, "function": function,
"functionParams": requestParams, "salt": salt};
function sortObject(object){
var keys = _.keys(object);
var sortedKeys = _.sortBy(keys, function(key){
//console.log(key);
return key;
});
var sortedObj = {};
var sortedObjJson = "";
for(var index in keys){
var key = keys[index];
//console.log(key + ' ' + typeof object[key]);
if(typeof object[key] == 'object' && !(object[key] instanceof Array)){
sortedObj[key] = sortObject(object[key]);
} else if(object[key] instanceof Array) {
//sortedObj[key] = object[key].sort();
var arrayLength = object[key].length;
for (var i = 0; i < arrayLength; i++) {
sortedObj[key] = sortObject(object[key][i]);
//console.log(object[key][i]);
}
} else {
sortedObj[key] = object[key];
}
}
return sortedObj;
}
const sortedResult = sortObject(requestRebuilt);
console.log(sortedResult);
const requestOrdered = JSON.stringify(sortedResult);
console.log(requestOrdered);
var hash = CryptoJS.SHA256(requestOrdered).toString();
postman.setGlobalVariable("hash", hash);
Example input:
{
"username": "jdoe#mail.com",
"sTime": "2016-03-04T13:53:37Z",
"function": "begin",
"functionParams": {
"tip": "ABC123FFG",
"pad": 4 ,
"passenger": [{
"firstName": "John",
"phone": 1234567890,
"email": "jdoe#mail.com",
"dateOfBirth": "1915-10-02T00:00:00Z",
"bans": {
"weight": 9,
"count": 2
}
}
]},
"salt": "00d878f5e203",
"pep": "sdeODQ0T"
}
In python this is done by the following:
ordered = json.dumps(
{"username": user, "password": password, "time": time, "function": function, "functionParams": functionParams, "salt": salt}
sort_keys=True, separators=(',', ':'))
Result of ordered:
{"function":"begin","functionParams":{"passenger":[{"bans":{"count":2,"weight":9},"dateOfBirth":"1915-10-02T00:00:00Z","email":"jdoe#mail.com","firstName":"John","phone":1234567890}],"pad":4,"tip":"ABC123FFG"},"pep":"sdeODQ0T","salt":"00d878f5e203","sTime":"2016-03-04T13:53:37Z","username":"jdoe#mail.com"}
Pretty printed for easier reading but actual result should not have spaces or new lines:
{
"function": "begin",
"functionParams": {
"passenger": [
{
"bans": {
"count": 2,
"weight": 9
},
"dateOfBirth": "1915-10-02T00:00:00Z",
"email": "jdoe#mail.com",
"firstName": "John",
"phone": 1234567890
}
],
"pad": 4,
"tip": "ABC123FFG"
},
"pep": "sdeODQ0T",
"salt": "00d878f5e203",
"sTime": "2016-03-04T13:53:37Z",
"username": "jdoe#mail.com"
}
It's a common misconception that "object keys are not ordered" in javascript. MDN states that
Although ECMAScript makes iteration order of objects implementation-dependent, it may appear that all major browsers support an iteration order based on the earliest added property coming first (at least for properties not on the prototype).
and ES2015 makes this behaviour standard:
For each own property key P of O that is a String but is not an integer index, in property creation order...
That is, you can rely on the fact that object properties are always iterated in the insertion order (unless you're using delete, see here for details).
So, to sort keys in some object just create a new object and add keys to it in the sorted order:
function sortKeys(x) {
if (typeof x !== 'object' || !x)
return x;
if (Array.isArray(x))
return x.map(sortKeys);
return Object.keys(x).sort().reduce((o, k) => ({...o, [k]: sortKeys(x[k])}), {});
}
////
obj = {
"username": "jdoe#mail.com",
"sTime": "2016-03-04T13:53:37Z",
"function": "begin",
"functionParams": {
"tip": "ABC123FFG",
"pad": 4,
"passenger": [{
"firstName": "John",
"phone": 1234567890,
"email": "jdoe#mail.com",
"dateOfBirth": "1915-10-02T00:00:00Z",
"bans": {
"weight": 9,
"count": 2
}
}
]
},
"salt": "00d878f5e203",
"pep": "sdeODQ0T"
}
sorted = sortKeys(obj);
console.log(sorted);
I am wondering how would I get the next JSON item if I have the key in JavaScript. For example, if I provide the key 'Josh' how would I get the contents of 'Annie' along with the key 'Annie'? Would I have to process the JSON in an array and extract from there?
In addition, I believe that there is a proper term for transforming data from one type to another. Any chance anyone knows what it is... it is just on the tip of my tongue!
{
"friends": {
"Charlie": {
"gender": "female",
"age": "28"
},
"Josh": {
"gender": "male",
"age": "22"
},
"Annie": {
"gender": "female",
"age": "24"
}
}
}
In JavaScript the order of Object properties is not guaranteed (ECMAScript Third Edition (pdf):)
4.3.3 Object An object is a member of the type Object. It is an unordered collection of properties each of which contains a primitive
value, object, or function. A function stored in a property of an
object is called a method.
If the order doesn't have to be guaranteed you could do the following:
var t = {
"friends": {
"Charlie": {
"gender": "female",
"age": "28"
},
"Josh": {
"gender": "male",
"age": "22"
},
"Annie": {
"gender": "female",
"age": "24"
}
}
};
// Get all the keys in the object
var keys = Object.keys(t.friends);
// Get the index of the key Josh
var index = keys.indexOf("Josh");
// Get the details of the next person
var nextPersonName = keys[index+1];
var nextPerson = t.friends[nextPersonName];
If order matters I would recommend having another array of to hold the order of the names ["Charlie", "Josh", "Annie"] instead of using Object.keys().
var t = ...;
// Hard code value of keys to make sure the order says the same
var keys = ["Charlie", "Josh", "Annie"];
// Get the index of the key Josh
var index = keys.indexOf("Josh");
// Get the details of the next person
var nextPersonName = keys[index+1];
var nextPerson = t.friends[nextPersonName];