Convert a nested object/ associative array into a query string using JavaScript - javascript

I want to use JavaScript to convert an object into a query string.
For example, I want to convert:
{
a: 'hello',
b: {
b1: 'my',
b2: 'friend',
b3: {
c: 90
}
}
}
to:
?a=hello&b%5Bb1%5D=my&b%5Bb2%5D=friend&b%5Bb3%5D%5Bc%5D=90
I have found quite a few answers to this here: Flatten a javascript object to pass as querystring , but they don't seem to deal with the issue of associative arrays (or objects within objects).
I found a good answer for JQuery which works fine by using jQuery.param, but i would like an answer using either native JS or Underscore.js.
How can I do this?

you can do this:
let obj = {
a: 'hello',
b: {
b1: 'my',
b2: 'friend',
b3: {
c: 90
}
}
}
function getQueryString(obj, encode) {
function getPathToObj(obj, path = []) {
let result = [];
for (let key in obj) {
if (!obj.hasOwnProperty(key)) return;
//deep copy
let newPath = path.slice();
newPath.push(key);
let everyPath = [];
if (typeof obj[key] === "object") {
everyPath = getPathToObj(obj[key], newPath);
} else {
everyPath.push({
path: newPath,
val: obj[key]
});
}
everyPath.map((item) => result.push(item))
}
return result;
}
function composeQueryString(paths) {
let result = "";
paths.map((item) => {
let pathString = "";
if (item.path.length > 1) {
pathString = item.path.reduce((a, b, index) => {
return a + '['+ b +']';
})
} else {
pathString = item.path[0];
}
if (result) {
pathString = "&" + pathString + '=' + item.val;
} else {
pathString = "?" + pathString + '=' + item.val;
}
result += pathString;
});
return result;
}
const str = composeQueryString(getPathToObj(obj));
return encode === true ? encodeURI(str) : str;
}
console.log(getQueryString(obj, true));
get: ?a=hello&b%5Bb1%5D=my&b%5Bb2%5D=friend&b%5Bb3%5D%5Bc%5D=90

I highly recommend not trying to reinvent existing wheels. Your own implementation is probably going to be much less flexible and way more error-prone (have you thought about encoding the query string parameters correctly, for instance?) Instead, take a look at the query-string module.

With Axios you can easily achieve this:
const instance = axios.create({
url: '/user',
baseUrl: 'https://my-api-server'
});
const config = {
params: {
a: 'hello',
b: {
b1: 'my',
b2: 'friend',
b3: {
c: 90
}
}
}
}
const uri = instance.getUri(config)
document.write(uri)
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
For more information you can visit this answer.
Good Luck...

Related

How to get nested value from within an object using a string without using multiple brackets?

Put differently can I do this somehow: obj['data.users.admins.dashboard[3]']
I could create a simple function and parse string then call the object. But I want to know what's possible first as there would lot of edge case, nontrivial.
above would be same as
obj['data']['users']['amdins']['dashboard'][3]
You can use lodash get https://lodash.com/docs/4.17.15#get
var object = { 'a': [{ 'b': { 'c': 3 } }] };
_.get(object, 'a[0].b.c');
You can do this very easily with some clever usage of split() and reduce():
const obj = { data : { users: { admins: { dashboard: [1, 2, 3, 4] } } } };
const path = 'data.users.admins.dashboard[3]';
const result = path.replace(/\[/g,'.').replace(/\]/g,'').split('.')
.reduce((obj,key) => obj && obj[key], obj);
console.log(result);
How?
Replace array accessors like [3] with just .3 -> data.users.admins.dashboard.3
Split string on periods -> ['data', 'users', 'admins', 'dashboard', '3']
reduce over the array accessing each key if the previous one was valid
Here's the snippet in a helper function:
function getByPath(obj, path) {
return path.replace(/\[/g, '.').replace(/\]/g, '').split('.').reduce((obj, key) => obj && obj[key], obj);
}
//sample usage
console.log(getByPath({thing: [1, 2]}, "thing[0]"))
It is possible by using eval, but eval is EVIL. Use it at your own risk!
const data = { users: { admins: { dashboard: [1, 2, 3, 4] } } };
eval('data.users.admins.dashboard[3]');
You can use Proxy:
const obj = new Proxy({
data: {
users: {
admins: {
dashboard: [10, 21, 31, 41],
}
}
},
}, {
get: function (map, key, receiver) {
try {
return eval(`map.${key}`)
} catch (error) {
return undefined;
};
},
});
obj['data.users.admins.dashboard[3]'] // 41
obj['erter.sdfdsfds.admins.sdfsdf[3]'] // undefined
In normal objects it would throw an error can't read property of undefined == better than regular objects in js :)
Updated:
https://repl.it/repls/HarmlessMessyCommas
const obj = new Proxy({
data: {
users: {
admins: {
dashboard: [10, 21, 31, 41, {
hi: 'I am hi'
}],
}
}
},
}, {
get: function (map, key, receiver) {
try {
let splitedKey = key.split(".");
let current = map;
while (splitedKey.length > 0) {
let newValue = splitedKey.shift();
let openingBracketIndex = newValue.lastIndexOf("[");
let arrIndex = Number(newValue.slice(openingBracketIndex + 1, newValue.length - 1));
let isArr = !isNaN(arrIndex)
if (openingBracketIndex > -1 && newValue[newValue.length - 1] === "]" && isArr) {
let arrName = newValue.slice(0, openingBracketIndex);
current = current[arrName][arrIndex];
} else {
current = current[newValue];
}
}
return current;
} catch (error) {
return undefined;
};
},
});
console.log(obj['data.users.admins.dashboard[3]'] == 41);
console.log(obj['data.users.admins.dashboard[0]'] == 10);
console.log(obj["data.users.admins.dashboard[4].hi"] === "I am hi");
console.log(obj['erter.sdfdsfds.admins.sdfsdf[3]'] === undefined)
It works but not perfectly, especially with nested arrays are not going to work b/c even a human wouldn't know if someone is trying to access a property name or array, ex:
const map = {
"dashboard[1]": 'i am string',
"dashboard": [1, 2],
}
So if someone wrote map.dashboard[1] so it is going to return "2" instead of "i am string" b/c if condition for array is above the else condition (else condition = if not array)
I hope it helps :)

Manipulate JSON String

Bit of a challenge I need to write some javascript which will do the following:
Search through a JSON string for something that looks like this
{"code_coverage_temp":["jacoco","pmd"]}
and then replace it with this
{"jacoco":true,"pmd":true}
It must be dynamic enough to deal with the values in array changing in size
I'm currently trying to find solution myself but any help would be appreciated
{
"server":{
"ip":"111.111.111.111",
"credentials":{
"username":"fwesf",
"password":"fewfew"
}
},
"pipeline":{
"job":{
"name":"gfhd"
},
"repository":{
"name":"github",
"location":"fwqfw",
"credentials":{
"username":"fsfs",
"password":"fdsfdsf"
}
},
"build":{
"language":"java",
"build_tool":"maven"
},
"test":{
"bool":true,
"test_type":"unit",
"testing_framework":"junit5"
},
"static_code_analysis":{
"bool":true,
"code_coverage_temp":[
"pmd",
"jacoco"
]
},
"security_tools":{
"bool":true,
"security_tools_temp":[
"zap"
]
},
"sonarcube":{
"bool":true
}
}
}
added entire json im working with
You can use replace method of the String.
Since number of values in the array can change, you need to parse the matched string so that you can iterate over the array and put all the strings in the array in an object. Finally you need to stringify the object because replace method returns a string. You can parse returned string again to get the object if you need to.
Edit
Since you posted your original json, following code will replace "static_code_analysis":{"bool":true,"code_coverage_temp":["pmd","jacoco"]} with "static_code_analysis":{"bool":true,"cobertura":true,"jacoco":false,"pmd":true }
const jsonStr = '{"server":{"ip":"111.111.111.111","credentials":{"username":"fwesf","password":"fewfew"}},"pipeline":{"job":{"name":"gfhd"},"repository":{"name":"github","location":"fwqfw","credentials":{"username":"fsfs","password":"fdsfdsf"}},"build":{"language":"java","build_tool":"maven"},"test":{"bool":true,"test_type":"unit","testing_framework":"junit5"},"static_code_analysis":{"bool":true,"code_coverage_temp":["pmd","jacoco"]},"security_tools":{"bool":true,"security_tools_temp":["zap"]},"sonarcube":{"bool":true}}}';
const pattern = /"code_coverage_temp":\[("[A-z]+",?\s?)+\]/g;
let res = jsonStr.replace(pattern, match => {
const parsedStr = JSON.parse(`{${match}}`);
let obj = parsedStr.code_coverage_temp
.reduce((acc, curr) => (acc[curr] = true, acc), {});
obj = JSON.stringify(obj);
obj = obj.replace('{', '')
.replace('}', '');
return obj;
});
res = JSON.parse(res);
console.log(res);
It would be much simpler if you just parse the json and then operate on it.
const jsonStr = '{"server":{"ip":"111.111.111.111","credentials":{"username":"fwesf","password":"fewfew"}},"pipeline":{"job":{"name":"gfhd"},"repository":{"name":"github","location":"fwqfw","credentials":{"username":"fsfs","password":"fdsfdsf"}},"build":{"language":"java","build_tool":"maven"},"test":{"bool":true,"test_type":"unit","testing_framework":"junit5"},"static_code_analysis":{"bool":true,"code_coverage_temp":["pmd","jacoco"]},"security_tools":{"bool":true,"security_tools_temp":["zap"]},"sonarcube":{"bool":true}}}';
const parsedJson = JSON.parse(jsonStr);
const obj = parsedJson.pipeline.static_code_analysis;
obj.code_coverage_temp.forEach(str => obj[str] = true);
delete obj.code_coverage_temp;
console.log(parsedJson);
You can try something like this (altough it's naive method):
const test = {
a: ['b', 'c'],
d: {
e: ['d', 'f', 'g'],
h: [],
d: 'value'
}
}
function parse(obj) {
const vals = Object.values(obj)
vals.forEach(v => {
if (Array.isArray(v)) {
v.forEach((o, i) => {
if (typeof o == 'object') {
parse(o)
}
if (typeof o == 'string') {
{
v[i] = {
o: true
}
}
}
return o
})
} else if (typeof v == 'object') {
parse(v)
}
})
}

Javascript hasOwnProperty for chain (nested) of properites? [duplicate]

I have a HUGE collection and I am looking for a property by key someplace inside the collection. What is a reliable way to get a list of references or full paths to all objects containing that key/index? I use jQuery and lodash if it helps and you can forget about infinite pointer recursion, this is a pure JSON response.
fn({ 'a': 1, 'b': 2, 'c': {'d':{'e':7}}}, "d");
// [o.c]
fn({ 'a': 1, 'b': 2, 'c': {'d':{'e':7}}}, "e");
// [o.c.d]
fn({ 'aa': 1, 'bb': 2, 'cc': {'d':{'x':9}}, dd:{'d':{'y':9}}}, 'd');
// [o.cc,o.cc.dd]
fwiw lodash has a _.find function that will find nested objects that are two nests deep, but it seems to fail after that. (e.g. http://codepen.io/anon/pen/bnqyh)
This should do it:
function fn(obj, key) {
if (_.has(obj, key)) // or just (key in obj)
return [obj];
// elegant:
return _.flatten(_.map(obj, function(v) {
return typeof v == "object" ? fn(v, key) : [];
}), true);
// or efficient:
var res = [];
_.forEach(obj, function(v) {
if (typeof v == "object" && (v = fn(v, key)).length)
res.push.apply(res, v);
});
return res;
}
a pure JavaScript solution would look like the following:
function findNested(obj, key, memo) {
var i,
proto = Object.prototype,
ts = proto.toString,
hasOwn = proto.hasOwnProperty.bind(obj);
if ('[object Array]' !== ts.call(memo)) memo = [];
for (i in obj) {
if (hasOwn(i)) {
if (i === key) {
memo.push(obj[i]);
} else if ('[object Array]' === ts.call(obj[i]) || '[object Object]' === ts.call(obj[i])) {
findNested(obj[i], key, memo);
}
}
}
return memo;
}
here's how you'd use this function:
findNested({'aa': 1, 'bb': 2, 'cc': {'d':{'x':9}}, dd:{'d':{'y':9}}}, 'd');
and the result would be:
[{x: 9}, {y: 9}]
this will deep search an array of objects (hay) for a value (needle) then return an array with the results...
search = function(hay, needle, accumulator) {
var accumulator = accumulator || [];
if (typeof hay == 'object') {
for (var i in hay) {
search(hay[i], needle, accumulator) == true ? accumulator.push(hay) : 1;
}
}
return new RegExp(needle).test(hay) || accumulator;
}
If you can write a recursive function in plain JS (or with combination of lodash) that will be the best one (by performance), but if you want skip recursion from your side and want to go for a simple readable code (which may not be best as per performance) then you can use lodash#cloneDeepWith for any purposes where you have to traverse a object recursively.
let findValuesDeepByKey = (obj, key, res = []) => (
_.cloneDeepWith(obj, (v,k) => {k==key && res.push(v)}) && res
)
So, the callback you passes as the 2nd argument of _.cloneDeepWith will recursively traverse all the key/value pairs recursively and all you have to do is the operation you want to do with each. the above code is just a example of your case. Here is a working example:
var object = {
prop1: 'ABC1',
prop2: 'ABC2',
prop3: {
prop4: 'ABC3',
prop5Arr: [{
prop5: 'XYZ'
},
{
prop5: 'ABC4'
},
{
prop6: {
prop6NestedArr: [{
prop1: 'XYZ Nested Arr'
},
{
propFurtherNested: {key100: '100 Value'}
}
]
}
}
]
}
}
let findValuesDeepByKey = (obj, key, res = []) => (
_.cloneDeepWith(obj, (v,k) => {k==key && res.push(v)}) && res
)
console.log(findValuesDeepByKey(object, 'prop1'));
console.log(findValuesDeepByKey(object, 'prop5'));
console.log(findValuesDeepByKey(object, 'key100'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.10/lodash.min.js"></script>
With Deepdash you can pickDeep and then get paths from it, or indexate (build path->value object)
var obj = { 'aa': 1, 'bb': 2, 'cc': {'d':{'x':9}}, dd:{'d':{'y':9}}}
var cherry = _.pickDeep(obj,"d");
console.log(JSON.stringify(cherry));
// {"cc":{"d":{}},"dd":{"d":{}}}
var paths = _.paths(cherry);
console.log(paths);
// ["cc.d", "dd.d"]
paths = _.paths(cherry,{pathFormat:'array'});
console.log(JSON.stringify(paths));
// [["cc","d"],["dd","d"]]
var index = _.indexate(cherry);
console.log(JSON.stringify(index));
// {"cc.d":{},"dd.d":{}}
Here is a Codepen demo
Something like this would work, converting it to an object and recursing down.
function find(jsonStr, searchkey) {
var jsObj = JSON.parse(jsonStr);
var set = [];
function fn(obj, key, path) {
for (var prop in obj) {
if (prop === key) {
set.push(path + "." + prop);
}
if (obj[prop]) {
fn(obj[prop], key, path + "." + prop);
}
}
return set;
}
fn(jsObj, searchkey, "o");
}
Fiddle: jsfiddle
In case you don't see the updated answer from #eugene, this tweak allows for passing a list of Keys to search for!
// Method that will find any "message" in the Apex errors that come back after insert attempts
// Could be a validation rule, or duplicate record, or pagemessage.. who knows!
// Use in your next error toast from a wire or imperative catch path!
// message: JSON.stringify(this.findNested(error, ['message', 'stackTrace'])),
// Testing multiple keys: this.findNested({thing: 0, list: [{message: 'm'}, {stackTrace: 'st'}], message: 'm2'}, ['message', 'stackTrace'])
findNested(obj, keys, memo) {
let i,
proto = Object.prototype,
ts = proto.toString,
hasOwn = proto.hasOwnProperty.bind(obj);
if ('[object Array]' !== ts.call(memo)) memo = [];
for (i in obj) {
if (hasOwn(i)) {
if (keys.includes(i)) {
memo.push(obj[i]);
} else if ('[object Array]' === ts.call(obj[i]) || '[object Object]' === ts.call(obj[i])) {
this.findNested(obj[i], keys, memo);
}
}
}
return memo.length == 0 ? null : memo;
}
Here's how I did it:
function _find( obj, field, results )
{
var tokens = field.split( '.' );
// if this is an array, recursively call for each row in the array
if( obj instanceof Array )
{
obj.forEach( function( row )
{
_find( row, field, results );
} );
}
else
{
// if obj contains the field
if( obj[ tokens[ 0 ] ] !== undefined )
{
// if we're at the end of the dot path
if( tokens.length === 1 )
{
results.push( obj[ tokens[ 0 ] ] );
}
else
{
// keep going down the dot path
_find( obj[ tokens[ 0 ] ], field.substr( field.indexOf( '.' ) + 1 ), results );
}
}
}
}
Testing it with:
var obj = {
document: {
payload: {
items:[
{field1: 123},
{field1: 456}
]
}
}
};
var results = [];
_find(obj.document,'payload.items.field1', results);
console.log(results);
Outputs
[ 123, 456 ]
We use object-scan for data processing tasks. It's pretty awesome once you've wrapped your head around how to use it.
// const objectScan = require('object-scan');
const haystack = { a: { b: { c: 'd' }, e: { f: 'g' } } };
const r = objectScan(['a.*.*'], { joined: true, rtn: 'entry' })(haystack);
console.log(r);
// => [ [ 'a.e.f', 'g' ], [ 'a.b.c', 'd' ] ]
.as-console-wrapper {max-height: 100% !important; top: 0}
<script src="https://bundle.run/object-scan#13.8.0"></script>
Disclaimer: I'm the author of object-scan
There are plenty more examples on the website.
The shortest and simplest solution:
Array.prototype.findpath = function(item,path) {
return this.find(function(f){return item==eval('f.'+path)});
}

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

Convert square brackets notation to object

I have an object containing preformated attribute names of a serialized HTMLFormElement (2-dimensional):
var plain = {
id: 1,
'items[A][Z]': 2,
'items[B]': false,
'items[C][][A]': 1
}
I want to convert the object by creating the respective sub object(s):
var result = {
id: 1,
items: {
A: {Z:2},
B: false,
C: [ {A:1} ]
}
}
As far as I'm aware, this is a common practise - but I can't find more ressources on the subject. How is something like that usually called and what's the best way to convert plain to result?
Edit: I've updated the examples with an Array. This seems to be related and is also supported by the body-parser of express.
You could split the path and reduce the path by walking the given object. If no object exist, create a new property with the name, Later assign the value and delete the splitted property.
var plain = { id: 1, 'items[A][Z]': 2, 'items[B]': false };
Object.keys(plain).forEach(function (k) {
var path = k.replace(/\[/g, '.').replace(/\]/g, '').split('.'),
last = path.pop();
if (path.length) {
path.reduce(function (o, p) {
return o[p] = o[p] || {};
}, plain)[last] = plain[k];
delete plain[k];
}
});
console.log(plain);
ES6
var plain = { id: 1, 'items[A][Z]': 2, 'items[B]': false };
Object.keys(plain).forEach(k => {
var path = k.replace(/\[/g, '.').replace(/\]/g, '').split('.'),
last = path.pop();
if (path.length) {
path.reduce((o, p) => o[p] = o[p] || {}, plain)[last] = plain[k];
delete plain[k];
}
});
console.log(plain);
You could use reduce() and filter() like this.
var plain = {
id: 1,
'items[A][Z]': 2,
'items[B]': false
}
var obj = {}
var result = Object.keys(plain).reduce(function(r, e) {
if (e.match(/\[(.*?)\]/gi)) {
var keys = e.split(/\[(.*?)\]/gi).filter(e => e != '');
keys.reduce(function(a, b, i) {
return (i != keys.length - 1) ? a[b] || (a[b] = {}) : a[b] = plain[e];
}, obj)
} else {
obj[e] = plain[e];
}
return r;
}, obj)
console.log(result)

Categories