Javascript: How to insert dynamic keys into map/objects? - javascript

I am a JS beginner and am doing basic tutorials. I am trying to perform a zip function to return a list of {videoID: bookmarkID}. So take for example:
var videos = [
{
"id": 70111470,
"title": "Die Hard"
},
{
"id": 654356453,
"title": "Bad Boys"
},
{
"id": 65432445,
"title": "The Chamber"
}
],
bookmarks = [
{id: 470, time: 23432},
{id: 453, time: 234324},
{id: 445, time: 987834}
];
}
This does not work (I get unexpected token '.'):
return Array.zip(videos,bookmarks, function(v, b){
return {v.id: b.id};
});
This does, but returns a list containing {'v': bookmarkID}:
return Array.zip(videos,bookmarks, function(v, b){
return {v: b.id};
});
How do I get the video ID to be the key for the value bookmarkID? Also, are these technically maps or objects? Thanks.

Try:
return Array.zip(videos,bookmarks, function(v, b){
return {[v.id]: b.id};
});

You could map one and get the elemnt of the other with the same index.
var videos = [{ "id": 70111470, "title": "Die Hard" }, { "id": 654356453, "title": "Bad Boys" }, { "id": 65432445, "title": "The Chamber" }],
bookmarks = [{ id: 470, time: 23432 }, { id: 453, time: 234324 }, { id: 445, time: 987834 }],
zipped = videos.map(function (v, i) {
var o = {};
o[v.id] = bookmarks[i].id;
return o;
});
console.log(zipped);
.as-console-wrapper { max-height: 100% !important; top: 0; }
ES6
var videos = [{ "id": 70111470, "title": "Die Hard" }, { "id": 654356453, "title": "Bad Boys" }, { "id": 65432445, "title": "The Chamber" }],
bookmarks = [{ id: 470, time: 23432 }, { id: 453, time: 234324 }, { id: 445, time: 987834 }],
zipped = videos.map((v, i) => ({ [v.id]: bookmarks[i].id }));
console.log(zipped);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Your first attempt doesn't work because the key of the object you used is not correct. When you use object literals to define an object, keys have to be numeric or string literals.
On the other hand, you could solve that problem using array-like notation to create that key in the object. In JS there are two ways of accessing the value of a key within an object: using dot notation or array-like notation. The array-like notation allows you to access keys of an object not determined until runtime (keys that are variables). For instance:
var obj = {};
for(var i = 0; i < 3; i++)
obj[i] = i + 1;
console.log(obj);
In case you're interested, there isn't much difference in terms of performance between the two.
Getting back to your problem. Zip is a function that, in words of functional programmers, takes a pair of lists and creates a list of pairs. From my point of view (and a functional point of view I think), it might be wiser to tackle it differently from what you've done.
For instance (and just as a quick idea), you could create a function that does the zip (typical zip) and apply to the result to another function that picks the values you're interested in:
var videos = [{
"id": 70111470,
"title": "Die Hard"
}, {
"id": 654356453,
"title": "Bad Boys"
}, {
"id": 65432445,
"title": "The Chamber"
}],
bookmarks = [{
id: 470,
time: 23432
}, {
id: 453,
time: 234324
}, {
id: 445,
time: 987834
}];
// Assuming same size.
function zip(list1, list2) {
return list1.length ?
[[list1[0], list2[0]]].concat(zip(list1.slice(1), list2.slice(1))) : [];
}
function makeObj(list) {
var obj = {};
obj[list[0].id] = list[1].id;
return obj;
}
console.log(zip(videos, bookmarks).map(makeObj));
I made a simple zip using recursion (there are optimal ways to do it, and also can be done taking into account that the lists can have different sizes) but I think it can help you to grasp the idea.
Hope it helps

Related

How can I add an element in object at certain position?

I have this object:
var ages = [{
"getasafieldDetail": {
"id": "xxx",
"asaentrySet": [{
"result": "ON",
"buy": {
"username": "Dis"
},
"offerSet": [{
"createdStr": "2001-08-09 at 11:52 pm",
"value": 5.0
}]
}]
}
}];
and i want to add an element and have an output like this:
var ages = [{
"getasafieldDetail": {
"id": "xxx",
"asaentrySet": [{
"result": "ON",
"buy": {
"username": "Dis"
},
"land": "111", // THIS <<<<------------
"offerSet": [{
"createdStr": "2001-08-09 at 11:52 pm",
"value": 5.0
}]
}]
}
}];
i tried using splice but not works...
ages.splice(ages[0]['getasafieldDetail']['asaentrySet'][0]['offerSet'],0,'"land": "111"');
ages.join();
There is the handy syntax of Destructuring assignments which helps with cutting and reassembling objects.
Edit
#FireFuro99 did point to the ES6/ES2015 spec which explicitly states how to preserve/handle an object's key-order at the object's creation time.
Thus one can say ...
Every JS engine which does support Destructuring assignment has to respect too any object's key order from/at this object's creation time.
const ages = [{
getasafieldDetail: {
id: "xxx",
asaentrySet: [{
result: "ON",
buy: {
username: "Dis",
},
offerSet: [{
createdStr: "2001-08-09 at 11:52 pm",
value: 5.0,
}],
}],
},
}];
const { result, buy, ...rest } = ages[0].getasafieldDetail.asaentrySet[0];
ages[0].getasafieldDetail.asaentrySet[0] = {
result,
buy,
land: "111",
...rest,
};
console.log({ ages });
.as-console-wrapper { min-height: 100%!important; top: 0; }
Splice only works on Arrays.
To make this work, convert your Object to an Array using Object.entries(), then use splice, and then convert it back to an object using Object.fromEntries().
const entrySet = Object.entries(ages[0]['getasafieldDetail']['asaentrySet'][0]);
entrySet.splice(2,0, ["land", "111"]);
ages[0]['getasafieldDetail']['asaentrySet'][0] = Object.fromEntries(entrySet);
This will insert the key-value pair at the the specified position.
The advantage this has over the destructuring assignment is, that you can specify the index, whereas destructuring is pretty hardcoded.
ages[0]["getasafieldDetail"]["asaentrySet"][0].land = '111' will create the key land in the first object in asaentrySet and assign the value 111. Key order is not guaranteed
var ages = [{
"getasafieldDetail": {
"id": "xxx",
"asaentrySet": [{
"result": "ON",
"buy": {
"username": "Dis"
},
"offerSet": [{
"createdStr": "2001-08-09 at 11:52 pm",
"value": 5.0
}]
}]
}
}];
ages[0]["getasafieldDetail"]["asaentrySet"][0].land = '111'
console.log(ages)
When it is an array of objects you could simple, add, passing the position that you want by editing the array like the example below:
let land = {land: 1111}
let ages = [{'a':11},'2', 'wd']
let new =[]
new.push(ages[1])
new.push(land)
ages[1] = new
console.log(ages)
output:
(3) [{…}, Array(2), "wd"]
You get what you want from the array, edit it, and put back in the same position, may it can help.

Filter out objects in array if one value is the same

I am fetching data from an api that, sometimes, gives me multiple objects with the same values, or very similar values, which I want to remove.
For example, I might get back:
[
{
"Name": "blah",
"Date": "1992-02-18T00:00:00.000Z",
"Language": "English",
},
{
"Name": "blahzay",
"Date": "1998-02-18T00:00:00.000Z",
"Language": "French",
}, {
"Name": "blah", // same name, no problem
"Date": "1999-02-18T00:00:00.000Z", // different date
"Language": "English", // but same language
},
]
So I want to check that no two objects have a key with the same "Language" value (in this case, "English").
I would like to get the general process of filtering out the entire object if it's "Language" value is duplicated, with the extra issue of not having the same number of objects returned each time. So, allowing for dynamic number of objects in the array.
There is an example here:
Unexpeected result when filtering one object array against two other object arrays
but it's assuming that you have a set number of objects in the array and you are only comparing the contents of those same objects each time.
I would be looking for a way to compare
arrayName[eachObject].Language === "English"
and keep one of the objects but any others (an unknown number of objects) should be filtered out, most probably using .filter() method along with .map().
The below snippets stores the languages that have been encountered in an array. If the current objects language is in the array then it is filtered out. It makes the assumption that the first object encountered with the language is stored.
const objs = [
{
"Name": "blah",
"Date": "1992-02-18T00:00:00.000Z",
"Language": "English",
},
{
"Name": "blahzay",
"Date": "1998-02-18T00:00:00.000Z",
"Language": "French",
}, {
"Name": "blah", // same name, no problem
"Date": "1999-02-18T00:00:00.000Z", // different date
"Language": "English", // but same language
},
],
presentLanguages = [];
let languageIsNotPresent;
const objsFilteredByLanguage = objs.filter(function (o) {
languageIsNotPresent = presentLanguages.indexOf(o.Language) == -1;
presentLanguages.push(o.Language);
return languageIsNotPresent;
});
console.log(objsFilteredByLanguage);
You could take a hash table and filter the array by checking Name and Language.
var array = [{ Name: "blah", Date: "1992-02-18T00:00:00.000Z", Language: "English" }, { Name: "blahzay", Date: "1998-02-18T00:00:00.000Z", Language: "French" }, { Name: "blah", Date: "1999-02-18T00:00:00.000Z", Language: "English" }],
hash = {},
result = array.filter(({ Name, Language }) => {
var key = `${Name}|${Language}`;
if (!hash[key]) return hash[key] = true;
});
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Using Set makes it easy to remove duplicates for as many keys as you like. I tried to be as verbose as possible so that each step was clear.
var objects = [{ "Name": "blah", "Date": "1992-02-18T00:00:00.000Z", "Language": "English", }, { "Name": "blah", "Date": "1998-02-18T00:00:00.000Z", "Language": "French", }, { "Name": "blah", "Date": "1999-02-18T00:00:00.000Z", "Language": "English" }];
function uniqueKeyVals(objects, key) {
const objVals = objects.map(object => object[key]); // ex. ["English", "French", "English"]
return objects.slice(0, new Set(objVals).size); // ex. { "English", "French" }.size = 2
}
function removeKeyDuplicates(objects, keys) {
keys.forEach(key => objects = uniqueKeyVals(objects, key));
return objects;
}
// can also use uniqueKeyVals(key) directly for just one key
console.log("Unique 'Language': \n", removeKeyDuplicates(objects, ["Language"]));
console.log("Unique ['Language', 'Name']: \n", removeKeyDuplicates(objects, ["Language", "Name"]));
I would use the underscore module for JavaScript and the unique function in this scenario. Here is a sample array of data objects:
let data = [{
name: 'blah',
date: Date.now(),
language: "en"
},
{
name: 'noblah',
date: Date.now(),
language: 'es'
},
{
name: 'blah',
date: Date.now(),
language: 'en'
}];
Then we can use the unique function in the underscore library to only return a copy of the data that has unique values associated with the language key:
const result = _.unique(data, 'language');

Normalize objects in array such that all contain the same set of keys

I am hitting an endpoint that is returning an array of objects, each object can potentially have a set of fields, e.g.,
const FIELDS = [
'id',
'title',
'contributor',
'mediatype',
'source'
]
However, some objects will only have some of those fields, some may have all.
const items = [
{
"id": 1,
"title": "some title 1",
"contributor": "bob",
"mediatype": "text"
},
{
"id": 2,
"title": "some title 2",
"mediatype": "text"
}.
{
"id": 3,
"title": "some title 3",
"mediatype": "movies"
"source": "comcast"
}
]
I want to "normalize" all the objects such that every single one contains every expected field, filling the "gaps" with null, or some falsey value such that graphql (which I intend to eventually feed it into) is happy.
const items = [
{
"id": 1,
"title": "some title 1",
"contributor": "bob",
"mediatype": "text",
"source": null
},
{
"id": 2,
"title": "some title 2",
"mediatype": "text",
"contributor": null,
"source": null
}.
{
"id": 3,
"title": "some title 3",
"mediatype": "movies",
"contributor": null,
"source": "comcast"
}
]
My "nasty" looking code looks something like this
const normalize = items =>
items.map(item => {
FIELDS.forEach(f => {
if (!item[f]) {
item[f] = null;
}
});
return item;
});
Any suggestions for writing this more elegantly - either with vanilla JS or lodash, which I am equally open to using as its already available in my codebase?
You can use spread syntax, but then it would be better to define FIELDS as a template object:
const FIELDS = {
id: null,
title: null,
contributor: null,
mediatype: null,
source: null
};
const normalize = items => items.map(item => ({...FIELDS, ...item}));
Your if (!item[f]) test will match on any falsy value, which is probably not what you want.
Instead, you should properly check if the key exists, e.g.:
if (!(f in item))
Not sure if this is any better really... but here is some equivalent alternative syntax.
Use an "equals itself or null" to squeeze out a bit more sugar:
const normalize = items =>
items.map(item => {
FIELDS.forEach(f => item[f] = item[f] || null);
return item;
});
Or test everyone's patience with this one liner:
const normalize = items =>
items.map(item => FIELDS.reduce((acc, field) => {acc[field] = item[field] || null; return acc}, item));
The choice is yours.

how to limit the number of function each?

I have the following object:
"data": [
{
"label": "dataName",
"sections": [
{
"label": "label sections 1",
"fields": [
{
"id": 1,
"name": "field 1",
"value": "value field 1"
},
{
"id": 2,
"name": "field 2",
"value": "value field 2"
}
]
},
{
"label": "label sections 2",
"fields": [
{
"id": 5,
"name": "field 3",
"value": "value field 3"
}
]
}
]
I would like to create a new array by retrieving data from each field.
like this :
array [
{id: field.id, name: field.name, value: field.value }
{id: field.id, name: field.name, value: field.value }
]
I thought I would use each function like this :
_.each(data, function (elt) {
_.each(elt.ections, function (elt) {
....
})
});
but using the each function I should multiply the functions each.
Is there a solution to get the same result without using several functions each?
If you have a solution ?
Cordially
Use the reduce method:
var reduceSections = data.reduce((a,b) => (a.concat(b.sections)),[]);
var reduceFields = reduceSections.reduce((a,b) => (a.concat(b.fields)),[]);
var result = reduceFields;
console.log(result);
For more information, see
MDN JavaScript Reference - Array.prototype.reduce
MDN JavaScript Reference - Array.prototype.concat
The DEMO
var data = [{
"label": "dataName",
"sections": [{
"label": "label sections 1",
"fields": [{
"id": 1,
"name": "field 1",
"value": "value field 1"
},{
"id": 2,
"name": "field 2",
"value": "value field 2"
}]
},{
"label": "label sections 2",
"fields": [{
"id": 5,
"name": "field 3",
"value": "value field 3"
}]
}]
}];
var reduceSections = data.reduce((a,b) => (a.concat(b.sections)),[]);
var reduceFields = reduceSections.reduce((a,b) => (a.concat(b.fields)),[]);
var result = reduceFields;
console.log(result);
Only downside is that mutating the original data object will mutate the result in the array. (no shallow cloning)
That may or may not be a downside depending on the application.
If you want to clone the objects:
var clone = result.map(obj => Object.assign({},obj));
For more information, see
MDN JavaScript Reference - Object.assign
MDN JavaScript Reference - Array.prototype.map
As you are making use of lodash already, you have access to _.flatMap, _.map and _.clone.
Unfortunately, with your data structure, iterating over the arrays in your data is required, but with depending on what you are trying to achieve, there are alternatives to _.each.
Assuming you want to join all of cloned entries in fields, that are nested in each entry of the array sections, that are nested in each entry of the array data, you can use the following code:
function cloneFields(elt) { return _.map(elt.fields, _.clone) }
var allClonedFields = _.flatMap(data, elt => {
return _.flatMap(elt.sections, cloneFields);
});
The function cloneFields() is initialized outside of the loop for performance so that it isn't created on every iteration.
This code will pull out each entry in data, then from that entry pull out each entry in the sections key, then return the clone of each entry in the fields key and then join them into one large array giving the following result:
[ { id: 1, name: 'field 1', value: 'value field 1' },
{ id: 2, name: 'field 2', value: 'value field 2' },
{ id: 5, name: 'field 3', value: 'value field 3' } ]
If you don't know exactly how "deep" is your object i recommand you using recursive function. Here is what i suggest :
function recursivlyCreateObject(data) {
let result;
if(Array.isArray(data)) {
result = [];
data.forEach(function(element) {
result.push(recursivlyCreateObject(element));
});
} else {
result = {};
Object.keys(data).forEach(function(key) {
result[key] = data[key];
});
}
return result;
}
You can test it here
EDIT : note that this won't do much more than a simple console.log over the data but can help you about iterating an object recursivly
If i understand correctly, you're trying to get the fields property from each element in the array. To do this, take a look at array.map().
using array.map, you could do something like this:
let array = data.map(x => x.fields);
or:
let array = data.map(x => (
{id: x.fields.id,name: x.fields.name, value: x.fields.value }
));
https://www.w3schools.com/jsref/jsref_map.asp

Join JSON attributes

I am working with javascript and I would like to join two JSON file into a single JSON object that contains all the attributes. Right now the two JSON file have separate information but I need to combine them.
Station Information JSON - Example Below:
{
"last_updated":1493307962,
"ttl":10,
"data":{
"stations":[
{
"station_id":"219",
"name":"Central Square - East Boston",
"short_name":"A32036",
"lat":42.37454454514976,
"lon":-71.03837549686432,
"region_id":10,
"rental_methods":[
"KEY",
"CREDITCARD"
],
"capacity":19,
"eightd_has_key_dispenser":false
},
{
"station_id":"220",
"name":"Test 1",
"short_name":"Test 1",
"lat":0,
"lon":0,
"rental_methods":[
"KEY",
"CREDITCARD"
],
"capacity":0,
"eightd_has_key_dispenser":false
}
]
}
}
Station Status JSON - Example Below:
{
"last_updated":1493308075,
"ttl":10,
"data":{
"stations":[
{
"station_id":"219",
"num_bikes_available":7,
"num_bikes_disabled":1,
"num_docks_available":11,
"num_docks_disabled":0,
"is_installed":1,
"is_renting":1,
"is_returning":1,
"last_reported":1493283725,
"eightd_has_available_keys":false
},
{
"station_id":"220",
"num_bikes_available":0,
"num_bikes_disabled":0,
"num_docks_available":0,
"num_docks_disabled":0,
"is_installed":0,
"is_renting":0,
"is_returning":0,
"last_reported":0,
"eightd_has_available_keys":false
}
]
}
}
Specifically, I looked at this post (How to join two json object in javascript, without using JQUERY) but the two JSON files have more complex structure, so I could not make it work.
Any suggestion will be really appreciated.
This code behaves like a join on the 2nd object (but can be extended to perform a full outer join)
It handles conflicts by appending a string _conflict to the key name
I've written this one to get you started but you'll have to customize it to support your exact structure
The combined object isn't a list anymore but has the same indexes as the array.
var obj1 = {
"conflicting_key":1493307962,
"concurrent_key":10,
"data":{
"listOfEvents":[
{
"event_id":219,
"name":"Central Square - East Boston",
"rental_methods":[
"KEY",
"CREDITCARD"
],
"capacity":19
},
{
"event_id":220,
"name":"Test 1",
"lat":0,
"lon":0,
"rental_methods":[
"KEY",
"CREDITCARD"
],
"capacity":0,
"eightd_has_key_dispenser":false
}
]
}
};
var obj2 = {
"conflicting_key":1493308075,
"concurrent_key":10,
"data":{
"listOfEvents":[
{
"event_id":219,
"num_bikes_available":7,
"num_bikes_disabled":1,
"last_reported":1493283725,
"eightd_has_available_keys":false
},
{
"event_id":220,
"num_bikes_available":0,
"num_bikes_disabled":0,
"num_docks_available":0,
"is_returning":0,
"last_reported":0,
"eightd_has_available_keys":false
}
]
}
};
function combine(obj1, obj2) {
var combinedObject = Object.assign({}, obj1);
for(var key in obj2) {
if(typeof obj2[key] !== "object") {
if(obj1[key] !== obj2[key]) {
var keyName = key;
if(key in obj1) {
keyName = keyName + "_conflict";
}
combinedObject[keyName] = obj2[key];
}
} else {
combinedObject[key] = combine(obj1[key], obj2[key]);
}
}
return combinedObject;
}
console.log(combine(obj1, obj2));
I guess you want to merge stations. If both stations arrays are in the same order (as your example shows) you can do it easily this way:
First parse both JSON using JSON.parse and then merge each station object using Object.assign
var obj1 = JSON.parse('your-first-json');
var obj2 = JSON.parse('your-second-json');
obj1.data.stations.forEach(function(item, i) {
Object.assign(item, obj2.data.stations[i])
});
//obj1 will have the obj2 sation data.
If the arrays are not in the same order (same index - same ID) you'll have to perform a lookup by ID before performing the merge.
You could use Array.find for that:
obj1.data.stations.forEach(function(station, i){
var station2 = obj2.data.stations.find(function(item) {
return item.station_id === station.station_id;
});
Object.assign(station, station2);
});
I don't know where you're running this, if in node or in the browser, but there are polyfills for both Object.assign & Array.find in the links I've provided. Also there are many similar functions using jQuery or other similar libraries.
var obj1 = {
"last_updated":1493307962,
"ttl":10,
"data":{
"stations":[
{
"station_id":"219",
"name":"Central Square - East Boston",
"short_name":"A32036",
"lat":42.37454454514976,
"lon":-71.03837549686432,
"region_id":10,
"rental_methods":[
"KEY",
"CREDITCARD"
],
"capacity":19,
"eightd_has_key_dispenser":false
},
{
"station_id":"220",
"name":"Test 1",
"short_name":"Test 1",
"lat":0,
"lon":0,
"rental_methods":[
"KEY",
"CREDITCARD"
],
"capacity":0,
"eightd_has_key_dispenser":false
}
]
}
};
var obj2 = {
"last_updated":1493308075,
"ttl":10,
"data":{
"stations":[
{
"station_id":"219",
"num_bikes_available":7,
"num_bikes_disabled":1,
"num_docks_available":11,
"num_docks_disabled":0,
"is_installed":1,
"is_renting":1,
"is_returning":1,
"last_reported":1493283725,
"eightd_has_available_keys":false
},
{
"station_id":"220",
"num_bikes_available":0,
"num_bikes_disabled":0,
"num_docks_available":0,
"num_docks_disabled":0,
"is_installed":0,
"is_renting":0,
"is_returning":0,
"last_reported":0,
"eightd_has_available_keys":false
}
]
}
};
obj1.data.stations.forEach(function(item, i) {
Object.assign(item, obj2.data.stations[i])
});
console.log(obj1)
I assume you're not interested in the parameters surrounding the stations. You could use this code to get an array of stations with all the information based on station_id
obj1.data.stations.map(el1 => Object.assign({},el1,obj2.data.stations.filter(el2 => el2.station_id === el1.station_id)));
Where obj1 and obj2 are your JSONs.
"[
{
"0": {
"station_id": "219",
"num_bikes_available": 7,
"num_bikes_disabled": 1,
"num_docks_available": 11,
"num_docks_disabled": 0,
"is_installed": 1,
"is_renting": 1,
"is_returning": 1,
"last_reported": 1493283725,
"eightd_has_available_keys": false
},
"station_id": "219",
"name": "Central Square - East Boston",
"short_name": "A32036",
"lat": 42.37454454514976,
"lon": -71.03837549686432,
"region_id": 10,
"rental_methods": [
"KEY",
"CREDITCARD"
],
"capacity": 19,
"eightd_has_key_dispenser": false
},
{
"0": {
"station_id": "220",
"num_bikes_available": 0,
"num_bikes_disabled": 0,
"num_docks_available": 0,
"num_docks_disabled": 0,
"is_installed": 0,
"is_renting": 0,
"is_returning": 0,
"last_reported": 0,
"eightd_has_available_keys": false
},
"station_id": "220",
"name": "Test 1",
"short_name": "Test 1",
"lat": 0,
"lon": 0,
"rental_methods": [
"KEY",
"CREDITCARD"
],
"capacity": 0,
"eightd_has_key_dispenser": false
}
]"

Categories