Is it possible to use json parse to change the keys from underscore to camelCase deep nested?

Viewed 401

I have object in json string:

const str = `[{"user_id":"561904e8-6e45-5012-a9d8-e2ff8761acf6","email_addr":"jilov@fake.com","details":[{"city_name":"fake city","country_name":"fake country"}]},{"user_id":"5904003b-452b-535c-9615-94706bf6c66c","email_addr":"sawu@fake.com","details":[{"city_name":"fake city","country_name":"fake country"}]}]`;

I want to parse this string (JSON.parse), but I also want to convert every underscore key to camelCase (in all levels).

Is it possible to use parse and convert in the same function? or I need to do it in separate steps?

5 Answers

Because you asked in the comment how to do that with reviver, here a solution using that method.

But I'm not sure if I should consider that one as a good solution.

It relies on how the traversing for the reviving pass is done and according to 25.5.1 JSON.parse ( text [ , reviver ] ) and 25.5.1.1 InternalizeJSONProperty ( holder, name, reviver ) it should always work.

But I personally wouldn't rely on that (because reviver wasn't intended for that usage) and instead, reimplement the traversing.

this in the reviver callback refers to the container to which key and value belong.

const str = `[{"user_id":"561904e8-6e45-5012-a9d8-e2ff8761acf6","email_addr":"jilov@fake.com","details":[{"city_name":"fake city","country_name":"fake country"}]},{"user_id":"5904003b-452b-535c-9615-94706bf6c66c","email_addr":"sawu@fake.com","details":[{"city_name":"fake city","country_name":"fake country"}]}]`

const snakeToCamel = str => str.replace(/([-_]\w)/g, g => g[1].toUpperCase());

var parsed = JSON.parse(str, function(key, value) {
  const camelCaseKey = snakeToCamel(key)
 
  if (this instanceof Array || camelCaseKey === key) {
    // if this is Array 
    // or key does not change after converted to camel case
    // then just return the value so that the default "reviving" is done
    return value
  } else {
    // if key changes assing value to camel case one and return nothing
    this[camelCaseKey] = value
  }
});

console.dir(parsed)

source of snakeToPascal

I just made a playground that solves that problem. You can check it out here, that was a fun problem.

const str = `[{"user_id":"561904e8-6e45-5012-a9d8-e2ff8761acf6","email_addr":"jilov@fake.com","details":[{"city_name":"fake city","country_name":"fake country"}]},{"user_id":"5904003b-452b-535c-9615-94706bf6c66c","email_addr":"sawu@fake.com","details":[{"city_name":"fake city","country_name":"fake country"}]}]`;
const obj = JSON.parse(str);

function toCamelCase(snake_case) {
  return snake_case.replace(/([-_][a-z])/ig, ($1) => {
    return $1.toUpperCase()
      .replace('-', '')
      .replace('_', '');
  });
}

function transformObject(obj) {
  if (obj && Array.isArray(obj)) {
    return obj.map(e => transformObject(e));
  }
  if (obj && typeof obj === "object") {
    const final = {};
    for (const [key, val] of Object.entries(obj)) {
      final[toCamelCase(key)] = transformObject(val);
    }
    return final;
  }
  return obj;
}
const transformed = transformObject(obj);
console.log(transformed);

Here is something I think should work:

const str = `[{"user_id":"561904e8-6e45-5012-a9d8-e2ff8761acf6","email_addr":"jilov@fake.com","details":[{"city_name":"fake city","country_name":"fake country"}]},{"user_id":"5904003b-452b-535c-9615-94706bf6c66c","email_addr":"sawu@fake.com","details":[{"city_name":"fake city","country_name":"fake country"}]}]`;
let obj = JSON.parse(str);


function snakeToCamel(str) {
    const splitString = str.split('_');
    let result = "";
    splitString.forEach((sstr, iter) => {
        if (iter !== 0) {
            result += sstr.charAt(0).toUpperCase() + sstr.slice(1);
        } else
            result += sstr;

    })
    return result;
}

function convertToCamelCase(target) {
    const keys = Object.keys(target);
    keys.forEach(key => {
        if (typeof target[key] === 'object')
            convertToCamelCase(target[key]);

        const converted = snakeToCamel(key);
        if (key !== converted) {
            target[converted] = target[key];
            delete target[key];
        }
    });
}

convertToCamelCase(obj)
console.log(obj);

A simple Array.reduce() function with a recursive camelCase modifier function will do the trick. This is scalable and works for deeply nested variables as well

const str = `[{"user_id":"561904e8-6e45-5012-a9d8-e2ff8761acf6","email_addr":"jilov@fake.com","details":[{"city_name":"fake city","country_name":"fake country"}]},{"user_id":"5904003b-452b-535c-9615-94706bf6c66c","email_addr":"sawu@fake.com","details":[{"city_name":"fake city","country_name":"fake country"}]}]`;

const getCamelCaseFrom = (a) => {
  return Object.fromEntries(Object.entries(a).map(s => {
    if (Array.isArray(s[1])) s[1] = s[1].map(x => getCamelCaseFrom(x));
    s[0] = s[0].split('_').map((e, i) => i === 0 ? e : e.slice(0, 1).toUpperCase() + e.slice(1)).join('');
    return s
  }))
}
let json = JSON.parse(str).reduce((b, a) => ([...b, getCamelCaseFrom(a)]), [])

console.log(json)

With no regex and by simple reducing you may perhaps do like;

function snake2camel(str){
  var b = false;
  return Array.prototype.reduce.call( str
                                    , (r,c) => c === "_" ? ( b = true
                                                           , r
                                                           )
                                                         : r += (b ? ( b = false
                                                                     , c.toUpperCase()
                                                                     )
                                                                   : c)
                                    , ""
                                    );
}
var str = `[{"user_id":"561904e8-6e45-5012-a9d8-e2ff8761acf6","email_addr":"jilov@fake.com","details":[{"city_name":"fake city","country_name":"fake country"}]},{"user_id":"5904003b-452b-535c-9615-94706bf6c66c","email_addr":"sawu@fake.com","details":[{"city_name":"fake city","country_name":"fake country"}]}]`;

console.log(JSON.parse(snake2camel(str)));
.as-console-wrapper {
  max-height: 100% !important;
}

Related