Преобразование идентификатора именования между camelCase и подчеркиваниями во время сериализации/десериализации JSON

Я работаю над приложением python/django, которое служит в качестве сервера веб-API для его внешнего аналога. Обмен данными между сервером и клиентом осуществляется в формате JSON с использованием XMLHttpRequest (Javascript). Для тех, кто знаком с Python и Javascript, вы знаете, что они имеют разные соглашения об именах идентификаторов, когда дело доходит до переменных/методов/атрибутов; Python использует names_with_underscores, в то время как Javascript предпочитает camelCaseNames. Я хотел бы сохранить оба соглашения в их соответствующих мирах и выполнить преобразование по идентификаторам, когда происходит обмен данными.

Я решил провести преобразование на сервере (Python). По моему мнению, наиболее логичным местом для этого двухстороннего преобразования является во время сериализации/десериализации JSON. Как мне следует реализовать этот подход? Примеры высоко оценены.

Обратите внимание, что я на Python 2.7.

Ответ 1

Один из способов сделать это с помощью регулярных выражений

import re

camel_pat = re.compile(r'([A-Z])')
under_pat = re.compile(r'_([a-z])')

def camel_to_underscore(name):
    return camel_pat.sub(lambda x: '_' + x.group(1).lower(), name)

def underscore_to_camel(name):
    return under_pat.sub(lambda x: x.group(1).upper(), name)

и

>>> camel_to_underscore('camelCaseNames')
'camel_case_names'
>>> underscore_to_camel('names_with_underscores')
'namesWithUnderscores'

Примечание. Вы должны использовать функцию (выражение lambda здесь) для выполнения изменения случая, но это кажется довольно простым.

EDIT:

Если вы действительно хотели перехватить и настроить объекты json между Python и Javascript, вам придется переписать функциональность модуля json. Но я думаю, что это гораздо больше проблем, чем того стоит. Вместо этого что-то вроде этого было бы эквивалентным и не было бы слишком большим по производительности.

Чтобы преобразовать каждый ключ в dict, представляющий ваш объект json, вы можете сделать что-то вроде этого,

def convert_json(d, convert):
    new_d = {}
    for k, v in d.iteritems():
        new_d[convert(k)] = convert_json(v,convert) if isinstance(v,dict) else v
    return new_d

Вам нужно только указать, какую функцию применять,

>>> json_obj = {'nomNom': {'fooNom': 2, 'camelFoo': 3}, 'camelCase': {'caseFoo': 4, 'barBar': {'fooFoo': 44}}}
>>> convert_json(json_obj, camel_to_underscore)
{'nom_nom': {'foo_nom': 2, 'camel_foo': 3}, 'camel_case': {'case_foo': 4, 'bar_bar': {'foo_foo': 44}}}

Вы можете объединить всю эту логику в новые функции load и dump,

import json

def convert_load(*args, **kwargs):
    json_obj = json.load(*args, **kwargs)
    return convert_json(json_obj, camel_to_underscore)

def convert_dump(*args, **kwargs):
    args = (convert_json(args[0], underscore_to_camel),) + args[1:]
    json.dump(*args, **kwargs)

И используйте то же, что и json.load и json.dump.

Ответ 2

Ответ Jared не учитывает возможность массивов с объектами в структуре объекта json.

Для рекурсивного управления массивами решение требует трех функций.

Для преобразования из CamelCase в underscores_with_spaces:

def convert(s):
    a = re.compile('((?<=[a-z0-9])[A-Z]|(?!^)[A-Z](?=[a-z]))')
    return a.sub(r'_\1', s).lower()

Для объекта json

def convertJSON(j):
    out = {}
    for k in j:
        newK = convert(k)
        if isinstance(j[k],dict):
            out[newK] = convertJSON(j[k])
        elif isinstance(j[k],list):
            out[newK] = convertArray(j[k])
        else:
            out[newK] = j[k]
    return out

Для массивов в объекте json:

def convertArray(a):
    newArr = []
    for i in a:
        if isinstance(i,list):
            newArr.append(convertArray(i))
        elif isinstance(i, dict):
            newArr.append(convertJSON(i))
        else:
            newArr.append(i)
    return newArr

Использование:

convertJSON({
    "someObject": [
        {
            "anotherObject": "CamelCaseValue"
        },
        {
            "anotherObject": "AnotherCamelCaseValue"
        }
    ]
})

Урожайность:

{
    'some_object': [
        {
            'another_object': 'CamelCaseValue'
        },
        {
            'another_object': 'AnotherCamelCaseValue'
        }
    ]
}

Ответ 3

Я улучшил ответ эвана Сироки.

import re


class convert:
    def __init__(self):
        self.js_to_py_re = re.compile('((?<=[a-z0-9])[A-Z]|(?!^)[A-Z](?=[a-z]))')
        self.py_to_js_re = re.compile(r'_([a-z])')

    def convert_js_to_py(self, s):
        return self.js_to_py_re.sub(r'_\1', s).lower()

    def convert_py_to_js(self, s):
        return self.py_to_js_re.sub(lambda x: x.group(1).upper(), s)

    def js_to_py_JSON(self, j):
        out = {}
        for k in j:
            newK = self.convert_js_to_py(k)
            if isinstance(j[k], dict):
                out[newK] = self.js_to_py_JSON(j[k])
            elif isinstance(j[k], list):
                out[newK] = self.js_to_py_array(j[k])
            else:
                out[newK] = j[k]
        return out

    def js_to_py_array(self, a):
        newArr = []
        for i in a:
            if isinstance(i, list):
                newArr.append(self.js_to_py_array(i))
            elif isinstance(i, dict):
                newArr.append(self.js_to_py_JSON(i))
            else:
                newArr.append(i)
        return newArr

    def py_to_js_JSON(self, j):
        out = {}
        for k in j:
            newK = self.convert_py_to_js(k)
            if isinstance(j[k], dict):
                out[newK] = self.py_to_js_JSON(j[k])
            elif isinstance(j[k], list):
                out[newK] = self.py_to_js_array(j[k])
            else:
                out[newK] = j[k]
        return out

    def py_to_js_array(self, a):
        newArr = []
        for i in a:
            if isinstance(i, list):
                newArr.append(self.py_to_js_array(i))
            elif isinstance(i, dict):
                newArr.append(self.py_to_js_JSON(i))
            else:
                newArr.append(i)
        return newArr


if __name__ == '__main__':
    py_to_js = {
        'some_object': [
            {
                'another_object': 'CamelCaseValue'
            },
            {
                'another_object': 'AnotherCamelCaseValue'
            }
        ]
    }
    js_to_py = {
        "someObject": [
            {
                "anotherObject": "CamelCaseValue"
            },
            {
                "anotherObject": "AnotherCamelCaseValue"
            }
        ]
    }
    print convert().py_to_js_JSON(py_to_js)
    print convert().js_to_py_JSON(js_to_py)

Выше приведено:

{'someObject': [{'anotherObject': 'CamelCaseValue'}, {'anotherObject': 'AnotherCamelCaseValue'}]}
{'some_object': [{'another_object': 'CamelCaseValue'}, {'another_object': 'AnotherCamelCaseValue'}]}

Ответ 4

Я только нашел этот ответ после того, как сделал это сам для проекта с TornadoWeb. Поэтому я переписал его для использования рекурсии, это python 3.7, но его можно легко адаптировать к python 2.7, просто изменив элементы на iteritems

def camel(snake_str):
    first, *others = snake_str.split('_')
    return ''.join([first.lower(), *map(str.title, others)])

def camelize_dict(snake_dict):
    new_dict = {}
    for key, value in snake_dict.items():
        new_key = camel(key)
        if isinstance(value, list):
            new_dict[new_key] = list(map(camelize_dict, value))
        elif isinstance(value, dict):
            new_dict[new_key] = camelize_dict(value)
        else:
            new_dict[new_key] = value
    return new_dict

просто импортируйте camelize_dict (словарь)

вы также можете использовать верблюд для лямбды:

camel = lambda key: ''.join([key.split('_')[0].lower(), *map(str.title, key.split('_')[1:])])

Ответ 5

Для будущих гуглеров пакет humps может сделать это для вас.

import humps
humps.decamelize({'outerKey': {'innerKey': 'value'}})
# {'outer_key': {'inner_key': 'value'}}