Преобразование идентификатора именования между 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'}}