I have a NodeJS script that takes data from a source in the form of "key, value" pairs, and I would like to put that data into a JSON object.
I am using SNMP to get the k, v pairs, where the key is the OID. I would like to then map those values onto a JSON object without having to iteratively check each OID against a known value.
The k,v pairs are stored in objects as such, and I have no flexibility over this incoming data.
let pair = {
key: "abc...",
value: "xyz..."
}
I have defined the JSON object as an empty structure.
let jsonObject = {
ipaddress: "",
network: {
uptime: "",
throughput: "",
devices: ""
}
}
And I iterate through my keys (pairs is the name of an array containing the k,v objects)
for (let i = 0; i < pairs.length; i++) {
if (pairs[i].key == "1.2.3.4.5.6.7") jsonObject.ipaddress = pairs[i].value;
if (pairs[i].key == "2.3.4.5.6.7.8") jsonObject.network.uptime = pairs[i].value;
if (pairs[i].key == "3.4.5.6.7.8.9") jsonObject.network.devices = pairs[i].value;
}
I would like to know if there is any way to simplify this, as I have around 200 keys to process (the above code is simply an example), and it doesn't seem particularly well optimised for me to iterate over every possible key.
EDIT: The jsonObject is a lot more complex than shown here, with lots of layers and the names of the keys do not match up 1:1 with the json object property names as shown.
EDIT 2: This seems like an odd situation, I know.
For example, I want to take the K,V input:
Key Value
1.2.3.4.5.6 "10.0.0.1"
2.3.4.5.6.7 "3 Days 14 Hours 32 Minutes"
3.4.5.6.7.8 "1.1.1.1"
And convert it to a dissimilarly named JSON object
{
uptime: "3 Days 14 Hours 32 Minutes",
networking: {
ip: "10.0.0.1",
dns: "1.1.1.1"
}
}
Potentially using some form of mapping, instead of using ~200 if statements
Use the Array.prototype.reduce function.
const { name, ...properties } = pairs.reduce(
(obj, { key, value }) => Object.assign(obj, { [key]: value }),
{}
)
const obj = { name, properties }
Reduce will reduce an array into a scalar, in this case an object with all k,v pairs as fields with values on an object, starting with {} as a default value the merging each k,v pair 1 at a time.
Object.assign will merge objects, overwriting objects on the left with the fields of keys of objects on the right.
And finally the syntax to dynamically add a key on an object literal is { [k]: v }
By the way this is a useful trick for reducing the complexity of algorithms where you need to look up values in an array in a loop. Simply create an index of one array then in the loop, lookup in the index instead.
You can use Array.prototype.forEach for iterating and assigning the object.
const jsonObject = {
name: '',
properties: {
email: '',
address: '',
town: ''
}
};
const pairs = [{key: 'name', value: 'thatsimplekid'},
{key: 'email', value: 'aaa#domain.com'},
{key: 'town', value: 'myTown'},
{key: 'address', value:'123 main st' }];
pairs.forEach( ({key, value}) =>
jsonObject.hasOwnProperty(key) ?
jsonObject[key] = value :
jsonObject.properties[key] = value);
console.log(jsonObject);
for (let i = 0; i < pairs.length; i++) {
const key = pairs[i].key;
const value = pairs[i].value;
if (pairs[i].key == "name") {
jsonObject[key] = value;
} else {
jsonObject.properties[key] = value;
}
}
After the 'name' all values goes to the properties object, so a simple if would do it
Related
Let's say I have two arrays which are returned in a response from a REST call. For simplification I defined them hard-coded as keys and subKeys in the following example code.
From these arrays I'd like to create a nested object which, when outputted as a JSON string, looks like this:
Target JSON
{
"key1": {
"subKey1": "someValue"
},
"key2": {
"subKey2": "someValue"
},
"key3": {
"subKey3": "someValue"
}
}
Code sample
var keys = ["key1", "key2", "key3"]; // These come from a REST response
var subKeys = ["subKey1", "subKey2", "subKey3"]; // These come from a REST response
var targetObj = {}
for (const key in keys) {
targetObj[key] = {}
for (const subKey in subKeys) {
targetObj[key][subKey] = "someValue";
}
}
console.log(JSON.stringify(targetObj, null, 2));
While this gives me the correct behavior in my application I have the impression that there might be simpler approaches to achieve the same result, either in "vanilla" JavaScript or ES6? What bothers me here is that I define an empty object in each run of the for loop.
Your code does not produce the example output you said you want. It will put all 3 subkeys under each key, not one per key. Also you end up with numeric keys, not the key names.
var keys = ["key1", "key2", "key3"]; // These come from a REST response
var subKeys = ["subKey1", "subKey2", "subKey3"]; // These come from a REST response
var targetObj = {}
for (let i=0; i<keys.length; i++) {
const key = keys[i];
targetObj[key] = { [subKeys[i]]: "someValue" };
}
console.log(JSON.stringify(targetObj, null, 2));
First you were using "in" instead of "of" in the for-loop, and secondly you were not using the same index to find the subkey.
To avoid creating an empty object you can use this syntax:
{ [variable]: "value" }
This creates the object with the variable value as the key, and a string as the value. Putting the variable name in square brackets tells it to use the variable value rather than its name. Saying { variable: "value" } wouldn't work because the key would be "variable" not the value of variable.
Just use Array.prototype.reduce method:
const keys = ["key1", "key2", "key3"];
const subKeys = ["subKey1", "subKey2", "subKey3"];
const result = keys.reduce((acc, key, index) =>
({ ...acc, [key]: { [subKeys[index]]: 'someValue' } })
, {});
Note, this works only if keys and subKeys arrays are synced and their indexes are consistent with each other.
I've been at this for a bit trying to come up with a solution. I have tested a handful of stack solutions to iterate through nested objects but haven't gotten this to work correctly. My data structure is below.
I've been trying to first off iterate through nested objects which this stack seemed to be similar to mine.
Iterate through Nested JavaScript Objects
However, when I'm in my for loop getting access to geo, value object to grab all of its properties, ip, hostname, city etc I'm coming up empty handed with undefined. Here's a code snippet of what I've tried below.
I'm trying to take all of the keys and values in this entire object and stringify them into a beautiful parameter string to send over to the server in an ajax request.
for (var i = 0; i < myarray.length; i++) {
var o = myarray[i];
if (o.name === "geo") {
o.value.ip;
}
}
0: {name: "firstname", value: "John"}
1: {name: "lastname", value: "Smith"}
2: {name: "email", value: "asddas#gmail.com"}
3: {name: "password", value: "asdsdadasads"}
4: {name: "paypal_email", value: "asdsa#gmail.com"}
5: {name: "phone", value: "1234567894"}
6: {name: "geo",value: "
{"ip":"111.111.111.111","hostname":"rr.com","city":"MyCity","region":"Ohio","country":"US","loc":"41.34.23","org":"Inc","postal":"1234","timezone":"America/New_York","readme":"https://www.google.com"}"
__proto__: Object
length: 7
__proto__: Array(0)
The problem is that the structure of the geo object is odd:
name: "geo",value: "{"ip":"111.111.111.111","hostname":"rr.com","city":"MyCity","region":"Ohio","country":"US","loc":"41.34.23","org":"Inc","postal":"1234","timezone":"America/New_York","readme":"https://www.google.com"}"
The value looks to be a string in JSON notation. You'll have to parse it first in order to look up properties on it:
if (o.name === "geo") {
const nestedObj = JSON.parse(o.value);
console.log(nestedObj.ip);
}
You might also consider fixing whatever's serving you that object so that the value is an actual object - if that's possible (it may not be, but it would make the code make a lot more sense).
You can also consider using .find instead of a for loop, to make the code shorter and more elegant:
const json = myarray.find(({ name }) => name === 'geo').value;
const nestedObj = JSON.parse(json);
(if the geo object may not exist, test for undefined first, of course)
It would appear that the value field for the object with the name of "geo" is a JSON string.
Because the value is a string, you won't be able to directly access the ip field without first parsing that string to an object:
for (var i = 0; i < myarray.length; i++) {
var o = myarray[i];
if (o.name === "geo") {
/* "Convert" the JSON string in o.value to the corresponding object */
const valueObject = JSON.parse(o.value);
/* The ip field can now be accessed from the valueObject, parsed from
the JSON string in o.value */
console.log(valueObject.ip);
}
}
I need to create a javascript function which takes in an object of key/value pairs and
a filter function that accepts one argument and returns a Boolean. The function should return an array containing the values from the object for which filter(key) is true.
So I would have a function:
function filterValuesByKeys(obj, filter){
//returns array
}
What I'm mostly stuck on is looping through the object based on the keys, and adding the value to an array if the key matched/satisfied the argument. I can print all of the keys, but I don't know how to get each key individually in a loop.
When looping through a JavaScript object, you want to be careful about what you're trying to accomplish. Using for (var key in obj) will go through all the keys, but also any keys in the object's prototype chain.
One approach to get only the object's "own" properties is using for/in, but then checking
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
//do stuff
}
}
Another option with more traditional looping is to use Object.keys(obj) to get the own keys of obj. You can then do a regular loop through that array or even use forEach on it.
var keys = Object.keys(obj);
keys.forEach(function(key) {
// do stuff
});
There are some other neat ways using native functional Array methods, like reduce. I'll leave that to you.
function customFilter(v) {
return (v.indexOf('c') === 0);
}
function filterValuesByKeys(obj, filter) {
var ary = [],
keys = Object.keys(obj);
keys.forEach(function(k) {
if (filter(k)) {
ary.push(obj[k]);
}
})
return ary;
}
var person = {
name: 'George',
age: 50,
address: '1 center st',
city: 'some city',
state: 'some state',
country: 'USA'
};
var result = filterValuesByKeys(person, customFilter);
console.log(result);
is there a data structure or a pattern in Javascript that can be used for both fast lookup (by key, as with associative arrays) and for ordered looping?
Right, now I am using object literals to store my data but I just disovered that Chrome does not maintain the order when looping over the property names.
Is there a common way to solve this in Javascript?
Thanks for any hints.
Create a data structure yourselves. Store the ordering in an array that is internal to the structure. Store the objects mapped by a key in a regular object. Let's call it OrderedMap which will have a map, an array, and four basic methods.
OrderedMap
map
_array
set(key, value)
get(key)
remove(key)
forEach(fn)
function OrderedMap() {
this.map = {};
this._array = [];
}
When inserting an element, add it to the array at the desired position as well as to the object. Insertion by index or at the end is in O(1).
OrderedMap.prototype.set = function(key, value) {
// key already exists, replace value
if(key in this.map) {
this.map[key] = value;
}
// insert new key and value
else {
this._array.push(key);
this.map[key] = value;
}
};
When deleting an object, remove it from the array and the object. If deleting by a key or a value, complexity is O(n) since you will need to traverse the internal array that maintains ordering. When deleting by index, complexity is O(1) since you have direct access to the value in both the array and the object.
OrderedMap.prototype.remove = function(key) {
var index = this._array.indexOf(key);
if(index == -1) {
throw new Error('key does not exist');
}
this._array.splice(index, 1);
delete this.map[key];
};
Lookups will be in O(1). Retrieve the value by key from the associative array (object).
OrderedMap.prototype.get = function(key) {
return this.map[key];
};
Traversal will be ordered and can use either of the approaches. When ordered traversal is required, create an array with the objects (values only) and return it. Being an array, it would not support keyed access. The other option is to ask the client to provide a callback function that should be applied to each object in the array.
OrderedMap.prototype.forEach = function(f) {
var key, value;
for(var i = 0; i < this._array.length; i++) {
key = this._array[i];
value = this.map[key];
f(key, value);
}
};
See Google's implementation of a LinkedMap from the Closure Library for documentation and source for such a class.
The only instance in which Chrome doesn't maintain the order of keys in an object literal seems to be if the keys are numeric.
var properties = ["damsonplum", "9", "banana", "1", "apple", "cherry", "342"];
var objLiteral = {
damsonplum: new Date(),
"9": "nine",
banana: [1,2,3],
"1": "one",
apple: /.*/,
cherry: {a: 3, b: true},
"342": "three hundred forty-two"
}
function load() {
var literalKeyOrder = [];
for (var key in objLiteral) {
literalKeyOrder.push(key);
}
var incremental = {};
for (var i = 0, prop; prop = properties[i]; i++) {
incremental[prop] = objLiteral[prop];
}
var incrementalKeyOrder = [];
for (var key in incremental) {
incrementalKeyOrder.push(key);
}
alert("Expected order: " + properties.join() +
"\nKey order (literal): " + literalKeyOrder.join() +
"\nKey order (incremental): " + incrementalKeyOrder.join());
}
In Chrome, the above produces: "1,9,342,damsonplum,banana,apple,cherry".
In other browsers, it produces "damsonplum,9,banana,1,apple,cherry,342".
So unless your keys are numeric, I think even in Chrome, you're safe. And if your keys are numeric, maybe just prepend them with a string.
As
has been noted, if your keys are numeric
you can prepend them with a string to preserve order.
var qy = {
_141: '256k AAC',
_22: '720p H.264 192k AAC',
_84: '720p 3D 192k AAC',
_140: '128k AAC'
};
Example
What's the best way to store a key=>value array in javascript, and how can that be looped through?
The key of each element should be a tag, such as {id} or just id and the value should be the numerical value of the id.
It should either be the element of an existing javascript class, or be a global variable which could easily be referenced through the class.
jQuery can be used.
That's just what a JavaScript object is:
var myArray = {id1: 100, id2: 200, "tag with spaces": 300};
myArray.id3 = 400;
myArray["id4"] = 500;
You can loop through it using for..in loop:
for (var key in myArray) {
console.log("key " + key + " has value " + myArray[key]);
}
See also: Working with objects (MDN).
In ECMAScript6 there is also Map (see the browser compatibility table there):
An Object has a prototype, so there are default keys in the map. This could be bypassed by using map = Object.create(null) since ES5, but was seldomly done.
The keys of an Object are Strings and Symbols, where they can be any value for a Map.
You can get the size of a Map easily while you have to manually keep track of size for an Object.
If I understood you correctly:
var hash = {};
hash['bob'] = 123;
hash['joe'] = 456;
var sum = 0;
for (var name in hash) {
sum += hash[name];
}
alert(sum); // 579
You can use Map.
A new data structure introduced in JavaScript ES6.
Alternative to JavaScript Object for storing key/value pairs.
Has useful methods for iteration over the key/value pairs.
var map = new Map();
map.set('name', 'John');
map.set('id', 11);
// Get the full content of the Map
console.log(map); // Map { 'name' => 'John', 'id' => 11 }
Get value of the Map using key
console.log(map.get('name')); // John
console.log(map.get('id')); // 11
Get size of the Map
console.log(map.size); // 2
Check key exists in Map
console.log(map.has('name')); // true
console.log(map.has('age')); // false
Get keys
console.log(map.keys()); // MapIterator { 'name', 'id' }
Get values
console.log(map.values()); // MapIterator { 'John', 11 }
Get elements of the Map
for (let element of map) {
console.log(element);
}
// Output:
// [ 'name', 'John' ]
// [ 'id', 11 ]
Print key value pairs
for (let [key, value] of map) {
console.log(key + " - " + value);
}
// Output:
// name - John
// id - 11
Print only keys of the Map
for (let key of map.keys()) {
console.log(key);
}
// Output:
// name
// id
Print only values of the Map
for (let value of map.values()) {
console.log(value);
}
// Output:
// John
// 11
In javascript a key value array is stored as an object. There are such things as arrays in javascript, but they are also somewhat considered objects still, check this guys answer - Why can I add named properties to an array as if it were an object?
Arrays are typically seen using square bracket syntax, and objects ("key=>value" arrays) using curly bracket syntax, though you can access and set object properties using square bracket syntax as Alexey Romanov has shown.
Arrays in javascript are typically used only with numeric, auto incremented keys, but javascript objects can hold named key value pairs, functions and even other objects as well.
Simple Array eg.
$(document).ready(function(){
var countries = ['Canada','Us','France','Italy'];
console.log('I am from '+countries[0]);
$.each(countries, function(key, value) {
console.log(key, value);
});
});
Output -
0 "Canada"
1 "Us"
2 "France"
3 "Italy"
We see above that we can loop a numerical array using the jQuery.each function and access info outside of the loop using square brackets with numerical keys.
Simple Object (json)
$(document).ready(function(){
var person = {
name: "James",
occupation: "programmer",
height: {
feet: 6,
inches: 1
},
}
console.log("My name is "+person.name+" and I am a "+person.height.feet+" ft "+person.height.inches+" "+person.occupation);
$.each(person, function(key, value) {
console.log(key, value);
});
});
Output -
My name is James and I am a 6 ft 1 programmer
name James
occupation programmer
height Object {feet: 6, inches: 1}
In a language like php this would be considered a multidimensional array with key value pairs, or an array within an array. I'm assuming because you asked about how to loop through a key value array you would want to know how to get an object (key=>value array) like the person object above to have, let's say, more than one person.
Well, now that we know javascript arrays are used typically for numeric indexing and objects more flexibly for associative indexing, we will use them together to create an array of objects that we can loop through, like so -
JSON array (array of objects) -
$(document).ready(function(){
var people = [
{
name: "James",
occupation: "programmer",
height: {
feet: 6,
inches: 1
}
}, {
name: "Peter",
occupation: "designer",
height: {
feet: 4,
inches: 10
}
}, {
name: "Joshua",
occupation: "CEO",
height: {
feet: 5,
inches: 11
}
}
];
console.log("My name is "+people[2].name+" and I am a "+people[2].height.feet+" ft "+people[2].height.inches+" "+people[2].occupation+"\n");
$.each(people, function(key, person) {
console.log("My name is "+person.name+" and I am a "+person.height.feet+" ft "+person.height.inches+" "+person.occupation+"\n");
});
});
Output -
My name is Joshua and I am a 5 ft 11 CEO
My name is James and I am a 6 ft 1 programmer
My name is Peter and I am a 4 ft 10 designer
My name is Joshua and I am a 5 ft 11 CEO
Note that outside the loop I have to use the square bracket syntax with a numeric key because this is now an numerically indexed array of objects, and of course inside the loop the numeric key is implied.
Objects inside an array:
var cars = [
{ "id": 1, brand: "Ferrari" }
, { "id": 2, brand: "Lotus" }
, { "id": 3, brand: "Lamborghini" }
];
Simply do this
var key = "keyOne";
var obj = {};
obj[key] = someValue;
I know its late but it might be helpful for those that want other ways. Another way array key=>values can be stored is by using an array method called map(); (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map) you can use arrow function too
var countries = ['Canada','Us','France','Italy'];
// Arrow Function
countries.map((value, key) => key+ ' : ' + value );
// Anonomous Function
countries.map(function(value, key){
return key + " : " + value;
});