How to ensure that a python dict keys are lowercase?
Asked Answered
A

8

12

I have a dict that I want to convert in JSON using simplejson.

How can I ensure that all the keys of my dict are lowercase ?

    {
        "DISTANCE": 17.059918745802999, 
        "name": "Foo Bar", 
        "Restaurant": {
            "name": "Foo Bar", 
            "full_address": {
                "country": "France", 
                "street": "Foo Bar", 
                "zip_code": "68190", 
                "city": "UNGERSHEIM"
            }, 
            "phone": "+33.389624300", 
            "longitude": "7.3064454", 
            "latitude": "47.8769091", 
            "id": "538"
        }, 
        "price": "", 
        "composition": "", 
        "profils": {}, 
        "type_menu": "", 
        "ID": ""
    },

EDIT: Thanks all to had a look at my question, I am sorry I didn't explain in detailed why I wanted this. It was to patch the JSONEmitter of django-piston.

Alenaalene answered 19/11, 2010 at 9:31 Comment(5)
To ensure they are lowercase they must be lowercase. Create a dict with lowercase keys.Tenderhearted
What do you mean by "ensure"? Do you have and existing dict and do you want to check whether all keys are lowercase? Or convert them all to lowercase? Or do you want to create a kind of dict that only allows lowercase keys (when inserting them)?Longlongan
As you can see in the example, some of the keys are not lowercase. I want to be sure that every JSON key are lowercase.Alenaalene
"some of the keys are not lowercase" Why not? Was your editor broken? What stopped you from fixing them?Trackless
The dictionary is automaticaly generated by django-piston and to be sure that the models fields are rewrite with the cls_method of the handler, I need to put them uppercase. When I convert the dictionnary in JSON I need to make sure that the key are all lowercase.Alenaalene
B
31
>>> d = {"your": "DATA", "FROM": "above"}
>>> dict((k.lower(), v) for k, v in d.iteritems())
{'from': 'above', 'your': 'DATA'}
>>> def lower_keys(x):
...   if isinstance(x, list):
...     return [lower_keys(v) for v in x]
...   elif isinstance(x, dict):
...     return dict((k.lower(), lower_keys(v)) for k, v in x.iteritems())
...   else:
...     return x
...
>>> lower_keys({"NESTED": {"ANSWER": 42}})
{'nested': {'answer': 42}}
Bacardi answered 19/11, 2010 at 9:57 Comment(2)
I only picked up that you want nested dicts and lists treated specially from your self-answer; normally I would handle this at a different level.Bacardi
On python 3 you can use comprehension and won't need the dict(...) --> d = {k.lower(): v for k, v in d.items()}Rosaleerosaleen
A
8

Here is a solution that forbids setting a lowercase key:

class LowerCaseDict(dict):
    def __setitem__(self, key, val):
        if not key.islower():
            raise TypeError, "%s key must be lowercase" % key
        dict.__setitem__(self, key, val)

ld = LowerCaseDict()
ld['g']='g'
Afoot answered 19/11, 2010 at 10:57 Comment(3)
Or, just have __setitem__ do dict.__setitem__(self, key.lower(), val). The problem with this approach in general is that the constructor and .update don't use setitem - you'll need to work around that, or LowerCaseDict({'Foo': 1, 'bar': 2}) wouldn't act as expected (won't lowercase it for my way or won't raise a TypeError for your way).Cabral
You also have to make sure to also use LowerCaseDict for every nested dict.Vermiform
This implementation is incomplete as it does not properly cover the creation of the dictionary. See https://mcmap.net/q/407853/-how-to-ensure-that-a-python-dict-keys-are-lowercase for a working solution.Counterfactual
A
6

Here is my solution :

def lower_key(in_dict):
    if type(in_dict) is dict:
        out_dict = {}
        for key, item in in_dict.items():
            out_dict[key.lower()] = lower_key(item)
        return out_dict
    elif type(in_dict) is list:
        return [lower_key(obj) for obj in in_dict]
    else:
        return in_dict
Alenaalene answered 19/11, 2010 at 9:51 Comment(1)
Nice recursive solution, to handle the issue after "the fact".Vermiform
A
4

Since you have not mentioned clearly what you want to do:

Convert all keys to lowercase:

>>> y = dict((k.lower(), v) for k, v in x.iteritems())

Check for keys:

>>> for k in x.iterkeys():
    if k.islower():
        print k, 'True'
    else:
        print k, 'False'
Airhead answered 19/11, 2010 at 9:57 Comment(1)
The problem with .iteritems() and .iterkeys() is that they're not recursive.Vermiform
C
3

Here is a complete solution

from requests import CaseInsensitiveDict

Or if you want to see the code:

class CaseInsensitiveDict(dict):

    """Basic case insensitive dict with strings only keys."""

    proxy = {}

    def __init__(self, data):
        self.proxy = dict((k.lower(), k) for k in data)
        for k in data:
            self[k] = data[k]

    def __contains__(self, k):
        return k.lower() in self.proxy

    def __delitem__(self, k):
        key = self.proxy[k.lower()]
        super(CaseInsensitiveDict, self).__delitem__(key)
        del self.proxy[k.lower()]

    def __getitem__(self, k):
        key = self.proxy[k.lower()]
        return super(CaseInsensitiveDict, self).__getitem__(key)

    def get(self, k, default=None):
        return self[k] if k in self else default

    def __setitem__(self, k, v):
        super(CaseInsensitiveDict, self).__setitem__(k, v)
        self.proxy[k.lower()] = k
Counterfactual answered 11/1, 2015 at 17:24 Comment(0)
C
2

If you just want to check if they're all lowercase (your wording, using "ensure", is not clear, but I suspect this isn't what you want), you can do that compactly in one line:

all(k.islower() for k in x.iterkeys())
Cabral answered 19/11, 2010 at 11:16 Comment(0)
E
1

You could use python-benedict (a dict subclass) and standardize your instance to convert all string keys to snake_case.

from benedict import benedict

d = benedict({
    "DISTANCE": 17.059918745802999, 
    "name": "Foo Bar", 
    "Restaurant": {
        "name": "Foo Bar", 
        "full_address": {
            "country": "France", 
            "street": "Foo Bar", 
            "zip_code": "68190", 
            "city": "UNGERSHEIM"
        }, 
        "phone": "+33.389624300", 
        "longitude": "7.3064454", 
        "latitude": "47.8769091", 
        "id": "538"
    }, 
    "price": "", 
    "composition": "", 
    "profils": {}, 
    "type_menu": "", 
    "ID": ""
})

# convert all string keys to snake_case (nested dict keys included)
d.standardize()

and your dict will become:

{
    "distance": 17.059918745802999, 
    "name": "Foo Bar", 
    "restaurant": {
        "name": "Foo Bar", 
        "full_address": {
            "country": "France", 
            "street": "Foo Bar", 
            "zip_code": "68190", 
            "city": "UNGERSHEIM"
        }, 
        "phone": "+33.389624300", 
        "longitude": "7.3064454", 
        "latitude": "47.8769091", 
        "id": "538"
    }, 
    "price": "", 
    "composition": "", 
    "profils": {}, 
    "type_menu": "", 
    "id": ""
})

Installation: pip install python-benedict

Documentation: https://github.com/fabiocaccamo/python-benedict

Note: I am the author of this project

Endanger answered 29/10, 2019 at 16:6 Comment(0)
G
1

another adaptation to handle the values:


def lower_keys(x):
        if isinstance(x, str):
                return (x.lower())
        if isinstance(x, list):
                return [lower_keys(v) for v in x]
        elif isinstance(x, dict):
                return dict((k.lower(), lower_keys(v)) for k, v in x.items())
        else:
                return x
Gaiser answered 21/11, 2019 at 21:11 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.