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.

Natim
  • 17,274
  • 23
  • 92
  • 150
  • 2
    To ensure they are lowercase they must be lowercase. Create a dict with lowercase keys. – Luca Matteis Nov 19 '10 at 09:32
  • 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)? – Jordi Nov 19 '10 at 09:36
  • 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. – Natim Nov 19 '10 at 09:52
  • "some of the keys are not lowercase" Why not? Was your editor broken? What stopped you from fixing them? – S.Lott Nov 19 '10 at 13:46
  • 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. – Natim Nov 23 '10 at 10:14

8 Answers8

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}}
Fred Nurk
  • 13,952
  • 4
  • 37
  • 63
  • 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. – Fred Nurk Nov 19 '10 at 10:04
  • 8
    On python 3 you can use comprehension and won't need the `dict(...)` --> `d = {k.lower(): v for k, v in d.items()}` – Delblanco Oct 20 '16 at 07:57
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'
vonPetrushev
  • 5,457
  • 6
  • 39
  • 51
  • 6
    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). – Chris Morgan Nov 19 '10 at 11:22
  • You also have to make sure to also use LowerCaseDict for every nested dict. – ThomasH Nov 19 '10 at 11:31
  • This implementation is incomplete as it does not properly cover the creation of the dictionary. See http://stackoverflow.com/a/27889931/99834 for a working solution. – sorin Jan 11 '15 at 17:25
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
Natim
  • 17,274
  • 23
  • 92
  • 150
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'
user225312
  • 126,773
  • 69
  • 172
  • 181
2

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
sorin
  • 161,544
  • 178
  • 535
  • 806
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())
Chris Morgan
  • 86,207
  • 24
  • 208
  • 215
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

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
Fabio Caccamo
  • 1,871
  • 19
  • 21
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
DetroitMike
  • 124
  • 1
  • 1
  • 6