At present, I do this approach:
var obj = {
sender: {
name: "tech"
}
}
var str = "sender.name".split('.');
console.log( obj[str[0]][str[1]] ); //getting update as 'Tech'
In the above I use obj[str[0]][str[1]] for just 2 step, this is works fine. In case if I received a long node parent and child this approach not going to work.
Instead is there any correct dynamic way to do this?
You can use array#reduce to navigate through each key.
var obj = { sender: { name: "tech" } };
var str = "sender.name".split('.').reduce((r,k) => r[k],obj);
console.log(str);
You can use reduce:
var obj = {
foo: {
bar: {
baz: {
sender: {
name: "tech"
}
}
}
}
}
const props = "foo.bar.baz.sender.name".split('.');
const val = props.reduce((currObj, prop) => currObj[prop], obj);
console.log(val);
You could split the string and reduce the path for the result. The function uses a default object for missing or not given properties.
function getValue(object, path) {
return path
.split('.')
.reduce(function (o, k) { return (o || {})[k]; }, object);
}
var obj = { sender: { name: "tech" } },
str = "sender.name";
console.log(getValue(obj, str));
You should be looking into libraries such as "https://lodash.com/"
https://lodash.com/docs/4.17.10
Use _.get : https://lodash.com/docs/4.17.10#get
You can simply write _.get(obj, 'sender.name', 'default') and you will get the value as you expect
I'm currently learning JavaScript and my teacher asked me to do an exercise that would return an array with all the names of this object:
{
name: 'grandma',
daughter: {
name: 'mother',
daughter: {
name: 'daughter',
daughter: {
name: 'granddaughter'
}
}
}
}
my question is similar to this one but the solution does not work for me because my object does not contain any arrays. The code I have so far:
function toArray(obj) {
const result = [];
for (const prop in obj) {
const value = obj[prop];
if (typeof value === 'object') {
result.push(toArray(value));
}
else {
result.push(value);
}
}
return result;
}
function nameMatrioska(target) {
return toArray(target);
}
which prints out this : [ 'grandma', [ 'mother', [ 'daughter', [Array] ] ] ]
but what my teacher wants is: ['grandma', 'mother', 'daughter', 'granddaughter']
codepen
Obviously you push an array to an array, where all nested children appears as an array.
To solve this problem, you could iterate the array and push only single items to the result set.
A different method is, to use some built-in techniques, which works with an array, and returns a single array without a nested array.
Some methods:
Array#concat, creates a new array. It works with older Javascript versions as well.
result = result.concat(toArray(value));
Array#push with an array and Function#apply for taking an array as parameter list. It works in situ and with older versions of JS.
Array.prototype.push.apply(result, toArray(value));
[].push.apply(result, toArray(value)); // needs extra empty array
Spread syntax ... for spreading an array as parameters. ES6
result.push(...toArray(value));
Spread syntax is a powerful replacement for apply with a greater use. Please the the examples as well.
Finally an example with spread syntax.
function toArray(obj) {
const result = [];
for (const prop in obj) {
const value = obj[prop];
if (value && typeof value === 'object') { // exclude null
result.push(...toArray(value));
// ^^^ spread the array
}
else {
result.push(value);
}
}
return result;
}
function nameMatrioska(target) {
return toArray(target);
}
var object = { name: 'grandma', daughter: { name: 'mother', daughter: { name: 'daughter', daughter: { name: 'granddaughter' } } } };
console.log(nameMatrioska(object));
You need .concat instead of .push. Push adds one item to an array; concat joins two arrays together.
['grandmother'].concat(['mother', 'daughter'])
-> ['grandmother', 'mother', 'daughter']
Unlike push, which modifies the array you call it on, concat creates a new array.
var a1 = [ 'grandmother' ];
a1.push( 'mother' );
console.log( a1 );
-> ['grandmother', 'mother']
var a2 = [ 'steve' ];
var result = a2.concat(['Jesus', 'Pedro']);
console.log( a1 );
-> ['steve']
console.log( result );
-> ['steve', 'Jesus', 'Pedro']
Try this
function toArray(obj) {
var result = "";
for (const prop in obj) {
const value = obj[prop];
if (typeof value === 'object') {
result = result.concat(" " + toArray(value));
}
else {
result = result.concat(value);
}
}
return result;
}
function nameMatrioska(target) {
return toArray(target).split(" ");
}
function toArray(obj) {
var result = [];
for (var prop in obj) {
var value = obj[prop];
if (typeof value === 'object') {
result = result.concat(toArray(value))
} else {
result.push(value);
}
}
return result;
}
function nameMatrioska(target) {
return toArray(target);
}
//USER
var names = {
name: 'grandma',
daughter: {
name: 'mother',
daughter: {
name: 'daughter',
daughter: {
name: 'granddaughter'
}
}
}
};
console.log(nameMatrioska(names));
//Output: ["grandma", "mother", "daughter", "granddaughter"]
You are really close.
You have to flatten your array in your last step.
Tip: In general be careful when checking for type object because e.g. null, undefined are also objects in JavaScript world!
function isObject(value) {
if(value === undefined) return "Undefined";
if(value === null) return "Null";
const string = Object.prototype.toString.call(value);
return string.slice(8, -1);
}
function collectPropertiesRec(object, propertyName) {
const result = [ ];
for(const currentPropertyName in object) {
const value = object[currentPropertyName];
if(isObject(value) === 'Object') {
result.push(collectPropertiesRec(value, propertyName));
}
else if(currentPropertyName === propertyName) {
result.push(value);
}
}
return result;
}
function flattenDeep(arr1) {
return arr1.reduce((acc, val) => Array.isArray(val) ? acc.concat(flattenDeep(val)) : acc.concat(val), [ ]);
}
//USER
const names = {
name: 'grandma',
daughter: {
name: 'mother',
daughter: {
name: 'daughter',
daughter: {
name: 'granddaughter'
}
}
}
};
var result = collectPropertiesRec(names, "name");
alert(flattenDeep(result).join(", "));
I read the property of an object I want to access from a string: level1.level2.property OR level1.property OR ... The names and the nesting may vary. I store the objects in a separate module (workerFunctions).
I know that I can access the objects dynamically with the []notation, e.g.:
var level1="level1";
var property="property";
console.log(workerFunctions[level1][property])
However, I don't know how to construct this "workerFunctions[level1][property]" dynamically from varying input strings, so to produce e.g.:
console.log(workerFunctions[level1][level2][property])
in consequence of the string: level1.level2.property.
Thank you in advance.
You could split the path and use the parts as properties for the given object.
function getValue(o, path) {
return path.split('.').reduce(function (o, k) {
return (o || {})[k];
}, o);
}
var o = { A : { B: { C: { value: 'Brenda' } } } };
console.log(getValue(o, 'A.B.C').value); // Brenda
console.log(getValue(o, 'Z.Y.X')); // undefined
For better use with dots in properties, you could use an array directly to avoid wrong splitting.
function getValue(o, path) {
return path.reduce(function (o, k) {
return (o || {})[k];
}, o);
}
var o = { A : { 'B.C': { value: 'Brenda' } } };
console.log(getValue(o, ['A', 'B.C', 'value'])); // Brenda
console.log(getValue(o, ['Z.Y.X'])); // undefined
This should do it :
const str = 'level1.level2.property';
let value = { //workerFunctions;
level1: {
level2: {
property: 'this is the value'
}
}
};
str.split(/\./).forEach((prop) => {
value = value[prop];
});
console.log(value);
I have a JS object like this:
var tenants = {
'first': {
'name': 'first',
'expired': 1
},
'second': {
'name': 'second'
}
}
And I'd like to delete the 'expired' property of tenant 'first', should I just do this?
delete tenants['first']['expired'];
Note: this question is more specific than the question: How do I remove a property from a JavaScript object?, in that my question focuses on the 'nested' part.
Yes. That would work.
delete tenants['first']['expired']; or delete tenants.first.expired;.
If you are deleting it only because you wanted to exclude it from JSON.stringify(), you can also just set it to undefined, like tenants['first']['expired'] = undefined;
If the property you want to delete is stored in a string, you can use this function
function deletePropertyPath (obj, path) {
if (!obj || !path) {
return;
}
if (typeof path === 'string') {
path = path.split('.');
}
for (var i = 0; i < path.length - 1; i++) {
obj = obj[path[i]];
if (typeof obj === 'undefined') {
return;
}
}
delete obj[path.pop()];
};
Example Usage
var tenants = {
'first': {
'name': 'first',
'expired': 1
},
'second': {
'name': 'second'
}
}
var property = 'first.expired';
deletePropertyPath(tenants, property);
If your app is using lodash, then _.unset is a safe way for deleting nested properties. You can specify nested keys without worrying about their existence.
let games = { 'hitman': [{ 'agent': { 'id': 47 } }] };
_.unset(games, 'hitman[0].agent.id');
_.unset(games, 'hitman[0].muffin.cupcake'); // won't break
further reading: https://lodash.com/docs/4.17.15#unset
I came up with this:
const deleteByPath = (object, path) => {
let currentObject = object
const parts = path.split(".")
const last = parts.pop()
for (const part of parts) {
currentObject = currentObject[part]
if (!currentObject) {
return
}
}
delete currentObject[last]
}
Usage:
deleteByPath({ "a" : { "b" : true }},"a.b")
If you want to delete a property with a particular name in an arbitrarily deep object, I would recommend that you use a battle-tested library. You can use DeepDash, an extension to Lodash.
// Recursively remove any "expired" properties
_.eachDeep(e, (child, prop, parent, ctx):boolean => {
if (prop === 'expired') {
delete parent[prop];
return false; // per docs, this means do not recurse into this child
}
return true;
});
And if you would rather have a new copy (rather than mutating the existing object), DeepDash also has an omitDeep function you can use that will return the new object.
If you have the path of the key separated by ., say first.expired in your case, you can do deleteKey(tenants, 'first.expired'):
const deleteKey = (obj, path) => {
const _obj = JSON.parse(JSON.stringify(obj));
const keys = path.split('.');
keys.reduce((acc, key, index) => {
if (index === keys.length - 1) {
delete acc[key];
return true;
}
return acc[key];
}, _obj);
return _obj;
}
let tenants = {
'first': {
'name': 'first',
'expired': 1
},
'second': {
'name': 'second'
}
};
const PATH_TO_DELETE = 'first.expired';
tenants = deleteKey(tenants, PATH_TO_DELETE);
console.log('DELETE SUCCESSFUL:', tenants);
With modern JS you can simple do it this way:
const tenants = {
first: {
name: 'first',
expired: 1
},
second: {
name: 'second'
}
}
delete tenants?.first?.expired;
delete tenants?.second?.expired;
delete tenants?.third?.expired;
console.log(tenants);
By using optional chaining you're able to safely try to remove nested properties on objects that might not exist.
Check the mdn site to check browser compatibility
NOTE: Optional chaining does also works with braces
I have a very weird issue in my lodash codes
I have something like
data = {
'id':'123',
'employee_name': 'John',
'employee_type': 'new'
}
var newObj = _.mapValues(data, function (value, key) {
var t = _.camelCase(key);
console.log(t) -> shows employeeName and employeeType
return _.camelCase(key);
});
I was expecting my newObj will become
data = {
'id':'123',
'employeeName': 'John',
'employeeType': 'new'
}
after I ran the codes above, it still stays the same as it was like
data = {
'id':'123',
'employee_name': 'John',
'employee_type': 'new'
}
This is super weird and I'm not sure what went wrong. Can someone help me about this? Thanks a lot!
replacing snake_case or kebab-case to camelCase only for string (ES6+):
const snakeToCamel = str =>
str.toLowerCase().replace(/([-_][a-z])/g, group =>
group
.toUpperCase()
.replace('-', '')
.replace('_', '')
);
result:
console.log(snakeToCamel('TO_CAMEL')) //toCamel
console.log(snakeToCamel('to_camel')) //toCamel
console.log(snakeToCamel('TO-CAMEL')) //toCamel
console.log(snakeToCamel('to-camel')) //toCamel
Use _.mapKeys() instead of _.mapValues():
var data = {
'id': '123',
'employee_name': 'John',
'employee_type': 'new'
};
var newObj = _.mapKeys(data, (value, key) => _.camelCase(key));
console.log('newObj: ', newObj);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.2/lodash.min.js"></script>
If you need to ignore the redundant value param, you can use _.rearg() on _.camelCase() to generate a function that takes the 2nd param (the key) instead of the 1st param (the value).
var data = {
'id': '123',
'employee_name': 'John',
'employee_type': 'new'
};
var newObj = _.mapKeys(data, _.rearg(_.camelCase, 1));
console.log('newObj: ', newObj);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.2/lodash.min.js"></script>
You can also easily create your own function for that:
function camelCase(obj) {
var newObj = {};
for (d in obj) {
if (obj.hasOwnProperty(d)) {
newObj[d.replace(/(\_\w)/g, function(k) {
return k[1].toUpperCase();
})] = obj[d];
}
}
return newObj;
}
var data = {
'id': '123',
'employee_name': 'John',
'employee_type': 'new'
}
console.log(camelCase(data));
Here's how to do it in native Javascript...
let data = {
'id':'123',
'employee_name': 'John',
'employee_type': 'new'
}
// #1 simple function which converts a string from snake case to camel case ...
const snakeToCamel = s => s.replace(/(_\w)/g, k => k[1].toUpperCase())
// #2 create new data object with camelCase keys...
data = Object.entries(data).reduce((x,[k,v]) => (x[snakeToCamel(k)]=v) && x, {})
console.log(data)
For my use case I needed (or wanted) a function that would handle any arbitrary json object, including nested objects, arrays, etc. Came up with this, seems to be working so far:
const fromSnakeToCamel = (data) => {
if (_.isArray(data)) {
return _.map(data, fromSnakeToCamel);
}
if (_.isObject(data)) {
return _(data)
.mapKeys((v, k) => _.camelCase(k))
.mapValues((v, k) => fromSnakeToCamel(v))
.value();
}
return data;
}
Note that if it's not an array or an object, I just return the data because I only actually want to convert keys. Anyway, hope this helps someone
These are all good answers, but they did not fit what I needed. I like Ashish's answer because it handles nested objects, but what if there are underscores in the data that you want? So, here is a varient on Bambam's answer to make it recursive, because lodash can sometimes be a pain.
function toCamelCase (obj) {
let rtn = obj
if(!rtn) {
return rtn
} else if (typeof (obj) === 'object') {
if (obj instanceof Array) {
rtn = obj.map(toCamelCase)
} else {
rtn = {}
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
const newKey = key.replace(/(_\w)/g, k => k[1].toUpperCase())
rtn[newKey] = toCamelCase(obj[key])
}
}
}
}
return rtn
}
TypeScript
As always, nobody asked for typescript version, but here it is, please don't beat me ^-^.
Without _, No RegExp
I split functions in two modules but you can keep them outside with proper naming
I put never to mark out that the type is actually correct since TS doesn't always know if it is.
You still can use _ and get code shorter but I wanted to breakdown the process.
module CaseTransform {
export type Snake = Lowercase<`${string}_${string}`>
export type Camel = Capitalize<string> | `${Capitalize<string>}${Capitalize<string>}`
export type SnakeToCamel<S extends string> = S extends `${infer Start}_${infer Rest}` ? `${Start}${Capitalize<SnakeToCamel<Rest>>}` : S
type SnakeToCamel__TEST__ = SnakeToCamel<"my_account_profile"> // myAccountProfile
export function capitalize<S extends string>(string: S): Capitalize<S> {
if (string.length === 0) return "" as never
return (string[0].toUpperCase() + string.slice(1)) as never
}
export function snakeToCamel<S extends string>(string: S): SnakeToCamel<S> {
const [start, ...rest] = string.split("_")
return (start + rest.map(capitalize)) as never
}
const snakeToCamel__TEST__ = snakeToCamel("ASD_asd_asdad_")
}
module ObjectTransform {
export function snakeToCamel<O extends object, K extends keyof O>(object: O): { [P in K as (P extends CaseTransform.Snake ? CaseTransform.SnakeToCamel<P> : P)]: O[P] } {
return Object
.entries(object)
.reduce((result, [key, value]) => ({
...result,
[CaseTransform.snakeToCamel(key)]: value
}), {}) as never
}
}
const sample = {
id: 123,
employee_name: "John",
employee_type: "new",
camelCase: "123",
PascalCase: "123"
}
const __TEST__ = ObjectTransform.snakeToCamel(sample)
Note
If you want all characters (even abbreviations) to be in lowercase, put .toLowercase() after string AND change SnakeToCamel type to
type SnakeToCamel<S extends string> = S extends `${infer Start}_${infer Rest}` ? `${Lowercase<Start>}${Capitalize<SnakeToCamel<Rest>>}` : Lowercase<S>
Easy!
Typings Result
JavaScript Playground
function capitalize(string) {
if (string.length === 0) return ""
return (string[0].toUpperCase() + string.slice(1))
}
function snakeToCamel(string){
const [start, ...rest] = string.split("_")
return (start + rest.map(capitalize).join(""))
}
const snakeToCamel__TEST__ = snakeToCamel("ASD_asd_asdad_")
console.log(snakeToCamel__TEST__)
function objectKeysSnakeToCamel(object) {
return Object
.entries(object)
.reduce((result, [key, value]) => ({
...result,
[snakeToCamel(key)]: value
}), {})
}
const sample = {
id: 123,
employee_name: "John",
employee_type: "new",
camelCase: "123",
PascalCase: "123"
}
const __TEST__ = objectKeysSnakeToCamel(sample)
console.log(__TEST__)
Here is another answer using simple for loop.
var data = {
'id': '123',
'employee_name': 'John',
'employee_type': 'new'
};
var output = {}
for (var key in data) {
output[_.camelCase(key)] = data[key];
}
Try this it will definitely work as expected.
const helpers = {};
helpers.camelize = function(str) {
return str.trim().replace(/[A-Z]+/g, (letter, index) => {
return index == 0 ? letter.toLowerCase() : '_' + letter.toLowerCase();
}).replace(/(.(\_|-|\s)+.)/g, function(subStr) {
return subStr[0]+(subStr[subStr.length-1].toUpperCase());
});
}
helpers.camelizeKeys = function(data) {
const result = {};
for (const [key, val] of Object.entries(data)) {
result[helpers.camelize(key)] = val;
}
return result;
}
helpers.camelizeNestedKeys = function(dataObj) {
return JSON.parse(JSON.stringify(dataObj).trim().replace(/("\w+":)/g, function(keys) {
return keys.replace(/[A-Z]+/g, (letter, index) => {
return index == 0 ? letter.toLowerCase() : '_' + letter.toLowerCase();
}).replace(/(.(\_|-|\s)+.)/g, function(subStr) {
return subStr[0]+(subStr[subStr.length-1].toUpperCase());
});
}));
}
const data = {
'id':'123',
'employee_name': 'John',
'employee_type': 'new'
};
const nestedData = {
'id':'123',
'employee_name': 'John',
'employee_type': 'new',
'exployee_projects': [
{"project_name": "test1", "project_year": 2004},
{"project_name": "test2", "project_year": 2004}
]
};
// Few camelize Examples
const str1 = "banana_orange_apple_mango";
const str2 = "banana-orange-apple-mango";
const str3 = "banana orange apple mango";
const str4 = "BANANA Orange APPLE-mango";
const str5 = "banana 5orange apple #mango";
const str6 = "banana__orange-_apple5-#mango";
console.log(helpers.camelize(str1));
console.log(helpers.camelize(str2));
console.log(helpers.camelize(str3));
console.log(helpers.camelize(str4));
console.log(helpers.camelize(str5));
console.log(helpers.camelize(str6));
console.log("=============================");
// camelize object keys
console.log(helpers.camelizeKeys(data));
console.log("=============================");
// camelize nested object keys
console.log(helpers.camelizeNestedKeys(nestedData));
If you want to convert the nested object, then using lodash can be a bit painful.
I tried using regex, JSON.parse & JSON.stringify
and here is the code for the same
below code returns the new object that is having camel case instead of snake case
//input
var data = {
'id': '123',
'employee_name': 'John',
'employee_type': {'new_name': 'foo'}
};
JSON.parse(JSON.stringify(data).replace(
/(_\w)\w+":/g,
match => match[1].toUpperCase() + match.substring(2)
));
{
'id': '123',
'employeeName': 'John',
'employeeType': {'newName': 'foo'}
}
Based on Abbos Tajimov's answer (and Ali's comment), we could also take advantage of the arguments passed down to the inline function.
const snakeToCamel = str => {
if (!(/[_-]/).test(str)) return str
return str.toLowerCase()
.replace(/([-_])([a-z])/g, (_match, _p1, p2) => p2.toUpperCase())
}
camelCase(str) {
return str
.toLowerCase()
.replace(/([-_][a-z])/g, (ltr) => ltr.toUpperCase())
.replace(/[^a-zA-Z]/g, '')
}
another way
_(data)
.keys()
.map(_.camelCase)
.zipObject(_.values(data))
.value()
I really like Mardok's version with nested objects, only issue is that it converts "null" to {}
here mine:
import _ from 'lodash';
export const toCamelCase: any = (obj: any) => {
let rtn = obj
if (typeof obj === 'object') {
if (obj instanceof Array) {
rtn = obj.map(toCamelCase)
}
else if (_.isEmpty(obj)) {
rtn = null
} else {
rtn = {}
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
const newKey = key.replace(/(_\w)/g, k => k[1].toUpperCase())
rtn[newKey] = toCamelCase(obj[key])
}
}
}
}
return rtn
}
Creates camelized object recursively.
function camelCase(obj) {
const newObj = {};
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
const value = obj[key];
const keyCamel = key.replace(/(\_\w)/g, (match) => match[1].toUpperCase());
const isRecursive = typeof value === 'object';
newObj[keyCamel] = isRecursive ? camelCase(value) : value;
}
}
return newObj;
}
let data = {
id: '123',
employee_name: 'John',
inner: {
employee_type: 'new'
},
}
camelCase(data);
Found in typeorm repo https://github.com/typeorm/typeorm/blob/master/src/util/StringUtils.ts#L8
export function camelCase(str: string, firstCapital: boolean = false): string {
return str.replace(
/^([A-Z])|[\s-_](\w)/g,
function (match, p1, p2, offset) {
if (firstCapital === true && offset === 0) return p1
if (p2) return p2.toUpperCase()
return p1.toLowerCase()
},
)
}
Use npm json-case-handler which will allow you to do this in one line.
It can convert any nested objects
For your case, you can do this :
const jcc = require('json-case-convertor')
const snakeCasedJson = jcc.snakeCaseKeys(yourjsonData)
Just pass the value to input and the result will be camelcase:
const snakeToCamel = input =>
console.log(
input.slice(0, input.indexOf('_')).toLowerCase() +
input[input.indexOf('_') + 1].toUpperCase() +
input.slice(input.indexOf('_') + 2)
);
const inputs = [
'underscore_case',
'first_name',
'Some_Variable',
'calculate_AGE',
'delayed_departure',
'Hello_you',
'hAI_i',
];
for (let input of inputs) {
snakeToCamel(input);
}
This function will recursively convert all snake case keys in the object to camelCase. Including objects within arrays and object within objects.
const convertSnakeCaseToCamelCase = (obj) => {
let newObj = {};
if (typeof(obj) !== 'object') {
return obj;
} else if (Array.isArray(obj)) {
newObj = [];
}
for (const key in obj) {
const childObj = convertSnakeCaseToCamelCase(obj[key]);
if (Array.isArray(obj)) {
newObj.push(childObj);
} else {
const newKey = key.replace(/(\_\w)/g, (k) => k[1].toUpperCase());
newObj[newKey] = childObj;
}
}
return newObj;
};