5

I have a dictionary something like this:

{
     'firstName': 'abc',
     'lastName': 'xyz',
     'favoriteMovies': ['Star Wars', 'The lone ranger'],
     'favoriteCountries': [
          {'country': 'China', 'capitalCity': 'Beiging'},
          {'country': 'India', 'capitalCity': 'New Delhi'}
     ]
}

I want to convert it to snake_case like the following

{
    'first_name': 'abc',
    'last_name': 'xyz',
    'favorite_movies': ['Star Wars', 'The lone ranger'],
    'favorite_countries': [
        {'country': 'China', 'capital_city': 'Beiging'},
        {'country': 'India', 'capital_city': 'New Delhi'}
     ]
}  

The dictionary may be of any length depth.

My current solution is

import re

def convert_snake_case_to_camel_case(data):
    required_dict = {}

    for key, value in data.items():
        if type(value) == str:
            new_key = re.sub("([a-z0-9])([A-Z])", r"\1_\2", key).lower()
            required_dict[new_key] = value
        elif type(value) == list and all(list(map(lambda _: isinstance(_, str), value))):
            new_key = re.sub("([a-z0-9])([A-Z])", r"\1_\2", key).lower()
            required_dict[new_key] = value
        elif type(value) == list and all(list(map(lambda _: isinstance(_, dict), value))):
            new_key = re.sub("([a-z0-9])([A-Z])", r"\1_\2", key).lower()
            required_dict[new_key] = list(filter(convert_snake_case_to_camel_case, value))
    return required_dict

But I m not getting the expected result for the nested data.

mkrieger1
  • 19,194
  • 5
  • 54
  • 65
udeep shrestha
  • 912
  • 11
  • 11
  • 5
    What have you tried so far? Can you share your code snippet? Please go through the stackoverflow guidelines on how to ask a questions, if you are unaware of it. Link : http://stackoverflow.com/help/minimal-reproducible-example – High-Octane Feb 10 '20 at 10:20
  • Based on your question it looks like you want to achieve the opposite of what you say.. upper one is camelCase lower one is snake_case. – Michael K Feb 10 '20 at 10:23
  • @High-Octane, I have done it manually but feels wrong since the data i m working on is dynamic – udeep shrestha Feb 10 '20 at 10:26
  • My bad. I was using filter function while i should be using map function. Thank you guys – udeep shrestha Feb 10 '20 at 11:37

5 Answers5

9

Short recursive version:

import re
def to_snake(s):
  return re.sub('([A-Z]\w+$)', '_\\1', s).lower()

def t_dict(d):
   if isinstance(d, list):
      return [t_dict(i) if isinstance(i, (dict, list)) else i for i in d]
   return {to_snake(a):t_dict(b) if isinstance(b, (dict, list)) else b for a, b in d.items()}

data = {'firstName': 'abc', 'lastName': 'xyz', 'favoriteMovies': ['Star Wars', 'The lone ranger'], 'favoriteCountries': [{'country': 'China', 'capitalCity': 'Beiging'}, {'country': 'India', 'capitalCity': 'New Delhi'}]} 
print(t_dict(data))

Output:

{'first_name': 'abc', 'last_name': 'xyz', 
 'favorite_movies': ['Star Wars', 'The lone ranger'], 
 'favorite_countries': [
    {'country': 'China', 'capital_city': 'Beiging'}, 
    {'country': 'India', 'capital_city': 'New Delhi'}
  ]
}
Ajax1234
  • 69,937
  • 8
  • 61
  • 102
  • This should be the selected answer, however I am using the following regex pattern instead `re.compile(r'(?<!^)(?=[A-Z])')`. – Jenobi Mar 02 '23 at 17:34
5

You could use regex for this.

def camel_to_snake(str):
   return re.sub(r'(?<!^)(?=[A-Z])', '_', str).lower()

Then build another recursive function which converts all the dictionary keys by using the above function.

Mihai Alexandru-Ionut
  • 47,092
  • 13
  • 101
  • 128
3

Use the humps library

Example

import humps
humps.camelize('jack_in_the_box')  # jackInTheBox
humps.decamelize('rubyTuesdays')  # ruby_tuesdays
humps.pascalize('red_robin')  # RedRobin

array = [{"attrOne": "foo"}, {"attrOne": "bar"}]
humps.decamelize(array) # [{"attr_one": "foo"}, {"attr_one": "bar"}]
Phillip Kigenyi
  • 1,359
  • 14
  • 21
0
data = {
     'firstName': 'abc',
     'lastName': 'xyz',
     'favoriteMovies': ['Star Wars', 'The lone ranger'],
     'favoriteCountries': [
          {'country': 'China', 'capitalCity': 'Beiging'},
          {'country': 'India', 'capitalCity': 'New Delhi'}
     ]
}

new_data = {}
for key, value in data.items():
    new_key_list = ['_' + x.lower() if x.isupper() else x for x in key]
    new_key = ''.join(new_key_list)
    if isinstance(value[0],dict):
        new_value = []
        for item in value:
            temp_dict = {}
            for key2, value2 in item.items():
                new_key_list = ['_' + x.lower() if x.isupper() else x for x in key2]
                new_key = ''.join(new_key_list)
                temp_dict[new_key] = value2
            new_value.append(temp_dict)
        new_data[new_key] = new_value
    else:
        new_data[new_key] = value

Output:

{'first_name': 'abc', 'last_name': 'xyz', 'favorite_movies': ['Star Wars', 'The lone ranger'], 'capital_city': [{'country': 'China', 'capital_city': 'Beiging'}, {'country': 'India', 'capital_city': 'New Delhi'}]}
Zaraki Kenpachi
  • 5,510
  • 2
  • 15
  • 38
  • 1
    This not replace the keys inside arrays. – Mihai Alexandru-Ionut Feb 10 '20 at 10:28
  • If our dictionary is in a different structure, and includes integers / floats this solution does not work. It may work for the particular dictionary, but errors if key values are ints. Solution by @Ajax1234 works for any pattern. – Jenobi Mar 02 '23 at 17:38
0

Here's the working code

def convert_case(str_camelcase):
    # This function takes in a string in camelCase and converts it to snake_case
    str_snake_case = ""
    for ele in list(str_camelcase):
        if ele.islower():
            str_snake_case = str_snake_case + ele
        else:
            str_snake_case = str_snake_case + "_" + ele.lower()
    return str_snake_case
    

def convert_json(json_dict):
    # This function takes in a dictionary and converts the keys of the dictionary to snake_case
    temp = {}
    for item in json_dict:
        new_item = convert_case(item)
        temp[new_item]=json_dict[item]
        new_list = []
        if type(json_dict[item]) is list:
            for ele in json_dict[item]:
                if type(ele) is dict:
                    new_list.append(convert_json(ele))
            if len(new_list)!=0:
                temp[new_item] = new_list
        if type(json_dict[item]) is dict:
           # if the value is a dictionary, recursively convert it to snake_case
           temp[new_item] = convert_json(json_dict[item])
    return temp

json = {
   "firstName":"abc",
   "lastName":"xyz",
   "favoriteMovies":[
      "Star Wars",
      "The lone ranger"
   ],
   "favoriteCountries":[
      {
         "country":"China",
         "capitalCity":"Beiging"
      },
      {
         "country":"India",
         "capitalCity":"New Delhi"
      }
   ]
}

print(convert_json(json))

Output:

{
   "first_name":"abc",
   "last_name":"xyz",
   "favorite_movies":[
      "Star Wars",
      "The lone ranger"
   ],
   "favorite_countries":[
      {
         "country":"China",
         "capital_city":"Beiging"
      },
      {
         "country":"India",
         "capital_city":"New Delhi"
      }
   ]
}
Shitij Mathur
  • 385
  • 2
  • 10