Javascript: More concise way to reduce/aggregate by key? - javascript

This code gives the expected result, but is there a more concise way to achieve the same result? This is simply a matter of curiosity though.
The goal is to have a map representing the total students in each school, as well as a map representing the total teachers in each school.
// Example data
const studentsMap = {
student123: {
teacher: 'teacher123'
},
student456: {
teacher: 'teacher123'
},
student789: {
teacher: 'badID'
},
student000: {}
};
const teachersMap = {
teacher123: {
school: 'school123'
},
teacher456: {
school: 'school123'
},
teacher789: {
school: 'school456'
}
};
const studentsTotalBySchool = Object.keys(studentsMap).reduce((totals, key) => {
const current = studentsMap[key];
if (!teachersMap[current.teacher] || !teachersMap[current.teacher].school) {
return totals;
}
totals[teachersMap[current.teacher].school] = (totals[teachersMap[current.teacher].school] || 0) + 1;
return totals;
}, {});
const teachersTotalBySchool = Object.keys(teachersMap).reduce((totals, key) => {
const current = teachersMap[key];
totals[current.school] = (totals[current.school] || 0) + 1;
return totals;
}, {});
Is there a way to write this more succinctly without sacrificing too much readability?

You can use Object.entries and destructuring like so:
const studentsTotalBySchool = Object.entries(studentsMap).reduce((totals, [key, { teacher }) => {
if (!teachersMap[teacher] || !teachersMap[teacher].school) return totals;
totals[teachersMap[teacher].school] = (totals[teachersMap[teacher].school] || 0) + 1;
return totals;
}, {});
const teachersTotalBySchool = Object.entries(teachersMap).reduce((totals, [key, { school }) => {
totals[school] = (totals[school] || 0) + 1;
return totals;
}, {});

this will get you the same results with much less code
let schools = {
school123: {
teacher123 : {
students: ["student123", "student456"]
},
teacher456 : {
students: ["student789"]
}
},
school456: {
teacher123 : {
students: ["student123", "student456"]
},
teacher456 : {
students: ["student789"]
}
}
};
function findTotal(school, totalOf){
let accumulated = 0;
switch(totalOf){
case "students":
for(let teachers of Object.keys(schools[school])){
accumulated += schools[school][teachers].students.length;
}
break;
case "teachers":
accumulated = Object.keys(schools[school]).length;
}
return accumulated;
}
console.log(findTotal("school123", "students"))
console.log(findTotal("school123", "teachers"))

Related

Nested Array Iteration for array of objects find all values search filter

Issue Unable to return testcaseid from array to this.filteredArray
able to return header value and all values of array if search word is empty.
help me on this.
help how to iterate testcaseid and header on search input filed.
Array -
PanelList$: any[] =
[
{
"header":"header1",
"data":[
{
"testcaseId":"tz_param",
"description":"tz_param"
},
{
"testcaseId":"tzication",
"description":"tzication"
}
]
},
{
"header":"security",
"data":[
{
"testcaseId":"tz_prompt",
"description":"tz_prompt"
},
{
"testcaseId":"z_Root_CA",
"description":"z_Root_CA"
},
{
"testcaseId":"tz_part1",
"description":"tz_part1"
}
]
}
]
input search code -
<input matInput (keyup)="applyFilter($event.target.value);" autocomplete="off" placeholder="Search Test Cases...">
Filter Function - to search header and testcase id
applyFilter(filterWord) {
let arraycase;
let arraycase1;
const word = filterWord.toString().toLowerCase();
this.filteredArray = this.PanelList$.filter(his => {
if(his.header.toString().toLowerCase() === word) {
console.log('1' + 'header')
return his}
if(his.header.toString().toLowerCase().includes(word)) {
console.log('2' + 'return full array')
return his}
his.data.filter(ids => {
if(ids.testcaseId.toString().toLowerCase() === word) {
console.log('3')
arraycase = [{header: his.header, data: [ids] }]
console.log(arraycase);
return arraycase
} {
return arraycase
}
})
console.log(arraycase1 + 'asdads');
})
Update 1 -
this.PanelList$ = JSON.parse(msg.data);
this.filteredArray = JSON.parse(msg.data);
both this.PanelList$ and this.filteredArray has same array
Final Answer to my question - i am returning the value as i am expected -
applyFilter(filterWord) {
console.log(this.PanelList$);
let arraycase;
const word = filterWord.toString().toLowerCase();
this.filteredArray = this.PanelList$.map((his) => {
if (his.header.toString().toLowerCase() === word) {
return his;
}
if (his.header.toString().toLowerCase().includes(word)) {
return his;
}
arraycase = [];
his.data.filter((ids) => {
if (ids.testcaseId.toString().toLowerCase() === word) {
arraycase = { header: his.header, data: [ids] };
console.log(arraycase);
return arraycase;
}
});
return arraycase;
});
if (this.filteredArray[0].length === 0) {
this.testCaseIdTable = true;
}
}
Since I do not have all of your code I just added some required modifications so that I can test and you can have an idea how to modify your code.
//applyFilter(filterWord) {
let arraycase;
let arraycase1;
PanelList =
[
{
"header":"TestBootNotification_CS",
"data":[
{
"testcaseId":"tc_real_module_param",
"description":"tc_real_module_param"
},
{
"testcaseId":"tc_BootNotification",
"description":"tc_BootNotification"
}
]
},
{
"header":"security",
"data":[
{
"testcaseId":"tc_with_prompt",
"description":"tc_with_prompt"
},
{
"testcaseId":"tc_install_Root_CA",
"description":"tc_install_Root_CA"
},
{
"testcaseId":"tc_install_client_cert_part1",
"description":"tc_install_client_cert_part1"
}
]
}
]
var word = "TestBootNotification_CS".toString().toLowerCase(); //filterWord.toString().toLowerCase();
this.filteredArray =
PanelList.map(
(his) => {
if(his.header.toString().toLowerCase() === word) {
console.log('1' + 'header')
return his}
if(his.header.toString().toLowerCase().includes(word)) {
console.log('2' + 'return full array')
return his}
arraycase = []
his.data.filter(ids => {
if(ids.testcaseId.toString().toLowerCase() === word) {
console.log('3')
// arraycase = [{header: his.header, data: [ids] }]
var obj = {header: his.header, data: [ids] }
arraycase.push( obj )
console.log(arraycase);
return arraycase
} else {
return arraycase
}
})
console.log(arraycase1 + 'asdads')
}
)
console.log( this.filteredArray )

Trying to extract common functionality among these

I have below method where i am using reducer to set these dictionaries "earliestOptionByInitialRevision" and "latestOptionByInitialRevision" inside the reducer and the code is looks like as below
const lookups = optionsInput?.reduce(
(acc, option) => {
const [optionById, earliestOptionByInitialRevision, latestOptionByInitialRevision] = acc;
optionById[option.id] = option;
const isCustomProject = option.initialRevisionId === null || option.initialRevisionId === undefined;
if (
isCustomProject ||
!earliestOptionByInitialRevision[option.initialRevisionId] ||
option.revision < earliestOptionByInitialRevision[option.initialRevisionId].revision
) {
// trying to extract the below into common
//function because in below if condition i have used the same and difference is
// "earliestOptionByInitialRevision"
if (isCustomProject) {
option = {...option, initialRevisionId:'customProjectOption'}
if (!earliestOptionByInitialRevision[option.initialRevisionId])
{
earliestOptionByInitialRevision[option.initialRevisionId] = [option];
} else {
earliestOptionByInitialRevision[option.initialRevisionId].push(option);
}
} else {
earliestOptionByInitialRevision[option.initialRevisionId] = option;
}
}
if (
isCustomProject ||
!latestOptionByInitialRevision[option.initialRevisionId] ||
option.revision > latestOptionByInitialRevision[option.initialRevisionId].revision
) {
// the below if condition same as with above and the difference
// is "latestOptionByInitialRevision"
if (isCustomProject) {
option = {...option, initialRevisionId:'customProjectOption'}
if (!latestOptionByInitialRevision[option.initialRevisionId]) {
latestOptionByInitialRevision[option.initialRevisionId] = [option];
} else {
latestOptionByInitialRevision[option.initialRevisionId].push(option);
}
} else {
latestOptionByInitialRevision[option.initialRevisionId] = option;
}
}
return acc;
},
[{}, {}, {}]
) ?? [{}, {}, {}];
const [optionById, earliestOptionByInitialRevision, latestOptionByInitialRevision] = lookups;
i would like to extract the below common functionality but could not be able to get through on how to achieve the same. Could any one please help on this that would be very grateful to me
if (isCustomProject) {
option = {...option, initialRevisionId:'customProjectOption'}
if (!earliestOptionByInitialRevision[option.initialRevisionId]) {
earliestOptionByInitialRevision[option.initialRevisionId] = [option];
} else {
earliestOptionByInitialRevision[option.initialRevisionId].push(option);
}
} else {
earliestOptionByInitialRevision[option.initialRevisionId] = option;
}
Many thanks in advance
update:
A quick fix could be to extract the common functionality into a function, parameterised by the dict (JavaScript object).
const optionsInput = [{
id: 1,
revision: 2
},
{
id: 2,
revision: 1
},
];
const lookups = optionsInput?.reduce(
(acc, option) => {
const [optionById, earliestOptionByInitialRevision, latestOptionByInitialRevision] = acc;
optionById[option.id] = option;
const isCustomProject = option.initialRevisionId === null || option.initialRevisionId === undefined;
const updateDict = (dict) => {
if (isCustomProject) {
const newOption = {...option, initialRevisionId:'customProjectOption'}
if (!dict[option.initialRevisionId]) {
dict[option.initialRevisionId] = [newOption];
} else {
dict[option.initialRevisionId].push(newOption);
}
} else {
dict[option.initialRevisionId] = option;
}
};
if (isCustomProject || !earliestOptionByInitialRevision[option.initialRevisionId] || option.revision < earliestOptionByInitialRevision[option.initialRevisionId].revision) {
updateDict(earliestOptionByInitialRevision);
}
if (isCustomProject || !latestOptionByInitialRevision[option.initialRevisionId] || option.revision > latestOptionByInitialRevision[option.initialRevisionId].revision) {
updateDict(latestOptionByInitialRevision);
}
return acc;
},
[{}, {}, {}]
) ?? [{}, {}, {}];
console.log(lookups);

How to update async await function when a variable change?

genderPie()
let filter = {};
async function genderPie() {
const d = await getData();
const g = await d.reduce((a, o) => (o.GEN && a.push(o.GEN), a), []);
const gender = Object.keys(g).length;
const m = await d.reduce((a, o) => (o.GEN == 1 && a.push(o.GEN), a), []);
const male = Object.keys(m).length;
const f = await d.reduce((a, o) => (o.GEN == 2 && a.push(o.GEN), a), []);
const female = Object.keys(f).length;
var data = [{
name: 'male',
y: male,
id: 1
}, {
name: 'female',
y: female,
id: 2
}];
chart = new Highcharts.Chart({
plotOptions: {
pie: {
innerSize: '80%',
dataLabels: {
connectorWidth: 0
}
}
},
series: [{
"data": data,
type: 'pie',
animation: false,
point: {
events: {
click: function(event) {
filter.GEN = '' + this.id + '';
}
}
}
}],
"chart": {
"renderTo": "gender"
},
});
}
async function getData() {
buildFilter = (filter) => {
let query = {};
for (let keys in filter) {
if (filter[keys].constructor === Array && filter[keys].length > 0) {
query[keys] = filter[keys];
}
}
return query;
}
//FILTER DATA
//Returns the filtered data
filterData = (dataset, query) => {
const filteredData = dataset.filter((item) => {
for (let key in query) {
if (item[key] === undefined || !query[key].includes(item[key])) {
return false;
}
}
return true;
});
return filteredData;
};
//FETCH JSON
const dataset = [{
"GEN": "2"
}, {
"GEN": "1"
}, {
"GEN": "1"
}, {
"GEN": "2"
},
{
"GEN": "2"
}, {
"GEN": "2"
}, {
"GEN": "2"
}, {
"GEN": "1"
}
]
//BUILD THE FILTER
const query = buildFilter(filter);
const result = filterData(dataset, query);
console.log(result)
return result
}
<script src="https://code.highcharts.com/highcharts.js"></script>
<div id="gender"></div>
does anyone can explain me how to handle the following?
I have two functions that filter data and than I build a chart with Hichart
Each time a user click for example a slice of a pie chart an event is fired and an object is populated.
That object allows me to filter the dataset and redraw the chart
The last thing I'm missing is about to update the filtering functions based on the object to be populated
first I'll do this
async function getData() {
buildFilter = (filter) => {
let query = {};
for (let keys in filter) {
if (filter[keys].constructor === Array && filter[keys].length > 0) {
query[keys] = filter[keys];
}
}
return query;
}
then
filterData = (data, query) => {
const filteredData = data.filter( (item) => {
for (let key in query) {
if (item[key] === undefined || !query[key].includes(item[key])) {
return false;
}
}
return true;
});
return filteredData;
};
const query = buildFilter(filter);
const result = filterData(data, query);
my object is
let filter = {}
when a user click the slice myobject become for example
let filter = {
gen: "1"
}
Take a look at this StackBlitz project.
In getData(), I simplified your filter to this one:
return data.filter(item => {
for (const property of Object.keys(filter)) {
if (item[property] !== filter[property]) {
return false;
}
}
return true;
});
and when a slice is clicked, I call genderPie() again, after updating the filter.
You might want to separate the data request from the filtering, so that the data is downloaded only once, not every time a filter is changed.

How to pick data from array and create a single Object from it?

I want to create a single object from an array of objects. Please refer the code provided.
Here's the input array
let queryArr = [
{
query: {
filter: {
term: {
search: 'complete',
}
}
}
},
{
query: {
notFilter: {
term: {
search: 'failed',
}
}
}
},
{
query: {
bool: {
term: {
search: 'complete',
}
}
}
}
]
The expected output
let oneQuery = {query: {
bool: { ... },
filter: { ... },
notFilter: { ... } // data from respective array object key
}};
The function I wrote
function createQuery(arr){
for(let i = 0; i < arr.length; i++){
if(Object.keys(arr[i].query === 'bool')){
oneQuery.query.bool = arr[i].query.bool;
}
if(Object.keys(arr[i].query === 'filter')){
oneQuery.query.filter = arr[i].query.filter;
}
if(Object.keys(arr[i].query === 'notFilter')){
oneQuery.query.notFilter = arr[i].query.notFilter;
}
}
return oneQuery;
}
createQuery(queryArr);
The output I'm getting:
query: {
bool: { ... },
filter: undefined,
notFilter: undefined
}
I don't get what I'm doing wrong here. A solution using reduce or map will be preferred.
Use Array.map() to get an array with the contents of each query property, then spread into Object.assign() to combine to a single object:
const queryArr = [{"query":{"filter":{"term":{"search":"complete"}}}},{"query":{"notFilter":{"term":{"search":"failed"}}}},{"query":{"bool":{"term":{"search":"complete"}}}}];
const createQuery = (arr) => ({
query: Object.assign({}, ...queryArr.map(({ query }) => query))
});
console.log(createQuery(queryArr));
To fix your code, initialize the query item, and get the 1st key from each item in the array - arr[i].query)[0]:
const queryArr = [{"query":{"filter":{"term":{"search":"complete"}}}},{"query":{"notFilter":{"term":{"search":"failed"}}}},{"query":{"bool":{"term":{"search":"complete"}}}}]
function createQuery(arr){
const oneQuery = { query: {} };
for(let i = 0; i < arr.length; i++){
if(Object.keys(arr[i].query)[0] === 'bool'){
oneQuery.query.bool = arr[i].query.bool;
}
if(Object.keys(arr[i].query)[0] === 'filter'){
oneQuery.query.filter = arr[i].query.filter;
}
if(Object.keys(arr[i].query)[0] === 'notFilter'){
oneQuery.query.notFilter = arr[i].query.notFilter;
}
}
return oneQuery;
}
console.log(createQuery(queryArr));
You problem seems to be this line
Object.keys(arr[i].query === 'filter')
This evaluates to Object.keys(true) or Object.keys(false)
Use reduce
queryArr.reduce( (acc, c) => (
acc[ Object.keys(c.query)[0] ] = Object.values(c.query)[0], //set the first key and value to accumulator
acc ), //return the accumulator
{}); //initialize accumulator to {}

Convert returned JSON Object Properties to (lower first) camelCase

I have JSON returned from an API like so:
Contacts: [{ GivenName: "Matt", FamilyName: "Berry" }]
To keep this consistent with my code style (camelCase - lower case first letter) I want to transform the array to produce the following:
contacts: [{ givenName: "Matt", familyName: "Berry" }]
What's the easiest/best way to do this? Create a new Contact object and iterate over all the contacts in the returned array?
var jsonContacts = json["Contacts"],
contacts= [];
_.each(jsonContacts , function(item){
var contact = new Contact( item.GivenName, item.FamilyName );
contacts.push(contact);
});
or can I map the original array or transform it somehow?
If you would use lodash instead of underscore, this would do:
_.mapKeys(obj, (v, k) => _.camelCase(k))
This would convert both TitleCase and snake_case to camelCase. Note that it is not recursive though.
Here's a reliable, recursive function that will properly camelCase all of a JavaScript object's properties:
function toCamel(o) {
var newO, origKey, newKey, value
if (o instanceof Array) {
return o.map(function(value) {
if (typeof value === "object") {
value = toCamel(value)
}
return value
})
} else {
newO = {}
for (origKey in o) {
if (o.hasOwnProperty(origKey)) {
newKey = (origKey.charAt(0).toLowerCase() + origKey.slice(1) || origKey).toString()
value = o[origKey]
if (value instanceof Array || (value !== null && value.constructor === Object)) {
value = toCamel(value)
}
newO[newKey] = value
}
}
}
return newO
}
Test:
var obj = {
'FirstName': 'John',
'LastName': 'Smith',
'BirthDate': new Date(),
'ArrayTest': ['one', 'TWO', 3],
'ThisKey': {
'This-Sub-Key': 42
}
}
console.log(JSON.stringify(toCamel(obj)))
Output:
{
"firstName":"John",
"lastName":"Smith",
"birthDate":"2017-02-13T19:02:09.708Z",
"arrayTest": [
"one",
"TWO",
3
],
"thisKey":{
"this-Sub-Key":42
}
}
You can do this with this recursive function (with lodash and ES6):
import { camelCase } from 'lodash';
const camelizeKeys = (obj) => {
if (Array.isArray(obj)) {
return obj.map(v => camelizeKeys(v));
} else if (obj != null && obj.constructor === Object) {
return Object.keys(obj).reduce(
(result, key) => ({
...result,
[camelCase(key)]: camelizeKeys(obj[key]),
}),
{},
);
}
return obj;
};
Test:
const obj = {
'FirstName': 'John',
'LastName': 'Smith',
'BirthDate': new Date(),
'ArrayTest': ['one', 'TWO', 3],
'ThisKey': {
'This-Sub-Key': 42
}
}
console.log(JSON.stringify(camelizeKeys(obj)))
Output:
{
"firstName": "John",
"lastName": "Smith",
"birthDate": "2018-05-31T09:03:57.844Z",
"arrayTest":[
"one",
"TWO",
3
],
"thisKey":{
"thisSubKey": 42
}
}
To change a plain object's keys from snake_case to camelCase recursively try the following
(which uses Lodash):
function objectKeysToCamelCase(snake_case_object) {
var camelCaseObject = {};
_.forEach(
snake_case_object,
function(value, key) {
if (_.isPlainObject(value) || _.isArray(value)) { // checks that a value is a plain object or an array - for recursive key conversion
value = objectKeysToCamelCase(value); // recursively update keys of any values that are also objects
}
camelCaseObject[_.camelCase(key)] = value;
}
)
return camelCaseObject;
};
test in this PLUNKER
Note: also works recursively for objects within arrays
Using lodash and ES6, this will replace all keys recursively to camelcase:
const camelCaseKeys = (obj) => {
if (!_.isObject(obj)) {
return obj;
} else if (_.isArray(obj)) {
return obj.map((v) => camelCaseKeys(v));
}
return _.reduce(obj, (r, v, k) => {
return {
...r,
[_.camelCase(k)]: camelCaseKeys(v)
};
}, {});
};
Just use humps
humps.camelize('hello_world');
humps.camelizeKeys(object, options); // will work through entire object
https://www.npmjs.com/package/humps
This is a great use case for axios interceptors
Basically, define a client class and attach a before/after interceptor that converts the request/response data.
export default class Client {
get(url, data, successCB, catchCB) {
return this._perform('get', url, data, successCB, catchCB);
}
post(url, data, successCB, catchCB) {
return this._perform('post', url, data, successCB, catchCB);
}
_perform(method, url, data, successCB, catchCB) {
// https://github.com/axios/axios#interceptors
// Add a response interceptor
axios.interceptors.response.use((response) => {
response.data = toCamelCase(response.data);
return response;
}, (error) => {
error.data = toCamelCase(error.data);
return Promise.reject(error);
});
// Add a request interceptor
axios.interceptors.request.use((config) => {
config.data = toSnakeCase(config.data);
return config;
}, (error) => {
return Promise.reject(error);
});
return axios({
method: method,
url: API_URL + url,
data: data,
headers: {
'Content-Type': 'application/json',
},
}).then(successCB).catch(catchCB)
}
}
Here's a gist with a longer example using React/axios.
there's a nice npm module for this..
https://www.npmjs.com/package/camelcase-keys
npm install camelcase-keys
const camelcaseKeys = require( "camelcase-keys" );
camelcaseKeys( { Contacts: [ { GivenName: "Matt", FamilyName: "Berry" } ] }, { deep: true } );
will return...
{ contacts: [ { givenName: "Matt", familyName: "Berry" } ] }
This solution based on the plain js solution above, uses loadash and Keeps an array if passed as a parameter and Only change the Keys
function camelCaseObject(o) {
let newO, origKey, value
if (o instanceof Array) {
newO = []
for (origKey in o) {
value = o[origKey]
if (typeof value === 'object') {
value = camelCaseObject(value)
}
newO.push(value)
}
} else {
newO = {}
for (origKey in o) {
if (o.hasOwnProperty(origKey)) {
newO[_.camelCase(origKey)] = o[origKey]
}
}
}
return newO
}
// Example
const obj = [
{'my_key': 'value'},
{'Another_Key':'anotherValue'},
{'array_key':
[{'me_too':2}]
}
]
console.log(camelCaseObject(obj))
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.js"></script>
Using lodash, you can do it like this:
export const toCamelCase = obj => {
return _.reduce(obj, (result, value, key) => {
const finalValue = _.isPlainObject(value) || _.isArray(value) ? toCamelCase(value) : value;
return { ...result, [_.camelCase(key)]: finalValue };
}, {});
};
Well I took up the challenge and think I figured it out:
var firstToLower = function(str) {
return str.charAt(0).toLowerCase() + str.slice(1);
};
var firstToUpper = function(str) {
return str.charAt(0).toUpperCase() + str.slice(1);
};
var mapToJsObject = function(o) {
var r = {};
$.map(o, function(item, index) {
r[firstToLower(index)] = o[index];
});
return r;
};
var mapFromJsObject = function(o) {
var r = {};
$.map(o, function(item, index) {
r[firstToUpper(index)] = o[index];
});
return r;
};
// Map to
var contacts = [
{
GivenName: "Matt",
FamilyName: "Berry"
},
{
GivenName: "Josh",
FamilyName: "Berry"
},
{
GivenName: "Thomas",
FamilyName: "Berry"
}
];
var mappedContacts = [];
$.map(contacts, function(item) {
var m = mapToJsObject(item);
mappedContacts.push(m);
});
alert(mappedContacts[0].givenName);
// Map from
var unmappedContacts = [];
$.map(mappedContacts, function(item) {
var m = mapFromJsObject(item);
unmappedContacts.push(m);
});
alert(unmappedContacts[0].GivenName);
Property converter (jsfiddle)
The trick is handling the objects as arrays of object properties.
Here's handy library you might wanna try:
https://www.npmjs.com/package/camelize2
You simply need to install it with npm install --save camelize2 and then
const camelize = require('camelize2')
const response = {
Contacts: [{ GivenName: "Matt", FamilyName:"Berry" }]
}
const camelizedResponse = camelize(response)
Solution similar to #brandonscript, but in more ES6-functional way:
const camelCaseString = str => (
(str.charAt(0).toLowerCase() + str.slice(1) || str).toString()
);
const objectToCamelCase = val => {
if (typeof val != 'object' || val === null) {
return val;
}
if (val instanceof Array) {
return val.map(objectToCamelCase);
}
return Object.keys(val)
.filter(prop => val.hasOwnProperty(prop))
.map(prop => ({[camelCaseString(prop)]: objectToCamelCase(val[prop])}))
.reduce((prev, current) => ({...prev, ...current}))
};
// Example:
let converted = objectToCamelCase({UserId: 1, Hobbies: [{Id: 1, Label: "Read"}], Name: "John Doe"});
console.log(converted)
I needed a generic method that accepted an array or object. This is what I'm using (I borrowed KyorCode's firstToLower() implementation):
function convertKeysToCamelCase(obj) {
if (!obj || typeof obj !== "object") return null;
if (obj instanceof Array) {
return $.map(obj, function(value) {
return convertKeysToCamelCase(value);
});
}
var newObj = {};
$.each(obj, function(key, value) {
key = key.charAt(0).toLowerCase() + key.slice(1);
if (typeof value == "object" && !(value instanceof Array)) {
value = convertKeysToCamelCase(value);
}
newObj[key] = value;
});
return newObj;
};
Example calls:
var contact = { GivenName: "Matt", FamilyName:"Berry" };
console.log(convertKeysToCamelCase(contact));
// logs: Object { givenName="Matt", familyName="Berry"}
console.log(convertKeysToCamelCase([contact]));
// logs: [Object { givenName="Matt", familyName="Berry"}]
console.log(convertKeysToCamelCase("string"));
// logs: null
console.log(contact);
// logs: Object { GivenName="Matt", FamilyName="Berry"}
Took the challenge with lodash and some es6+ features
Here is my implementation with the reduce function.
function deeplyToCamelCase(obj) {
return _.reduce(obj, (camelCaseObj, value, key) => {
const convertedDeepValue = _.isPlainObject(value) || _.isArray(value)
? deeplyToCamelCase(value)
: value;
return { ...camelCaseObj, [_.camelCase(key)] : convertedDeepValue };
}, {});
};
Use lodash ...
function isPrimitive (variable) {
return Object(variable) !== variable
}
function toCamel (variable) {
if (isPrimitive(variable)) {
return variable
}
if (_.isArray(variable)) {
return variable.map(el => toCamel(el))
}
const newObj = {}
_.forOwn(variable, (value, key) => newObj[_.camelCase(key)] = toCamel(value))
return newObj
}
This function loop recursively through the object keys and using lodash returns a new object with every field converted to camelCase. It works also with arrays, nested arrays, nested objects.
function deepCamelCase (obj) {
const c = {}
if (typeof obj !== 'object') return obj
_.mapKeys(obj, (v, k) => {
let w = {}
if (typeof v === 'object') {
if (Array.isArray(v)) {
const k = []
for (const i of v) {
k.push(deepCamelCase(i))
}
} else {
_.mapValues(v, (n, m) => {
if (Array.isArray(n)) {
const k = []
for (const i of n) {
k.push(deepCamelCase(i))
}
w[_.camelCase(m)] = k
} else {
w[_.camelCase(m)] = deepCamelCase(n)
}
})
}
} else {
w = v
}
c[_.camelCase(k)] = w
})
return c
}
Updated code using the reference from https://plnkr.co/edit/jtsRo9yU12geH7fkQ0WL?p=preview
This handles the Objects with array with objects inside it too and so on, by keeping arrays as arrays (which you can iterate over using map)
function snakeToCamelCase(snake_case_object){
var camelCaseObject;
if (isPlainObject(snake_case_object)) {
camelCaseObject = {};
}else if(isArray(snake_case_object)){
camelCaseObject = [];
}
forEach(
snake_case_object,
function(value, key) {
if (isPlainObject(value) || isArray(value)) {
value = snakeToCamelCase(value);
}
if (isPlainObject(camelCaseObject)) {
camelCaseObject[camelCase(key)] = value;
}else if(isArray(camelCaseObject)){
camelCaseObject.push(value);
}
}
)
return camelCaseObject;
}
This is my take; more readable and with less nesting than brandoncode's implementation, and with more room for handling edge cases like Date (which isn't handled, by the way) or null:
function convertPropertiesToCamelCase(instance) {
if (instance instanceof Array) {
var result = [];
for (var i = 0; i < instance.length; i++) {
result[i] = convertPropertiesToCamelCase(instance[i]);
}
return result;
}
if (typeof instance != 'object') {
return instance;
}
var result = {};
for (var key in instance) {
if (!instance.hasOwnProperty(key)) {
continue;
}
result[key.charAt(0).toLowerCase() + key.substring(1)] = convertPropertiesToCamelCase(instance[key]);
}
return result;
}
Building on goredwards answer (which didn't handle the array fields correctly)
function objectKeysToCamelCase(snake_case_object) {
let camelCaseObject = {}
_.forEach(
snake_case_object,
function(value, key) {
if (_.isPlainObject(value)) {
value = objectKeysToCamelCase(value)
} else if (_.isArray(value)) {
value = value.map(v => _.isPlainObject(v) ? objectKeysToCamelCase(v) : v)
}
camelCaseObject[_.camelCase(key)] = value
},
)
return camelCaseObject
}
here is code I found for it, not fully tested though, but worth sharing.
It is far more readable than other answers, not sure about performance.
test it http://jsfiddle.net/ms734bqn/1/
const toCamel = (s) => {
return s.replace(/([-_][a-z])/ig, ($1) => {
return $1.toUpperCase()
.replace('-', '')
.replace('_', '');
});
};
const isArray = function (a) {
return Array.isArray(a);
};
const isObject = function (o) {
return o === Object(o) && !isArray(o) && typeof o !== 'function';
};
const keysToCamel = function (o) {
if (isObject(o)) {
const n = {};
Object.keys(o)
.forEach((k) => {
n[toCamel(k)] = keysToCamel(o[k]);
});
return n;
} else if (isArray(o)) {
return o.map((i) => {
return keysToCamel(i);
});
}
return o;
};
Pure JavaScript, shoud work fine
function convertKeysToCamelCase(object) {
if(object === undefined || object === null || typeof object !== "object") {
return object;
} else {
if(Array.isArray(object)) {
return object.map(item => convertKeysToCamelCase(item));
} else {
return Object.entries(object).reduce((result, [key, value]) => {
result[key.charAt(0).toLowerCase() + key.slice(1)] = convertKeysToCamelCase(value);
return result;
}, {});
}
}
}
you can do this simply by using json-case-convertor
const jcc = require('json-case-convertor')
const jsonData = ''//you json data to convert
const camelCasedJson = jcc.camelCaseKeys(jsonData) //Convert all the keys of object to snake case
This will handle all cascaded object as well
Convert object keys to camelCase with deep.
import _ from 'lodash';
export function objectKeysToCamelCase(entity) {
if (!_.isObject(entity)) return entity;
let result;
result = _.mapKeys(entity, (value, key) => _.camelCase(key));
result = _.mapValues(result, (value) => objectKeysToCamelCase(value));
return result;
}

Categories