Dynamically spread setState doesn't work on ternary operator spread - javascript

I have a nested state like this:
this.state = {
fields: {
subject: '',
from: {
name: '',
},
},
};
In an onChange function I'm handling updates to these nested values.
I'm trying to build a dynamically spread setState() for deep nests using dot notation.
For instance, with the array: const tree = ['fields','subject'] I'm able to update the subject state value with:
this.setState(prevState => ({
[tree[0]]: {
...prevState[tree[0]],
...(tree[2] ? {
...prevState[tree[1]],
[tree[2]]: value
}
: { [tree[1]]: value })
},
}));
Since the ternary operator is ending on { [tree[1]]: value }
But when my tree array is: const tree = ['fields','from','name'] the state value for fields.from.name is not changing, where it should be resolving to the first part of the ternary operator:
{
...prevState[tree[1]],
[tree[2]]: value
}
Am I missing something?

I've grown to prefer using libraries for these sorts of functions when it otherwise feels like I'm reinventing the wheel. lodash provides a set function (which also supports string paths):
_.set(object, path, value)
var object = { 'a': [{ 'b': { 'c': 3 } }] };
_.set(object, 'a[0].b.c', 4);
console.log(object.a[0].b.c);
// => 4
_.set(object, ['x', '0', 'y', 'z'], 5);
console.log(object.x[0].y.z);
// => 5
You'd also want to use _.cloneDeep(value) because _.set mutates the object.
this.state = {
fields: {
subject: '',
from: { name: '' },
},
};
const tree = ['fields', 'from', 'name']; // or 'fields.from.name'
this.setState(prevState => {
const prevState_ = _.cloneDeep(prevState);
return _.set(prevState_, tree, value);
});

You'll need a loop. For instance:
function update(prevState, tree, value) {
const newState = {};
let obj = newState;
for (let i = 0; i < tree.length; ++i) {
const name = tree[i];
if (i === tree.length - 1) {
obj[name] = value;
} else {
obj = obj[name] = {...prevState[name]};
}
}
return newState;
}
Live Example:
this.state = {
fields: {
subject: '',
from: {
name: '',
},
},
};
function update(prevState, tree, value) {
const newState = {};
let obj = newState;
let prev = prevState;
for (let i = 0; i < tree.length; ++i) {
const name = tree[i];
if (i === tree.length - 1) {
obj[name] = value;
} else {
const nextPrev = prev[name];
obj = obj[name] = {...nextPrev};
prev = nextPrev;
}
}
return newState;
}
const tree = ['fields','from','name']
const value = "updated";
console.log(update(this.state, tree, value));
I'm sure that can be shoehorned into a call to Array#reduce (because any array operation can be), but it wouldn't buy you anything.

Related

How to update an object value in array of objects when the keys are same

I have an Array of objects and one object
const filterArray = [{bestTimeToVisit: 'Before 10am'}, {bestDayToVisit: Monday}]
This values are setting in a reducer and the payload will be like
{bestTimeToVisit: 'After 10am'}
or
{bestDayToVisit: Tuesday}.
So what I need is when I get a payload {bestTimeToVisit: 'After 10am'} and if bestTimeToVisit not in filterList array, then add this value to the filterList array.
And if bestTimeToVisit already in the array with different value, then replace the value of that object with same key
if(filterArray.hasOwnProperty("bestTimeToVisit")) {
filterArray["bestTimeToVisit"] = payload["bestTimeToVisit"];
} else {
filterArray.push({"bestTimeToVisit": payload["bestTimeToVisit"]});
}
I convert the object array into a regular object and then back into an object array. makes things less complicated. I'm making the assumption each object coming back only has one key/value and that order doesnt matter.
const objectArraytoObject = (arr) =>
arr.reduce((acc, item) => {
const key = [Object.keys(item)[0]];
return { ...acc, [key]: item[key] };
}, {});
const newValues = [{ someKey: 'something' }, { bestDayToVisit: 'Tuesday' }];
const filterArray = [
{ bestTimeToVisit: 'Before 10am' },
{ bestDayToVisit: 'Monday' },
];
const newValuesObj = objectArraytoObject(newValues);
const filterObj = objectArraytoObject(filterArray);
const combined = { ...filterObj, ...newValuesObj };
const combinedToArray = Object.keys(combined).map((key) => ({
[key]: combined[key],
}));
console.log(combinedToArray);
Need to iterate over the array and find objects that satisfy for modification or addition if none are found.
function checkReduced(filterrray,valueToCheck="After 10am"){
let isNotFound =true;
for(let timeItem of filterrray) {
if(timeItem.bestTimeToVisit && timeItem.bestTimeToVisit !== valueToCheck) {
timeItem.bestTimeToVisit=valueToCheck;
isNotFound=false;
break;
}
}
if(isNotFound){filterrray.push({bestTimeToVisit:valueToCheck})}
}
const filterArray = [{bestDayToVisit: "Monday"}];
checkReduced(filterArray,"After 9am");//calling the function
const updateOrAdd = (arr, newItem) => {
// get the new item key
const newItemKey = Object.keys(newItem)[0];
// get the object have the same key
const find = arr.find(item => Object.keys(item).includes(newItemKey));
if(find) { // the find object is a reference type
find[newItemKey] = newItem[newItemKey]; // update the value
} else {
arr.push(newItem); // push new item if there is no object have the same key
}
return arr;
}
// tests
updateOrAdd([{ a: 1 }], { b: 2 }) // => [{ a: 1 }, { b: 2 }]
updateOrAdd([{ a: 1 }], { a: 2 }) // => [{ a: 2 }]

Reverse object hierarchy

is there any way to reverse the object in js?
wanna make a function, but struggling hard.
I tried to find the object depth-first and then make a founded amount of iterations for .. in .. inside the object but don't know how to rewrite the new one
const initObject = {
value: 5,
next: {
value: 10,
next: {
value: 15
next: null
}
},
}
//expected result
const newObject = {
value: 15,
next: {
value: 10,
next: {
value: 5,
next: null
}
}
}
You could use a recursive function to collect all the values. Then use reduce to create a nested object from the values:
const initObject = {
value: 5,
next: {
value: 10,
next: {
value: 15,
next: null
}
}
}
const getValues = ({ value, next }) =>
next
? [value, ...getValues(next)]
: [value]
const createObject = values =>
values.reduce((next, value) => ({ value, next }), null)
const output = createObject(getValues(initObject))
console.log(output)
let newObject = null;
for(let o = initObject; o; o = o.next) {
newObject = { value: o.value, next: newObject };
}
What this is doing is looping the initialObject from the outer layer inwards (using the next property to go from layer to layer until we reach null) while constructing the newObject from the inside outwards (each constructed layer will use the previous layer, stored as the current value of newObject, as the next property, hence the initial value of newObject is null), like so:
Note: As pointed out by #jperl in a comment bellow, if the objects have multiple properties (not just value), then simply use a spread syntax to include them all in newObject by replacing:
newObject = { value: o.value, next: newObject };
with:
newObject = { ...o, next: newObject };
Demo:
const initObject = { value: 5, next: { value: 10, next: { value: 15, next: null } } };
let newObject = null;
for(let o = initObject; o; o = o.next) {
newObject = { value: o.value, next: newObject };
}
console.log(newObject);
Here's a function which does the job, the process is:
Build an array of the value values
Reverse the array (using Array.reverse)
Rebuild your object structure with the reversed array values
const reverseObject = (object) => {
const values = [];
let currentObject = object;
while (currentObject && currentObject.value) {
values.push(currentObject.value);
currentObject = currentObject.next;
}
const reversedValues = values.reverse();
const newObject = {};
let currentNewObject = newObject;
for (let i = 0; i < reversedValues.length; i++) {
currentNewObject.value = reversedValues[i];
currentNewObject.next = null;
if (i < reversedValues.length - 1) {
currentNewObject = currentNewObject.next = {};
}
}
return newObject;
};
As Bergi already mentioned in the comments, this can be done using recursion. It is even very concise and readable if you do it as follows.
It iterates through each (nested) node and builds up an inverted object using the nextAccumulator variable.
function invert({value, next}, nextAccumulator = null) {
const node = {value, next: nextAccumulator};
return next ? invert(next, node) : node;
}
const inverted = invert({
value: 5,
next: {
value: 10,
next: {
value: 15,
next: null,
},
},
});
console.log(inverted);
You could take a single function approach with a second paramter for collecting the objects.
const
reverse = ({ value, next: sub }, next = null) => sub
? reverse(sub, { value, next })
: { value, next },
object = { value: 5, next: { value: 10, next: { value: 15, next: null } } },
result = reverse(object);
console.log(result);
You can do that in following steps:
Create a function to get all the values in form of array in order
reverse the array
convert that array back to object using recursion
const newObject = {
value: 15,
next: {
value: 10,
next: {
value: 5,
next: null
}
}
}
function reverse(obj){
let values = [];
values.reverse();
function getValue(tempObj){
values.push(tempObj.value);
if(tempObj.next){
getValue(tempObj.next);
}
}
getValue(obj)
const ob = {};
let i = 0;
function reassignValue(tempObj){
if(i >= values.length){
console.log(i)
return;
}
tempObj.value = values[i];
tempObj.next = {};
i++;
reassignValue(tempObj.next);
}
reassignValue(ob);
return ob
}
console.log(reverse(newObject))
That's a linked list. You'll easily find a reverse LinkedList algorithm on the Internet.
Note that I don't need to convert anything.
Typescript
type LinkedList = {
value: number,
next: LinkedList
} | null
const list: LinkedList = {
value: 5,
next: {
value: 10,
next: {
value: 15,
next: null
}
},
}
const reverseLinkedList = (list: LinkedList) => {
let prev = null;
let current = list
let next = null
while (current != null) {
next = current.next
current.next = prev
prev = current
current = next
}
list = prev
return list
}
console.log(reverseLinkedList(list))
Javascript
const list = {
value: 5,
next: {
value: 10,
next: {
value: 15,
next: null
}
},
}
const reverseLinkedList = (list) => {
let prev = null;
let current = list
let next = null
while (current != null) {
next = current.next
current.next = prev
prev = current
current = next
}
list = prev
return list
}
console.log(reverseLinkedList(list))
You can use recursion:
let initObject={value:5,next:{value:10,next:{value:15,next:null}}};
let flip = (obj,nexts=[]) =>{
nexts.push(obj.value);
if (obj.next){
flip(obj.next,nexts)
}
obj.value = nexts.shift()
return obj;
}
let result = flip(initObject)
console.log(result);
let original = flip(result)
console.log(original)

Different logic if both or only one key present in object?

I have this kind of schema:
const schema = {
actions: {
ident: {
action: (v) => v,
path: 'some.path.key',
},
mul: {
action: (v) => v * 2,
path: 'some.other.path.key',
},
},
};
And a helper function, that takes object with keys present in schema actions, e.g:
const obj = {
ident: 1,
mul: 2,
}
const res = helper(schema, obj);
/* res */
{
some: {
path: {
key: 1,
},
other: {
path: {
key: 4,
}
}
},
}
And construct a new object with a function applied to the value.
Sometimes i need a behavior when both keys present in source object, e.g:
const schema2 = {
actions: {
ident: {
action: (v) => v,
path: 'some.path.key',
},
mul: {
action: (v) => v * 2,
path: 'some.other.path.key',
},
'mul:ident': {
action: (v1, v2) => v1/v2,
path: 'key',
}
},
};
In case like this, i need the result object to be:
const obj = {
ident: 1,
mul: 2,
}
const res = helper(schema, obj);
/* res */
{
key: 2 // 2/1 == 2
}
How can i implement such conditional logic in a good way?
I'd traverse your actions backwards, then delete the keys from the input, and skip the action if the keys are missing:
const input = { ...obj };
const output = {};
// you might want to sort the keys in the desired order first (e.g. by the number of parameters)
for(const [key, { action, path }] of Object.entries(schema.actions).reverse()) {
const keys = key.split(".");
// If one ov the values is missing, another action already consumed it
if(keys.some(key => !(key in input))
continue;
// consume all keys into values
const values = keys.map(key => {
const value = input[key];
delete input[key];
});
// TODO: assign path to output correctly
output[path] = action(...values);
}

How to get from an array of objects all unique values of a property that is an array itself

This answer is already close, and there are some answers how to get unique values in an array (remove duplicates,)though I can't make it work for the case where it is about an array of objects, and the property that should be filtered is an array. Sorry, I am a JS newbie. Thanks for the help.
I have an array of objects like this
const posts = [
post1: {
id: 1,
title: 'One',
tags: ['tagA', 'tagB']
},
post2: {
id: 2,
title: 'Two',
tags: ['tagB', 'tagC']
},
post3: {
id: 3,
title: 'Three',
tags: ['tagB', tagC, tagD]
]
What I would need is an array of all unique tags ... in the case above with an expected output like this:
// [tagA, tagB, tagC, tagD]
EDIT / UPDATE
The key in the array of objects is used to manage the state of the react component... e.g.
constructor(props) {
super(props);
this.state = {
posts: []
};
}
...
updatePost = (key, updatedPost) => {
//1. Take copy of the current this.state.
const posts = {...this.state.texts};
//2. Update that state
posts[key] = updatedPost;
//3. Set that to state
const options = { encrypt: false }
putFile(postsFileName, JSON.stringify(posts), options)
.then(() => {
this.setState({
posts: posts
})
})
};
Assuming that the input is on [ {} , {} ] format:
You can use concat and map to flatten your array. Use new Set to get the unique values.
const posts = [{"id":1,"title":"One","tags":["tagA","tagB"]},{"id":2,"title":"Two","tags":["tagB","tagC"]},{"id":3,"title":"Three","tags":["tagB","tagC","tagD"]}];
var result = [...new Set([].concat(...posts.map(o => o.tags)))];
console.log(result);
If the variable is an object ( {a:{} , b:{} } ) , you can use Object.values to convert the object into an array.
const posts = {"post1":{"id":1,"title":"One","tags":["tagA","tagB"]},"post2":{"id":2,"title":"Two","tags":["tagB","tagC"]},"post3":{"id":3,"title":"Three","tags":["tagB","tagC","tagD"]}}
var result = [...new Set([].concat(...Object.values(posts).map(o => o.tags)))];
console.log(result);
You can reduce your posts and iterate over the tags and push those to the result that you haven't encountered already:
const posts = [
{
id: 1,
title: "One",
tags: ["tagA", "tagB"]
},
{
id: 2,
title: "Two",
tags: ["tagB", "tagC"]
},
{
id: 3,
title: "Three",
tags: ["tagB", "tagC", "tagD"]
}
];
const uniqueTags = posts.reduce((result, post) => {
post.tags.forEach(tag => {
if (!result.includes(tag)) {
result.push(tag);
}
});
return result;
}, []);
console.log(uniqueTags);
This is assuming you know that the array key is always 'tags'.
let filter = {};
let result = [];
posts.forEach(post => {
const tags = post['tags'];
tags.forEach(tag => {
if (!filter.hasOwnProperty(tag)) {
result.push(tag);
filter[tag] = true;
}
});
});
with jquery you can do something similar to this (not Tested):
var results = [];
$.each(myObject, function(key,valueObj){
var check.isArray(obj);
if(check){
alert(key + "/" + valueObj );
/*replace repeat*/
var sorted_check = check.slice().sort(); // You can define the comparing function here.
// JS by default uses a crappy string compare.
// (we use slice to clone the array so the
// original array won't be modified)
for (var i = 0; i < sorted_check.length - 1; i++) {
if (sorted_check[i + 1] == sorted_check[i]) {
results.push(sorted_check[i]);
}
}
}
});
and a good way with indexof:
Array.prototype.unique = function() {
var a = [];
for ( i = 0; i < this.length; i++ ) {
var current = this[i];
if (a.indexOf(current) < 0) a.push(current);
}
this.length = 0;
for ( i = 0; i < a.length; i++ ) {
this.push( a[i] );
}
return this;
}
Array.prototype.unique = function() {
var a = [];
for ( i = 0; i < this.length; i++ ) {
var current = this[i];
if (a.indexOf(current) < 0) a.push(current);
}
return a;
}
And Follow UP:
Array.prototype.unique = function(mutate) {
var unique = this.reduce(function(accum, current) {
if (accum.indexOf(current) < 0) {
accum.push(current);
}
return accum;
}, []);
if (mutate) {
this.length = 0;
for (let i = 0; i < unique.length; ++i) {
this.push(unique[i]);
}
return this;
}
return unique;
}
If you want to use a functional library like Ramda.js you can do this:
const posts = [
{
id: 1,
title: 'One',
tags: ['tagA', 'tagB'],
},
{
id: 2,
title: 'Two',
tags: ['tagB', 'tagC'],
},
{
id: 3,
title: 'Three',
tags: ['tagB', 'tagC', 'tagD'],
},
];
var unique = R.uniq(R.flatten(R.map(R.prop('tags'), posts)))
console.log(unique)
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.25.0/ramda.min.js"></script>

How to convert snake case to camelcase in my app

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;
};

Categories