Adding key/values pairs to JSON map down one level - javascript

I'm trying to dynamically add new key/value pairs to an existing json map using jquery/javascript. An example of my structure is as follows:
var widgetTypes = {
"CLASS_A": {
"A1": "A1 Description",
"A2": "A2 Description"
},
"CLASS_B": {
"B1": "B1 Description",
"B2": "B2 Description"
}
};
How do I append a key/value pair to either class (i.e. CLASS_A or CLASS_B). For example, how would I add item A3 with description "A3 Description" to the CLASS_A section of the structure so that I then had the following?
var widgetTypes = {
"CLASS_A": {
"A1": "A1 Description",
"A2": "A2 Description"
"A3": "A3 Description"
},
"CLASS_B": {
"B1": "B1 Description",
"B2": "B2 Description"
}
};
I know you can do something like widgetTypes['CLASS_A'].type = 'A3' etc. but I can't seem to get it quite right (or I'm way off the track). I found a lot of examples of adding items the level above (i.e. adding a CLASS_C) but nothing for what I'm interested in doing.

There are several ways:
. Dot notation.
[] Bracket notation.
Object.defineProperty() method.
var widgetTypes = {
"CLASS_A": {
"A1": "A1 Description",
"A2": "A2 Description"
},
"CLASS_B": {
"B1": "B1 Description",
"B2": "B2 Description"
}
};
1.
widgetTypes.CLASS_A.A3 = "A3 Description"
2.
widgetTypes["CLASS_A"]["A3"] = "A3 Description"
3.
Object.defineProperty(widgetTypes.CLASS_A, 'A3', {
value: "A3 Description",
writable: true,
enumerable: true,
configurable: true
});
Option 3 has several other advantages over the two that you can configure the property behavior.

widgetTypes.CLASS_A.A3 = "A3 Description";
or equivalently
widgetTypes["CLASS_A"]["A3"] = "A3 Description";

Related

Javascript, filtering on object in comparison to array

I'm still learning and I had a question the other day filtering two arrays, which helped a lot for my understanding!
However now, I have a new issue as the complexity has grown a bit from being two simple arrays! Now instead, I have a array with multiple objects named animals which categorizes animals based whether they are housepets, insects or wild animals and each contains a object with their characteristics. Now I want to compare this on a simple array seen below.
Array with Object
let animals = [
{
"housePets" : [ { "name": "Cat", "food" : "Fish", "description": "Long tail, sharp claws" }, { "name": "Dog", "food" : "Biscuits", "description" : "Humans best friend" } ]
},
{
"wildAnimals" : [ { "name": "Giraffe", "food" : "Leafes", "description" : "A very long neck" }, { "name": "Lion", "food" : "Meat", "description" : "Featured in Lion King" } ]
},
{
"insects" : [ { "name": "Ladybug", "food" : "Leafes", "description" : "Red and black" }, { "name": "Spider", "food" : "Flies", "description" : "From the friendly neighbourhood" } ]
}]
Arr:
let animalsArr2 = [housePets]
I'm having a bit of trouble fully understanding on how to work with this JSON format in general. The biggest struggle for me is targeting the specific array like wildAnimals.
For example, one thing I want to do is filter the animals array based on what's inside animalsArr2 and pick a random one from the animalsarr that's not featured in animalsArr2 like "wildAnimals" and "Insects". I hope someone can give me some ideas on how to tackle this issue or if it can be tackled in a easier way.
You can use the .find() array method to return the list that you want. For example if you want to return the wild animals list:
let animals = [
{
"housePets" : [
{ "name": "Cat", "food" : "Fish", "description": "Long tail, sharp claws" },
{ "name": "Dog", "food" : "Biscuits", "description" : "Humans best friend" }
]
},
{
"wildAnimals" : [
{ "name": "Giraffe", "food" : "Leafes", "description" : "A very long neck" },
{ "name": "Lion", "food" : "Meat", "description" : "Featured in Lion King" }
]
},
{
"insects" : [
{ "name": "Ladybug", "food" : "Leafes", "description" : "Red and black" },
{ "name": "Spider", "food" : "Flies", "description" : "From the friendly neighbourhood" }
]
}
];
const wildAnimals = animals.find((item) => item.wildAnimals);
The code above basically checks which item in your array has a property called wildAnimals and returns that item. You can read more about this method Here.
Hope that helped!
I'm gonna suppose you are forced to use this format of storing data, but I feel like I have to let you know that this isn't the best way to store info like this (you will see that it is hard to work with it). Instead of having an array of objects, you could have an object of arrays like this:
let animals = {
"housePets": [{ "name": "Cat", "food": "Fish", "description": "Long tail, sharp claws" }, { "name": "Dog", "food": "Biscuits", "description": "Humans best friend" }],
"wildAnimals": [{ "name": "Giraffe", "food": "Leafes", "description": "A very long neck" }, { "name": "Lion", "food": "Meat", "description": "Featured in Lion King" }]
}
With this approach, getting all house animals would be as easy as animals.housePets.
Back to your issue tho. If you want to get all house animals from this, you will have to filter the array. To filter arrays, you have to give it a condition that the element has to pass to be stored. Here you can see that only the object with the house animals has a housePets property, so we can take advantage of that:
let house_animals = animals.filter(obj => obj.housePets)[0].housePets; // [{ "name": "Cat", "food": "Fish", "description": "Long tail, sharp claws" }, { "name": "Dog", "food": "Biscuits", "description": "Humans best friend" }]
What it does is that it takes all the objects that have the "housePets" property, takes the first one (I suppose there will always be only one) and reads the property, giving you the array of animals.
To find animals that are only in the housePets section and not in others, you can do this:
let house_animals = animals.filter(obj => obj.housePets)[0].housePets;
let wild_animals = animals.filter(obj => obj.wildAnimals)[0].wildAnimals;
let insects = animals.filter(obj => obj.insects)[0].insects;
let only_house = house_animals.filter(animal => !wild_animals.includes(animal) && !insects.includes(animal); // This checks if the animal is in the wild_animals or insects array. If it isn't, it keeps it.
You can also check this question out
Hope this helped :)
You can follow this code as a starting point. The complete example with updated data is below.
There are two key functions:
function _get(array, label): Take the array and get the names of the animals under the label category.
function _filterAndGet(mainList, filterList): Take the main list of animals, and filter out any animals that are in filter list. Then, pick a random animal from that filtered list.
Code
const animals = [
{
housePets : [{name: 'Cat', food : 'Fish', description: 'Long tail, sharp claws'}, {name: 'Dog', food : 'Biscuits', description : 'Humans best friend'}]
},
{
wildAnimals : [{name: 'Giraffe', food : 'Leafes', description : 'A very long neck'}, {name: 'Lion', food : 'Meat', description : 'Featured in Lion King'}, {name: 'Cat', food : 'Fish', description: 'Long tail, sharp claws'}, {name: 'Ladybug', food : 'Leafes', description : 'Red and black'}]
},
{
insects : [{name: 'Ladybug', food : 'Leafes', description : 'Red and black'}, {name: 'Spider', food : 'Flies', description : 'From the friendly neighbourhood'}]
}]
const wildAnimals = _get(animals, 'wildAnimals');
console.log(`Wild animals: ${JSON.stringify(wildAnimals)}`);
const housePetsAndInsects = _get(animals, 'housePets').concat(_get(animals, 'insects'));
console.log(`House Pets and Insects: ${housePetsAndInsects}`);
console.log(`Random wild animal not in housePets and insects: ${_filterAndGet(wildAnimals, housePetsAndInsects)}`);
function _filterAndGet(mainList, filterList) {
const filterSet = new Set(filterList);
const filteredMainList = mainList.filter(e => !filterSet.has(e));
const index = Math.floor(Math.random() * filteredMainList.length);
return filteredMainList[index];
}
function _get(array, label) {
const res = [];
for (const elem of array) {
for (const objKey of Object.keys(elem)) {
if (objKey === label) {
for (const item of elem[objKey]) {
res.push(item.name);
}
return res;
}
}
}
return res;
}
Example output:
Wild animals: ["Giraffe","Lion","Cat","Ladybug"]
House Pets and Insects: Cat,Dog,Ladybug,Spider
Random wild animal not in housePets and insects: Lion
You can try with this simple way by using Array.find() along with Array.some() methods.
Working Demo :
let animals = [{
"housePets": [
{ "name": "Cat", "food" : "Fish", "description": "Long tail, sharp claws" },
{ "name": "Dog", "food" : "Biscuits", "description" : "Humans best friend" }]
}, {
"wildAnimals": [
{ "name": "Giraffe", "food" : "Leafes", "description" : "A very long neck" },
{ "name": "Lion", "food" : "Meat", "description" : "Featured in Lion King" }]
}, {
"insects": [
{ "name": "Ladybug", "food" : "Leafes", "description" : "Red and black" },
{ "name": "Spider", "food" : "Flies", "description" : "From the friendly neighbourhood" }]
}];
let animalsArr2 = ['housePets'];
const res = animals.find((obj) => animalsArr2.some((elem) => Object.keys(obj).includes(elem)));
console.log(res);

Issues with converting an array of objects into a different format

I'm struggling to convert an array that I've been given into a useful format:
Given
{
"myValues": [{
"0": {
"id": "5ed32599-3c4d-49ad-8a1a-79bbc39a3e02",
"value": "my Value",
"value2": "my Value 2"
},
"1": {
"id": "5ed32599-3c4d-49ad-8a1a-79bbc39a3e02",
"value": "my Value",
"value2": "my Value 2"
},
"id": "5ed32599-3c4d-49ad-8a1a-79bbc39a3e02"
}]
}
I am trying to remove the numbers in front of the objects and just the ID that sits outside of the two inner objects.
Therefore I would then be given:
{
"myValues": [{
"id": "5ed32599-3c4d-49ad-8a1a-79bbc39a3e02",
"value": "my Value",
"value2": "my Value 2"
},
{
"id": "5ed32599-3c4d-49ad-8a1a-79bbc39a3e02",
"value": "my Value",
"value2": "my Value 2"
}
]
}
I have been trying to use different ways of mapping Objects into new formats but I'm really struggling particularly to get rid of the IDs
You can use Object.values().
I added a filter() to remove the single id...not sure if that is a typo in example data or not...or if you also want it included in results
const myValues=[
{
"0":{
"id":"5ed32599-3c4d-49ad-8a1a-79bbc39a3e02",
value : 'my Value',
value2 : 'my Value 2'
},
"1":{
"id":"5ed32599-3c4d-49ad-8a1a-79bbc39a3e02",
value : 'my Value',
value2 : 'my Value 2'
},
"id":"5ed32599-3c4d-49ad-8a1a-79bbc39a3e02"
}
]
const arrValues = Object.values(myValues[0]).filter(el => typeof el === 'object')
console.log(arrValues)

Is there a way to de-dupe a javascript array and combine values of the data?

I'm using an ajax request to grab some XML data which I then need to push into a chart in fusioncharts.
The XML data is formatted as [time taken], [work done], [which team done for], [who did it] (see below).
I'm iterating over the XML and then building the array using the code below:
//Time Recorded
if (columnidchecker == 7781) {
timearray.push($j(this).find('displayData').text());
temp1 = $j(this).find('displayData').text();
}
//Type of Activity
if (columnidchecker == 7782) {
activityarray.push($j(this).find('displayData').text());
temp2 = $j(this).find('displayData').text();
}
//Team Done For
if (columnidchecker == 7783) {
subjectarray.push($j(this).find('displayData').text());
temp3 = $j(this).find('displayData').text();
}
//Name
if (columnidchecker == 7777) {
internalclientarray.push($j(this).find('displayData').text());
temp4 = $j(this).find('userDisplayName').text();
}
});
//PUSH INTO A NEW ARRAY WHICH CAN THEN BE SORTED AND DE-DUPED WITH TIME COMBINED AGAINST ACTIVITY / TEAM.
objectarray.push([temp1, temp2, temp3, temp4]);
This builds an array of entries from the XML which basically outputs to something which looks like this:
0: (4) ["1.50", "Ad-hoc queries or calls", "Team 1", "James"]
1: (4) ["2.50", "Ad-hoc queries or calls", "Team 1", "James"]
2: (4) ["1.00", "Advice", "Team 2", "James"]
3: (4) ["3.50", "Meeting (External 3rd Party)", "Team 1", "James"]
4: (4) ["1.20", "Administration", Team 2", "James"]
5: (4) ["5.50", "Advice", "Team 1", "John"]
I'm trying to build a chart in fusioncharts which needs the format as shown below (ignore foot stuffs - it's taken straight from the fusioncharts help pages!).
{
"chart": {
"theme": "fusion",
"caption": "Revenue split by product category",
"subCaption": "For current year",
"xAxisname": "Quarter",
"yAxisName": "Revenues (In USD)",
"showSum": "1",
"numberPrefix": "$"
},
"categories": [
{
"category": [
{
"label": "Q1"
},
{
"label": "Q2"
},
{
"label": "Q3"
},
{
"label": "Q4"
}
]
}
],
"dataset": [
{
"seriesname": "Food Products",
"data": [
{
"value": "11000"
},
{
"value": "15000"
},
{
"value": "13500"
},
{
"value": "15000"
}
]
},
{
"seriesname": "Non-Food Products",
"data": [
{
"value": "11400"
},
{
"value": "14800"
},
{
"value": "8300"
},
{
"value": "11800"
}
]
}
]
}
The problem i'm having is that I cannot work out how to take the array of data with times, activity, team, name and push them into categories.
I think the first step is to create a new array of names which can be pushed into the "Category" data field in fusioncharts.
I then need a way in which to take the times being recorded against each activity and for each team and make sure it's assigned to the right person within the stacked bar chart and combine the amount of time spent. (i.e. "James" spent a total of 4 hours doing "Ad Hoc Queries and Calls" for Team 1 but this is split across two time entries so I need a way in which to combine them into one.)
Any help on this would be massively appreciated.
I can de-dupe the names to create a new array by using the following code:
namesarray.push(temp4);
uniq = [...new Set(namesarray)];
but after that it starts getting pretty complicated.
Maybe this can help you along the way. It's probably not exactly in the form you want it, but it demonstrates how you could break the problem down into smaller parts.
Pseudo-code:
get the unique names.
get the unique "task" names (for lack of a
better word)
for each unique person name:
3.1. get the data rows for that person
3.2 for each of all unique tasks names:
find the person data rows matching the task name
sum the duration of those data rows
const testData = [
[
"1.50",
"Ad-hoc queries or calls",
"Team 1",
"James"
],
[
"2.50",
"Ad-hoc queries or calls",
"Team 1",
"James"
],
[
"1.00",
"Advice",
"Team 2",
"James"
],
[
"3.50",
"Meeting (External 3rd Party)",
"Team 1",
"James"
],
[
"1.20",
"Administration",
"Team 2",
"James"
],
[
"5.50",
"Advice",
"Team 1",
"John"
]
];
const columnIndexByName = {
TASK_DURATION: 0,
TASK_NAME: 1,
FOR_WHICH_TEAM: 2,
PERSON_DOING_TASK: 3
};
const sum = (acc, next) => acc + next;
const uniqueNames = [...new Set(testData.map(row => row[columnIndexByName.PERSON_DOING_TASK])) ];
const uniqueTaskNames = [...new Set(testData.map(row => row[columnIndexByName.TASK_NAME])) ];
let result = {};
uniqueNames.forEach(personName => {
const personDataRows = testData.filter(row => row[columnIndexByName.PERSON_DOING_TASK] === personName);
let taskDurationsByTaskName = {};
uniqueTaskNames.forEach(taskName => {
const taskRows = personDataRows.filter(row => row[columnIndexByName.TASK_NAME] === taskName);
const taskDurations = taskRows.map(row => Number.parseFloat( row[columnIndexByName.TASK_DURATION] ));
const taskTotalDuration = taskDurations.reduce(sum, 0);
taskDurationsByTaskName[taskName] = taskTotalDuration;
})
result[personName] = taskDurationsByTaskName;
})
const renderData = data => document.querySelector("#output").innerHTML = JSON.stringify(data, null, 2);
renderData(result);
<pre id="output"></pre>

Apply CSS style within .js var

I'm currently building a game which relies on a random number to show a particular string from an array within a .js file. What I want to do is effectively apply a span element to certain text within the array - eg. make positive outcomes green & negative red, however I can't figure out how to do this outside of html. Eg:
var data = [
{"label": "Question 1", "value": 1, "question": "Awareness: High, Consistency: Low."},
{"label": "Question 2", "value": 1, "question": "Consistency: High, Awareness: Low."},
]
A separate set of code, after selecting a random number, returns data[i].label. How would I set a span within js so that the "High" strings return in green, and "Low" return in red?
Cheers.
You have not specified much, but I went and did what I could.
First, your span
<span id="result">here we go</span>
The CSS file, minimalist, I did not include Bootstrap for this.
span {color:green}
.red{color:red}
The JS, the is no event, or event handler.
var data = [
{"label": "Question 1", "value": 1, "question": "Awareness: High, Consistency: Low."},
{"label": "Question 2", "value": 1, "question": "Consistency: High, Awareness: Low."}
]
The array, I have removed the trailing comma after the second object.
let outputs = data[0].question; This will hold the result from the array, data[0] targets the first object, you can also get map over the properties, but you have not entirely specified the scope and desired functionality.
var output = document.getElementById('result'); // the span element
output.innerText = outputs; // i prefer innerText to innerHTML
if (outputs.includes("Consistency: Low")) { // new string method includes, you can pass the whole ("Consistency: Low")in with no worries.
output.classList.toggle("red"); // if condidtion is true, toggle the red class
}
Codepen
https://codepen.io/damPop/pen/ZwvvGV?editors=0010
Uses classes
var data = [
{label: "Question 1", value: 1, awareness: 'High', consistency: 'Low' },
{label: "Question 2", value: 1, awareness: 'Low', consistency: 'High' },
{label: "Question 3", value: 1, awareness: 'Low', consistency: 'Low' },
{label: "Question 4", value: 1, awareness: 'High', consistency: 'High' }
];
const questionElement = document.getElementById('question');
const awarenessElement = document.getElementById('awareness');
const consistencyElement = document.getElementById('consistency');
document.getElementById('select').addEventListener('click', select);
select();
function select() {
const question = data[Math.floor(Math.random() * 4)];
questionElement.innerText = question.label;
awarenessElement.innerText = `Awareness: ${question.awareness}`;
awarenessElement.className = question.awareness;
consistencyElement.innerText = `Consistency: ${question.consistency}`;
consistencyElement.className = question.consistency;
}
.High {
background-color: green;
}
.Low {
background-color: red;
}
<span id="question"></span>
<div id="awareness"></div>
<div id="consistency"></div>
<button id="select">Select</button>

Lodash: filter a nested object by multiple properties

Consider the following example:
var products = {
"Products": [{
"Title": "A",
"Categories": [{
"Name": "Type",
"Properties": ["Type 1", "Type 2", "Type 3"]
}, {
"Name": "Market",
"Properties": ["Market 1", "Market 2", "Market 3", "Market 4"]
}, {
"Name": "Technology",
"Properties": ["Tech 1", "Tech 2"]
}]
}, {
"Title": "B",
"Categories": [{
"Name": "Type",
"Properties": ["Type 1", "Type 3"]
}, {
"Name": "Market",
"Properties": "Market 1"
}, {
"Name": "Technology",
"Properties": ["Tech 1", "Tech 3"]
}]
}, {
"Title": "C",
"Categories": [{
"Name": "Type",
"Properties": ["Type 1", "Type 2", "Type 3"]
}, {
"Name": "Market",
"Properties": ["Market 2", "Market 3"]
}, {
"Name": "Technology",
"Properties": ["Tech 2", "Tech 3"]
}]
}]
}
I'm trying to filter products by their properties so consider I'm using an array to keep track of my selected filters:
var filters = ['Type 3', 'Tech 1'];
With these filters I would like to return product A and product B.
I currently have this:
var flattenedArray = _.chain(products).map('Categories').flatten().value();
var result= _.some(flattenedArray , ['Properties', 'Tech 1']);
But I'm stuck on how to combine the properties for a combined search.
Use _.filter() to iterate the products. For each product combine the list of properties using _.flatMap(), and use _.intersection() and _.size() to find the amount of filters that exist in the categories. Compare that to the original number of filters, and return comparison's response.
var products = {"Products":[{"Title":"A","Categories":[{"Name":"Type","Properties":["Type 1","Type 2","Type 3"]},{"Name":"Market","Properties":["Market 1","Market 2","Market 3","Market 4"]},{"Name":"Technology","Properties":["Tech 1","Tech 2"]}]},{"Title":"B","Categories":[{"Name":"Type","Properties":["Type 1","Type 3"]},{"Name":"Market","Properties":"Market 1"},{"Name":"Technology","Properties":["Tech 1","Tech 3"]}]},{"Title":"C","Categories":[{"Name":"Type","Properties":["Type 1","Type 2","Type 3"]},{"Name":"Market","Properties":["Market 2","Market 3"]},{"Name":"Technology","Properties":["Tech 2","Tech 3"]}]}]};
var filters = ['Type 3', 'Tech 1'];
var result = _.filter(products.Products, function(product) {
return filters.length === _(product.Categories)
.flatMap('Properties')
.intersection(filters)
.size();
});
console.log(result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.2/lodash.min.js"></script>
If I understand you question correctly, this code may help:
_.filter(
products.Products,
product => _.difference(
filters,
_.chain(product.Categories).map(category => category.Properties).flatten().value()
).length === 0
);
It calculates a union of all properties for each product:
_.chain(product.Categories).map(category => category.Properties).flatten().value()
And then checks that it contains all filters array elements, using _.difference method.
Hope it helps.
another fancy way through _.conforms
var res = _.filter(
products.Products,
_.conforms({'Categories': function(categories) {
return _.chain(categories)
.flatMap('Properties') // flat arrays
.uniq() // remove dublicates
.keyBy() // transform to objects with Properties keys
.at(filters) // get objects values by filters
.compact() // remove undefineds
.size() // get size
.eq(filters.length) // compare to filters size
.value();
}
}))
This will work for a list of items where the givenProperty you want to filter on is either a string like 'doorColour' or an array of strings representing the path to the givenProperty like ['town', 'street', 'doorColour'] for a value nested on an item as town.street.doorColour.
It also can filter on more than one value so you could you just need pass in an array of substrings representing the string values you want to keep and it will retain items that have a string value which contains any substring in the substrings array.
The final parameter 'includes' ensures you retain these values if you set it to false it will exclude these values and retain the ones that do not have any of the values you specified in the substrings array
import { flatMap, path } from 'lodash/fp';
const filteredListForItemsIncludingSubstringsOnAGivenProperty = (items, givenProperty, substrings, including=true) => flatMap((item) =>
substrings.find((substring) => path(givenProperty)(item) && path(givenProperty)(item).includes(substring))
? including
? [item]
: []
: including
? []
: [item])(items);
E.g. fLFIISOAGP(contacts, ['person','name'], ['Joh','Pau',Pet']);
with items of structure {contact, business:null, personal:{name:'John'}}.
For the original question - this will also work - I would use this repeatedly on a list of items to filter with different keys to filter on more than one property.
const firstFilteredResult = filteredListForItemsIncludingSubstringsOnAGivenProperty(
products.Products,
["Categories", "0", "Properties"],
["Type 3"]);
const secondFilteredResult = filteredListForItemsIncludingSubstringsOnAGivenProperty(
firstFilteredResult,
["Categories", "2", "Properties"],
["Tech 1"]);
expect(secondFilteredResult[0]['Title']).to.equal( "A");
expect(secondFilteredResult[1]['Title']).to.equal( "B");
expect(secondFilteredResult.length).to.equal(2);

Categories

Resources