Convert a nested object with n levels into array - javascript

I have a nested object, something similar to this:
var obj = {
"prop1": {
"prop1A": "A",
"prop1B": {
"prop1BA": "BA"
},
"prop1C": "C"
}
};
My final goal is to filter this object to specific pre defined keys, according to another schema object, for example:
var filterSchema = {
"prop1":["prop1A", {"prop1B":["prop1BA"]}]
};
(The filter keys are predefined, I can structure this object differently if you have a better idea...
The output should be an array. In our case:
["A","BA"]
I managed to do this using recursion over the object. I was wondering if there is more elegant way for achieving this (tried using jQuery's map/extend with no luck)
EDIT
I know that this is a "N" level problem which should be solved by recursion. The difference here is that I have the pre-defined filter which already have the "N" levels. So I though maybe I can filter the Objet using the filter Array and than convert it into an array.
EDIT2
Thanks you all for the different answers. This is my own solution for the problem (which I was looking for a more elegant one in the beginning):
My solution
var obj = {
"prop1": {
"prop1A": "A",
//"prop1B": {
// "prop1BA": "BA"
//},
"prop1C": "C",
"prop1D": "D",
"prop1E": {"prop1E1": "444"}
},
"prop2": "12345"
};
var schemaObj = {
"prop1": {
"prop1A": "true",
"prop1B": {
"prop1BA": "true"
},
"prop1C": "true"
},
"prop2": "true"
};
var resultsArray = [];
var keys = Object.keys(schemaObj);
for(var i=0;i<keys.length;i++){
if(obj[keys[i]]){
parser(schemaObj[keys[i]], obj[keys[i]]);
}
}
function parser(v,o){
if( typeof v === "string" ){
resultsArray.push(o);
}
else{
var keys2 = Object.keys(v);
for(var j=0;j<keys2.length;j++){
if(o[keys2[j]]){
parser(v[keys2[j]], o[keys2[j]]);
}
}
}
}
console.log(resultsArray);
Just a reminder about the question - I already had the recursion solution. I am looking for a different solution

Javascript has eval that allows you to create new code at runtime. A possible solution is to using recursion only once to create a string that looks like:
code = "[obj.prop1.prop1A, obj.prop1.prop1B.prop1BA]"
then you can create a data conversion function with
f = eval("function(obj){return " + code + "]}")
and use it with f(x) to get your array.
This is also the most efficient solution if you have to extract the data many times.
For example:
function mkFilter(schema) {
var parts = [];
function xtract(s, prefix) {
if (typeof s === "string") {
parts.push(prefix + s);
} else if (s && s.constructor === Array) {
s.forEach(function(x){ xtract(x, prefix); });
} else {
for (var f in s) {
xtract(s[f], prefix + f + ".");
}
}
}
xtract(schema, "obj.");
var code = "(function(obj){ return [" + parts.join(", ") + "]; })";
return eval(code);
}
passing schemaFilter as argument mkFilter will return a function that given an object returns the array; with your input:
console.log(mkFilter(filterSchema)(obj));
displays ['A', 'BA']. Of course this approach makes sense if you need to reuse the same filter many times with different objects.
If the object may have missing parts and you don't want the filter to fail but just undefined values in the array the code generator needs to be changed slightly:
var code = "(function(obj){ return [";
parts.forEach(function(p){
var chain = p.split(".");
var expr = "";
for (var i=0; i<chain.length-1; i++) {
expr += chain.slice(0, i+1).join(".") + " && ";
}
code += expr + p + ",";
});
code += "]; })";
This will create in your example a filter evaluating
(function(obj){
return [obj && obj.prop1 && obj.prop1.prop1A,
obj && obj.prop1 && obj.prop1.prop1B &&
obj.prop1.prop1B.prop1BA,];
})

Use jquery's map function. you can try the example code snippet in the console, but jquery must be included
a = { aa: '123', ab: 'asdasd'}
$.map(a, function(key,val){
return val;
});
// The map creates an array with the value you return from the code block.
// Output is ["aa", "ab"]
for reference see
http://api.jquery.com/jQuery.map/

The following function seems to do what you want:
function filter(obj, schema, out) {
var i, schemaItems, schemaItem, isItemLevel;
if (!obj || !schema) return;
out = out || {values: []};
isItemLevel = Array.isArray(schema);
schemaItems = isItemLevel ? schema : Object.keys(schema);
for (i = 0; i < schemaItems.length; i++) {
schemaItem = schemaItems[i];
if (isItemLevel && typeof schemaItem === "string") {
out.values.push(obj[schemaItem]);
} else if (typeof schemaItem === "object") {
filter(obj, schemaItem, out);
} else if (typeof schemaItem === "string") {
filter(obj[schemaItem], schema[schemaItem], out);
}
}
return out.values;
}
called as
var obj = {
"prop1": {
"prop1A": "A",
"prop1B": {
"prop1BA": "BA"
},
"prop1C": "C"
}
};
var filterSchema = {
"prop1":["prop1A", {"prop1B":["prop1BA"]}]
};
filter(obj, filterSchema);
returns:
["A", "BA"]
Take it with a grain of salt, it is by far not tested well-enough and I certainly don't claim it's the most elegant way of solving this.
It works like this:
traverse the items of the schema (which is either an array or an object)
for each schemaItem
if we are in an array and the schemaItem is a string, output the respective property value of obj
else if the schemaItem is itself an object, recurse, but stay at the same level in obj
else if the schemaItem is a string, recurse, drilling into both obj and schema

Related

How to determine number of dimensions in a JSON object and put all data in an array in javascript?

I am pulling data from an API that is going to return JSON objects of varying lengths and content. It is a mishmash of objects and arrays that can be several levels deep.
I'm trying to get all of the pairs of data and get them into a single, 2D array that I can then put into google sheets using google sheets script.
Here's the code I'm starting with:
function parseTheAPI(rawResponse){
var theparsedJSONdata = JSON.parse(rawResponse)
console.log(theparsedJSONdata)
return theparsedJSONdata
};
Here's the response from the console:
{ '0xabcxyz':
{ products: [ [Object] ],
meta: [ [Object], [Object], [Object] ] } }
There's lots more actual data where it says Object. I understand how to get at individual pieces of data once I know the contents of the object. If the contents of the object change, however, the code would break.
I want to dynamically get at all the various information pairs and use them.
I've looked at SO questions like this and this and tried to figure out if I can just see that data. Here's my attempt at using recursion to figure what's inside:
function logJsonLevelOne(parsedJson){
for(var k in parsedJson){
if(parsedJson[k] instanceof Object){
console.log("parsedJsonOne = "+ parsedJson[k])
var n = Array.isArray(logJsonLevelOne[k])
logJsonLevelOne(parsedJson[k])
} else {
var n = logJsonLevelOne[k] instanceof Array;
console.log(n)
}
}
};
I get some of the data printed, but I don't know how to get to the next level. I don't understand what's coming out of each part of the "if" test. Here's an example of one of the nested objects printed in the console:
{ type: 'claimable',
category: 'claimable',
address: '0x00000000123456lkjlkj',
symbol: 'WMATIC',
decimals: 18,
label: 'Claimable WMATIC',
img: 'networks/polygon/0x0d500b1d8e8ef31e21c99d1db9a6444d3adf1270.png',
price: 1.64,
balance: 0.03567595026086894,
balanceRaw: '35675950260868942',
balanceUSD: 0.05850855842782506 }
How do I figure out what is in the JSON data, at any depth, see if it's an array or object, and put the extracted pairs into a single array?
EDIT:
I'm trying to get this data into two rows in a google sheet. so I can track individual pieces every day. The "price", the "balanceRaw"...
The issue is the user may be participating in different AAVE pools from day to day; i.e. this JSON object might have lots of different information every day.
Here's the raw data from the API:
{"0x0000123456abcdef":{"products":[{"label":"Aave V2","assets":[{"type":"interest-bearing","category":"deposit","address":"0x27F8D03b3a2196956ED754baDc28D73be8830A6e","symbol":"DAI","decimals":18,"label":"DAI in Aave","img":"networks/polygon/0x8f3cf7ad23cd3cadbd9735aff958023239c6a063.png","protocol":"aave","protocolDisplay":"Aave","protocolSymbol":"AAVE","price":0.999254,"apy":0.027310184786005925,"balanceRaw":"2668910745526108687981","balance":2668.910745526109,"balanceUSD":2666.9197381099466},{"type":"claimable","category":"claimable","address":"0x0d500b1d8e8ef31e21c99d1db9a6444d3adf1270","symbol":"WMATIC","decimals":18,"label":"Claimable WMATIC","img":"networks/polygon/0x0d500b1d8e8ef31e21c99d1db9a6444d3adf1270.png","price":1.65,"balance":0.042818503811087726,"balanceRaw":"42818503811087730","balanceUSD":0.07065053128829474}],"meta":[]}],"meta":[{"label":"Total","value":2666.990388641235,"type":"dollar"},{"label":"Assets","value":2666.990388641235,"type":"dollar"},{"label":"Debt","value":0,"type":"dollar"}]}}
EDIT 2
I've tried the this code from this SO question:
function flattenJsonObject (parsedJson){
console.log("test first")
Object.flatten = function(parsedJson) {
var result = {};
function recurse (cur, prop) {
console.log("test")
if (Object(cur) !== cur) {
result[prop] = cur;
} else if (Array.isArray(cur)) {
for(var i=0, l=cur.length; i<l; i++)
recurse(cur[i], prop + "[" + i + "]");
if (l == 0)
result[prop] = [];
} else {
var isEmpty = true;
for (var p in cur) {
isEmpty = false;
recurse(cur[p], prop ? prop+"."+p : p);
}
if (isEmpty && prop)
result[prop] = {};
}
}
recurse(parsedJson, "");
console.log(result)
return result;
}
}
Something is not working because the second and third console.log are not being printed in the console.
Here is a demo of the flattening function. Its starting point is a string of text, which is the response from your API call:
let sourceData = '{"0x0000123456abcdef":{"products":[{"label":"Aave V2","assets":[{"type":"interest-bearing","category":"deposit","address":"0x27F8D03b3a2196956ED754baDc28D73be8830A6e","symbol":"DAI","decimals":18,"label":"DAI in Aave","img":"networks/polygon/0x8f3cf7ad23cd3cadbd9735aff958023239c6a063.png","protocol":"aave","protocolDisplay":"Aave","protocolSymbol":"AAVE","price":0.999254,"apy":0.027310184786005925,"balanceRaw":"2668910745526108687981","balance":2668.910745526109,"balanceUSD":2666.9197381099466},{"type":"claimable","category":"claimable","address":"0x0d500b1d8e8ef31e21c99d1db9a6444d3adf1270","symbol":"WMATIC","decimals":18,"label":"Claimable WMATIC","img":"networks/polygon/0x0d500b1d8e8ef31e21c99d1db9a6444d3adf1270.png","price":1.65,"balance":0.042818503811087726,"balanceRaw":"42818503811087730","balanceUSD":0.07065053128829474}],"meta":[]}],"meta":[{"label":"Total","value":2666.990388641235,"type":"dollar"},{"label":"Assets","value":2666.990388641235,"type":"dollar"},{"label":"Debt","value":0,"type":"dollar"}]}}';
JSON.flatten = function(data) {
var result = {};
function recurse (cur, prop) {
if (Object(cur) !== cur) {
result[prop] = cur;
} else if (Array.isArray(cur)) {
for(var i=0, l=cur.length; i<l; i++)
recurse(cur[i], prop ? prop+"."+i : ""+i);
if (l == 0)
result[prop] = [];
} else {
var isEmpty = true;
for (var p in cur) {
isEmpty = false;
recurse(cur[p], prop ? prop+"."+p : p);
}
if (isEmpty)
result[prop] = {};
}
}
recurse(data, "");
return result;
}
let targetData = JSON.flatten(JSON.parse(sourceData));
console.log(targetData);
The result of running this snippet is data (JS objects) like the following (summarised for brevity):
"0x0000123456abcdef.products.0.assets.0.type": "interest-bearing"
"0x0000123456abcdef.products.0.assets.0.symbol": "DAI"
"0x0000123456abcdef.products.0.assets.0.price": 0.999254
​"0x0000123456abcdef.products.0.assets.0.balanceRaw": "2668910745526108687981"
and:
"0x0000123456abcdef.products.0.assets.1.type": "claimable"
"0x0000123456abcdef.products.0.assets.1.symbol": "WMATIC"
​"0x0000123456abcdef.products.0.assets.1.price": 1.65
​"0x0000123456abcdef.products.0.assets.1.balanceRaw": "42818503811087730"
You can see how the flattening process has built names which show you where in the hierarchy the data came from.
This may not be what you want (they are somewhat cumbersome). So, additional manipulation may be needed to format the names strings into something more friendly.
Here is what I use to get all pairs :
let result = [];
function getData(jsonString){
var data = JSON.parse(jsonString)
getAllPairs(eval(data),'data')
return result
}
function getAllPairs(obj,id) {
const regex = new RegExp('[^0-9]+');
for (let p in obj) {
var newid = (regex.test(p)) ? id + '.' + p : id + '[' + p + ']';
if (obj[p]!=null){
if (typeof obj[p] != 'object' && typeof obj[p] != 'function'){
result.push([p, obj[p]]);
}
if (typeof obj[p] == 'object') {
getAllPairs( obj[p], newid );
}
}
}
}
https://docs.google.com/spreadsheets/d/1_didPNsEswVHWygI3DNL3KGlRJgsmxYNqpOJNFLN-xI/copy

Not able to understand ` recursion and closure scope` on this flattening object

I should suppose to flatten a object, to do this I use this function:
var flatter = function(ob){
var f = {};
for(var i in ob) {
if(typeof ob[i] == 'object') {
var newOb = flatter(ob[i]);
for(var x in newOb) {
f[i+'.'+x] = newOb[x];
}
}else{
f[i] = ob[i];
}
}
return f;
}
works fine. I am getting proper result to applying this object:
var ob = {
"address" : {
"details" : {
"first" : "siva",
"last" : "sankara",
"mam":["mam1","mam2"]
}
}
};
the result is :
reslut : Object {address.details.first: "siva", address.details.last: "sankara", address.details.mam.0: "mam1", address.details.mam.1: "mam2"}
But I am not able to understand the result how i am getting. I understand that, this is oriented with recursion and closure scope - But seaching google I am not get any clear tutorial or article.
Any one help me to understand this my step by step please?
Here is the live demo
Thanks In advance!
function flatter(ob){
'use strict';
var f = {}, //return this
key;
for(key in ob) { //for each key
if (ob.hasOwnProperty(key)) {
if(typeof ob[key] === 'object') { //if value is object
//flatten this object again. Assign result to newOb
var newOb = flatter(ob[key]);
for(var x in newOb) {
f[key + '.' + x] = newOb[x];
}
} else {
f[key] = ob[key];
}
}
}
return f;
}
you can translate this code in something like that
function flatter(ob){
'use strict';
var f = {}, //return this object
key;
for(key in ob) { //for each key
if (ob.hasOwnProperty(key)) {
if(typeof ob[key] === 'object') { //if value is object
var newOb = (function (ob) {
'use strict';
var f = {}, //return this object
key;
for(key in ob) { //for each key
if (ob.hasOwnProperty(key)) {
if(typeof ob[key] === 'object') { //if value is object
var newOb = flatter(ob[key]);
for(var x in newOb) {
f[key + '.' + x] = newOb[x];
}
} else {
f[key] = ob[key];
}
}
}
return f;
}(ob[key]));
for(var x in newOb) {
f[key + '.' + x] = newOb[x];
}
} else {
f[key] = ob[key];
}
}
}
return f;
}
main idea is that every function call can be substituted by body of this function.
Object itself is a recursive structure, because can content objects. If given
{
id: 12345,
name: 'John',
friends: [12346, 75645, 96768]
}
recursion is not needed. Object doesn't contain any objects, so it could be straigtened without additional function call (by the way it is flat). If given
{
id: 12345,
name: {
first: 'John',
last: 'Doe'
},
friends: [12346, 75645, 96768]
}
then object contains object as field. So you can use function flatter where function call is substituted with body of function. If given
{
id: 12345,
name: {
first: 'Helen',
last: {
beforeMarriage: 'Dobsky',
afterMarriage: 'Bobsky'
}
},
friends: [12346, 75645, 96768]
}
then one can't do without 3 function calls. So you can copy body of function three times. But, object can have [infinitely] very deep structure. So number of nested bodies of function is unknown. So, instead of nesting body of function into function recursive call is used.
Recursive function should have at least one exit point to avoid infinite recursion
return f;
in our case. This exit point can be reached because number of fields in object is finite. This is not the only way to solve task. As object looks like tree (a kind of) recursion could be substituted with stack, which keeps complex fields and after processing simple fields return back to stack of objects and treat them in a loop.
Stack implementation. Not beautiful, but works)
function iflatter(input) {
'use strict';
var f = {}, //return this object
key,
stack = [],
ob,
prefix,
name;
stack.push(["", input]);
while (stack.length != 0) {
[prefix, ob] = stack.pop();
for(key in ob) { //for each key
if (ob.hasOwnProperty(key)) {
if (prefix !== "") {
name = prefix + "." + key;
} else {
name = key;
}
if(typeof ob[key] === 'object') {
stack.push([name, ob[key]]);
} else {
f[name] = ob[key];
}
}
}
}
return f;
}
You are passing an Object to flatter(). When the Object has a property that has a value that is an Object itself, it passes that Object to flatter() again, or recursively. See if(typeof ob[i] == 'object')? That means that if ob has property i and its value (gotten as ob[i]) is an Object. Note var flatter = function(){} is equivalent to function flatter(){}.

Convert string with dot notation to JSON

Given a string as dot notation, how would I create an object from that string (checking for already existing properties): eg
var obj = {};
stringToObj('a.b', 'value1', obj);
stringToObj('a.b.c', 'value2', obj);
would produce
{
"a": {
"b": {
"_x": "value1",
"c": {
"_x": "value2"
}
}
}
}
I've looked at this question and this one but neither seems to be sufficient for what Im doing.
Any thoughts?
For those of you who are looking for solution without the _x in the object try this code. A slight modification of the above code (which is brilliant)
stringToObj = function(path,value,obj) {
var parts = path.split("."), part;
var last = parts.pop();
while(part = parts.shift()) {
if( typeof obj[part] != "object") obj[part] = {};
obj = obj[part]; // update "pointer"
}
obj[last] = value;
}
As bonus the above code will work if you want to update parts of an existing object :)
var obj = {a:{b:3}};
stringToObj("a.b",10,obj);
console.log(obj); //result : {a:{b:10}}
You can take advantage of references:
function stringToObj(path,value,obj) {
var parts = path.split("."), part;
while(part = parts.shift()) {
if( typeof obj[part] != "object") obj[part] = {};
obj = obj[part]; // update "pointer"
}
obj["_x"] = value;
}
To expand on both #ilikeopensource and #Niet the Dark Absol answers, I needed to add the support for arrays (multiple instances of the same path) with the possibility for nested objects. My case was more of a hybrid of dot notation and json strings.
function stringToObj(path, value, obj) {
var objValue = value;
try {
objValue = JSON.parse(value);
} catch (e) { } //eat the error, must not be json so carry on... Hack to do a valid JSON check
var parts = path.split("."), part;
var last = parts.pop();
while (part = parts.shift()) {
if (typeof obj[part] != "object")
obj[part] = {};
obj = obj[part];
}
if (obj.hasOwnProperty(last) && obj[last] && obj[last].constructor === Array) {
obj[last].push(objValue);
}
else if (obj.hasOwnProperty(last) && obj[last]) {
var objArray = [];
objArray.push(obj[last])
objArray.push(objValue);
obj[last] = objArray;
}
else {
obj[last] = objValue;
}
}
Thanks guys for the help!
This may be well answered already, but I would like to share my resolution for those who still looking for a different approach.
If you would like to convert string dot notation to a string JSON, here's my approach.
function dotToJson({ notation, inclusive = true, value = null }) {
const fragments = notation.split(".").reverse();
if (!inclusive) {
fragments.pop();
}
console.log(fragments);
return fragments.reduce((json, fragment) => {
if (isNaN(fragment)) {
return `{ "${fragment}": ${json} }`
}
let fill = "";
if (Number(fragment) > 0) {
for (let i = 0; i < fragment; i++) {
fill += "null, "
}
}
return `[${fill}${json}]`;
}, JSON.stringify(value));
};
Attribute
Meaning
notation
Dot notation string
inclusive
Include the root fragment
value
Default value for leaf
You can see the results here, I tested it using Quokka.js
NOTE: Additionally to this, thiss may help to update objects because you can use spread operator with the parsed version of the JSON

How to flatten or combine member names into one list?

For example if I have something like so:
var Constants = {
scope:{
namespaceA: { A_X: "TEST_AX" , A_Y: "TEST_AY" },
namespaceN: { N_X: "TEST_NX" , N_Y: "TEST_NY" }
}
_mapping: [],
getMapping: function(){...}
}
var flattenList = flatten(Constants.scope); //returns ["TEST_AX","TEST_AY","TEST_NX","TEST_NY"]
var anotherWayFlattened = flatten(Constants.scope.namespaceA,Constants.scope.namespaceB); //returns same result as above
EDIT: one way would be to iterate over the scope via for-each loop but I was looking for something more elegent?
DOUBLE EDIT: ok I just whipped something up like so:
var flattenedList = (function(list){
var flatList = []
$.each(list,function(i,items){
for(var p in items) flatList.push(items[p]);
})
return flatList;
})([Constants.scope.namespaceA,Constants.scope.namespaceB]);
but was wondering if we can avoid passing in the particular property and just pass in Constants and search for the list of namespaces
[Constants.scope.namespaceA,Constants.scope.namespaceB]
I'm wondering why you pass the sub-objects explicitly in an array. Why not just pass the whole Constants.scope object?
var flattenedList = (function(obj){
var flatList = []
for (var prop in obj) {
var items = obj[prop];
for (var p in items)
flatList.push(items[p]);
}
return flatList;
})(Constants.scope);
From your comment it looks like you wanted this:
var flattenedList = (function(obj, test){
var flatList = []
for (var prop in obj) {
if (!test(prop))
continue;
var items = obj[prop];
for (var p in items)
flatList.push(items[p]);
}
return flatList;
})(Constants, function(name) {
return name.substr(0, 9) == "namespace";
// or maybe
return /^namespace[A-Z]$/.test(name);
});
if you wanted to recurse to any (non cyclical!) depth, you could do this :
function flattenList(list, accumulator){
accumulator = accumulator || [];
for(var p in list){
if(list.hasOwnProperty(p)) {
if(typeof list[p] === "string") {
accumulator.push(list[p]);
} else if(typeof list[p] === "object") { // this is not a reliable test!
flattenList(list[p], accumulator);
}
}
}
return accumulator;
}
This code makes a number of assumptions - we only have strings at the end of our objects etc. Alternatively, if you know the depth in advance, your current solution can be optimized by using concat :
var flattenedList = (function(list){
return Array.prototype.concat.apply([], list);
})([Constants.scope.namespaceA,Constants.scope.namespaceB]);
Here's an approach that allows for deeper nesting. I know that wasn't part of the goals, but I found it a more interesting problem. :-)
var flatten = (function() {
var toString = Object.prototype.toString, slice = Array.prototype.slice;
var flatten = function(input, output) {
var value;
output = (toString.call(output) == "[object Array]") ? output : [];
for (name in input) {if (input.hasOwnProperty(name)) {
value = input[name];
if (toString.call(value) == "[object Object]") {
flatten(value, output);
} else {
output.push(value);
}
}};
return output;
};
var merge = function(first, second) {
return first.concat(second);
}
return function() {
return slice.call(arguments).map(flatten).reduce(merge);
};
}());
This allows either approach:
flatten(Constants.scope);
flatten(Constants.scope.namespaceA, Constants.scope.namespaceN);
You can pass in as many separate arguments as you like, or one argument. They'll all be searched to arbitrary depths.
For some environments, you might have to shim Array.prototype functions map and reduce.

Access JavaScript property case-insensitively?

Assume I have an object:
var obj = {
foo:"bar",
fizz:"buzz"
};
I need to access a property of that object dynamically like so:
var objSetter = function(prop,val){
obj[prop] = val;
}
No problems there, except for that prop needs to be case insensitive in case the property name is passed into the function as, say, Foo instead of foo.
So how can I point to an object's property by name without regard to case? I would like to avoid iterating the entire object if possible.
Try this:
var myObject = { "mIxeDCaSEKeY": "value" };
var searchKey = 'mixedCaseKey';
var asLowercase = searchKey.toLowerCase();
myObject[Object.keys(myObject).find(key => key.toLowerCase() === asLowercase)];
You can alternatively already provide the searchKey in lowercase.
If you want it as a function:
/**
* #param {Object} object
* #param {string} key
* #return {any} value
*/
function getParameterCaseInsensitive(object, key) {
const asLowercase = key.toLowerCase();
return object[Object.keys(object)
.find(k => k.toLowerCase() === asLowercase)
];
}
If the key can't be found, then it'll return undefined, just like normal.
If you need to support older browsers, then you can use filter instead:
function getParameterCaseInsensitive(object, key) {
const asLowercase = key.toLowercase();
return object[Object.keys(object).filter(function(k) {
return k.toLowerCase() === asLowercase;
})[0]];
}
I suggest using the polyfills for Object.keys() and Array.filter() if you need even older support.
Note: If you want to also check non-enumerable keys, use Object.getOwnPropertyNames() instead of Object.keys().
Nerdy Note: This assumes your Object doesn't have a key undefined (eg: const foo = {[undefined]: 'bar'};). That's just weird.
Compare all the properties of obj with prop.
var objSetter = function(prop,val){
prop = (prop + "").toLowerCase();
for(var p in obj){
if(obj.hasOwnProperty(p) && prop == (p+ "").toLowerCase()){
obj[p] = val;
break;
}
}
}
For this, I prefer using the prototype over a standalone function just for ease of use and expressiveness. I just don't like funneling objects into functions if I don't have to.
Also, while the accepted answer works, I wanted a more comprehensive solution for both getting and setting that would behave as much like the native dot notation or bracket notation as possible.
With that in mind, I created a couple prototype functions for setting/getting an object property without regard to case. You have to remember to be VERY responsible when adding to the Object prototype. Especially when using JQuery and other libraries. Object.defineProperty() with enumerable set to false was used specifically to avoid conflict with JQuery. I also didn't bother naming the functions anything that indicates they are case-insensitive, but you certainly could. I like shorter names.
Here's the getter:
Object.defineProperty(Object.prototype, "getProp", {
value: function (prop) {
var key,self = this;
for (key in self) {
if (key.toLowerCase() == prop.toLowerCase()) {
return self[key];
}
}
},
//this keeps jquery happy
enumerable: false
});
Here's the setter:
Object.defineProperty(Object.prototype, "setProp", {
value: function (prop, val) {
var key,self = this;
var found = false;
if (Object.keys(self).length > 0) {
for (key in self) {
if (key.toLowerCase() == prop.toLowerCase()) {
//set existing property
found = true;
self[key] = val;
break;
}
}
}
if (!found) {
//if the property was not found, create it
self[prop] = val;
}
return val;
},
//this keeps jquery happy
enumerable: false
});
Now that we've created those functions, our code is super clean and concise and just works.
Case-insensitive getting:
var obj = {foo: 'bar', camelCase: 'humpy'}
obj.getProp("FOO"); //returns 'bar'
obj.getProp("fOO"); //returns 'bar'
obj.getProp("CAMELCASE"); //returns 'humpy'
obj.getProp("CamelCase"); //returns 'humpy'
Case-insensitive setting:
var obj = {foo: 'bar', camelCase: 'humpy'}
obj.setProp('CAmelCasE', 'super humpy'); //sets prop 'camelCase' to 'super humpy'
obj.setProp('newProp', 'newval'); //creates prop 'newProp' and sets val to 'newval'
obj.setProp('NewProp', 'anotherval'); //sets prop 'newProp' to 'anotherval'
Yet another variation on those already presented which pushes the iteration down into the Underscore/Lodash findKey function:
var _ = require('underscore');
var getProp = function (obj, name) {
var realName = _.findKey(obj, function (value, key) {
return key.toLowerCase() === name.toLowerCase();
});
return obj[realName];
};
For example:
var obj = { aa: 1, bB: 2, Cc: 3, DD: 4 };
getProp(obj, 'aa'); // 1
getProp(obj, 'AA'); // 1
getProp(obj, 'bb'); // 2
getProp(obj, 'BB'); // 2
getProp(obj, 'cc'); // 3
getProp(obj, 'CC'); // 3
getProp(obj, 'dd'); // 4
getProp(obj, 'DD'); // 4
getProp(obj, 'EE'); // undefined
This answer requires ES6.
const x = { 'aB': 1, 'X-Total-Count': 10, y3: 2 }
console.log(x[Object.keys(x).find(key=>{return key.match(/^ab$/i)})])
console.log(x[Object.keys(x).find(key=>{return key.match(/^x-total-count$/i)})])
console.log(x[Object.keys(x).find(key=>{return key.match(/^y3$/i)})])
It seems to me like a good candidate for Proxy with traps to convert string keys to either upper case or lower case and behaving like a regular object.
This works with either notation: dots or braquets
Here is the code:
'use strict';
function noCasePropObj(obj)
{
var handler =
{
get: function(target, key)
{
//console.log("key: " + key.toString());
if (typeof key == "string")
{
var uKey = key.toUpperCase();
if ((key != uKey) && (key in target))
return target[key];
return target[uKey];
}
return target[key];
},
set: function(target, key, value)
{
if (typeof key == "string")
{
var uKey = key.toUpperCase();
if ((key != uKey) && (key in target))
target[key] = value;
target[uKey] = value;
}
else
target[key] = value;
},
deleteProperty: function(target, key)
{
if (typeof key == "string")
{
var uKey = key.toUpperCase();
if ((key != uKey) && (key in target))
delete target[key];
if (uKey in target)
delete target[uKey];
}
else
delete target[key];
},
};
function checkAtomic(value)
{
if (typeof value == "object")
return new noCasePropObj(value); // recursive call only for Objects
return value;
}
var newObj;
if (typeof obj == "object")
{
newObj = new Proxy({}, handler);
// traverse the Original object converting string keys to upper case
for (var key in obj)
{
if (typeof key == "string")
{
var objKey = key.toUpperCase();
if (!(key in newObj))
newObj[objKey] = checkAtomic(obj[key]);
}
}
}
else if (Array.isArray(obj))
{
// in an array of objects convert to upper case string keys within each row
newObj = new Array();
for (var i = 0; i < obj.length; i++)
newObj[i] = checkAtomic(obj[i]);
}
return newObj; // object with upper cased keys
}
// Use Sample:
var b = {Name: "Enrique", last: "Alamo", AdDrEsS: {Street: "1233 Main Street", CITY: "Somewhere", zip: 33333}};
console.log("Original: " + JSON.stringify(b)); // Original: {"Name":"Enrique","last":"Alamo","AdDrEsS":{"Street":"1233 Main Street","CITY":"Somewhere","zip":33333}}
var t = noCasePropObj(b);
console.log(JSON.stringify(t)); // {"NAME":"Enrique","LAST":"Alamo","ADDRESS":{"STREET":"1233 Main Street","CITY":"Somewhere","ZIP":33333}}
console.log('.NaMe:' + t.NaMe); // .NaMe:Enrique
console.log('["naME"]:' + t["naME"]); // ["naME"]:Enrique
console.log('.ADDreSS["CitY"]:' + t.ADDreSS["CitY"]); // .ADDreSS["CitY"]:Somewhere
console.log('check:' + JSON.stringify(Object.getOwnPropertyNames(t))); // check:["NAME","LAST","ADDRESS"]
console.log('check2:' + JSON.stringify(Object.getOwnPropertyNames(t['AddresS']))); // check2:["STREET","CITY","ZIP"]
You could do this in order to "normalize" prop
var normalizedProp = prop.toLowerCase();
obj[normalizedProp] = val;
const getPropertyNoCase = (obj, prop) => obj[Object.keys(obj).find(key => key.toLowerCase() === prop.toLowerCase() )];
or
const getPropertyNoCase = (obj, prop) => {
const lowerProp = prop.toLowerCase(obj[Object.keys(obj).find(key => key.toLowerCase() === prop.toLowerCase() )];
}
The ES6 example posted by #nilloc is incorrect and will break in use.
Here is a working example:
const x = {'first':5,'X-Total-Count':10,'third':20};
console.log(x[Object.keys(x).reduce((result,key)=>{
if (!result) {
return key.match(/x-total-count/i)
} else {
return result;
}
},null)]);
or better yet, it should return undefined if the key doesn't exist:
const x = {'first':5,'X-Total-Count':10,'third':20};
console.log(x[Object.keys(x).reduce((result,key)=>{
if (!result) {
return key.match(/x-total-count/i) || undefined
} else {
return result;
}
},undefined)]);
One consideration is that the above example will return the last matching key in the object if there are multiple keys that match.
Here is an example with the code made into a function:
/**
* #param {Object} object
* #param {string} key
* #return {string||undefined} value || undefined
*/
function getKeyCase(obj,key) {
const re = new RegExp(key,"i");
return Object.keys(obj).reduce((result,key)=>{
if (!result) {
return key.match(re) || undefined
} else {
return result;
}
},undefined);
const x = {'first':5,'X-Total-Count':10,'third':20};
console.log(x[getKeyCase(x,"x-total-count")]);
Its really sad that the iteration can't be skipped as it seems. For me what is acceptable but may not be for everyone is to shape the object one time via iteration and then use it in regular hashmap fashion.
const hashmap = {
'FOO': 'foo as in function programming',
'bar': 'bar is in baz',
};
const shapedmap = Object.entries(hashmap).reduce(
(acc, [key, val]) => (acc[key.toUpperCase()] = val, acc), {}
);
for (const term of ['foo', 'bar', 'baz']) {
const match = shapedmap[term.toUpperCase()]
match && console.log('awesome, we got the term.', match);
};
Even if it just one time lookup has to be performed, it shouldn't less performant as any other iteration solution since after 1 pass, the lookup speed is constant. (I guess).
This is an old question, but it was the first one I found.
As #ZachSmith says, you can use a Proxy.
Here's some example code:
function lowercase(oldKey) {
// Check that it's a string.
return typeof oldKey === 'string' ? oldKey.toLowerCase() : oldKey;
}
const propertiesMap = new Map(
Object.keys(obj).map(propKey => [lowercase(propKey), obj[propKey]])
);
const caseInsensitiveGetHandler = {
get: function(target, property, receiver) {
return propertiesMap.get(lowercase(property));
}
};
obj = new Proxy(obj, caseInsensitiveGetHandler);
For my use case, I only needed to proxy the object's getter, but you may need to implement more of the Proxy methods.
There is no need for any iteration. Since prop might not be a string, it should be coerced to a string first where appropriate since that's what objects do natively. A simple getter function is:
function objGetter(prop) {
return obj[String(prop).toLowerCase()];
}
If there is a requirement is to restring access to own properties:
function objGetter(prop) {
prop = String(prop).toLowerCase();
if (obj.hasOwnProperty(prop)) {
return obj.prop;
}
}
and a setter:
function objSetter(prop, val) {
obj[String(prop).toLowerCase()] = val;
}
Heres a very simple code to do this
Assuming that data is the array of objects like
data=[{"A":"bc","B":"nn"}]
var data=data.reduce(function(prev, curr) {
var cc = curr; // current value
var K = Object.keys(cc); // get all keys
var n = {};
for (var i = 0; i < K.length; i++) {
var key = K[i];//get hte key
n[key.toLowerCase()] = cc[key] // convert to lowercase and assign
}
prev.push(n) // push to array
return prev;
}, [])
Output will be
data=[{"a":"bc","b":"nn"}]
You might only need to do case-insensitive matching (usually expensive because of object iteration) IF a case-sensitive match (cheap and quick) fails.
Say you have:
var your_object = { "Chicago" : 'hi' , "deTroiT" : 'word' , "atlanta" : 'get r dun' } ;
And you have, for whatever reason, the_value, Detroit:
if( your_object.hasOwnProperty( the_value ) )
{
// do what you need to do here
}
else
{ // since the case-sensitive match did not succeed,
// ... Now try a the more-expensive case-insensitive matching
for( let lvs_prop in your_object )
{ if( the_value.toLowerCase() == lvs_prop.toLowerCase() )
{
// do what you need to do here
break ;
} ;
}
} ;
why would we do it that complicated when we simply can make it all lower case:
var your_object = {
"chickago" : 'hi' ,
"detroit" : 'word',
"atlanta" : 'get r dun',
GetName: function (status) {
return this[status].name;
} };
to call it: your_object.GetName(your_var.toLowerCase());
Another simple way:
function getVal(obj, prop){
var val;
prop = (prop + "").toLowerCase();
for(var p in obj){
if(obj.hasOwnProperty(p) && prop == (p+ "").toLowerCase()){
val = obj[p]
break;
}
}
return val;
}
Use it like this:
var obj = {
foo:"bar",
fizz:"buzz"
};
getVal(obj,"FoO") -> returns "bar"
Here is a nice recursive function that allows you to traverse a javascript object in a case-insensitive way:
let testObject = {'a': {'B': {'cC': [1,2,3]}}}
let testSeq = ['a','b','cc']
function keySequence(o, kseq) {
if(kseq.length==0){ return o; }
let validKeys = Object.keys(o).filter(k=>k.toLowerCase()==kseq[0].toLowerCase());
if(validKeys.length==0) { return `Incorrect Key: ${kseq[0]}` }
return keySequence(o[validKeys[0]], kseq.slice(1))
}
keySequence(testObject, testSeq); //returns [1,2,3]
This will convert everything to lowercase, but in a bind this could help if you are not concerned with retaining case.
var somedata = {
"MixEdCase": 1234
}
var temp = JSON.parse(JSON.stringify(somedata).toLowerCase());
console.log(temp.mixedcase);
// or
console.log(temp["mixedcase"]);
So, you will need to get the object key that matches the case of the existing object, then use this to do your object update.
const obj = {
foo:"bar",
fizz:"buzz"
};
// to get obj.foo or obj.FOO or obj.foO returning "bar"
// create regex expression of case insensitive version of the key string
const regex=passedKey=> new RegExp(`^${passedKey}$`,'gi');
// find the key that matches the string you are passing
const formattedKey=passedKey=>Object.keys(obj).find(key=>regex(passedKey).test(key));
formattedKey('Foo'); // returns foo
formattedKey('FoO'); // returns foo
// consequently you can can use it like wise
obj[formattedKey('Foo')] // returns bar
obj[formattedKey('FoO')] // returns bar
obj[formattedKey('foo')] // returns bar

Categories

Resources