我正在开发一个python / django应用程序,它作为前端对应的Web API服务器。服务器和客户端之间的数据交换采用JSON格式,使用XMLHttpRequest(Javascript)。对于那些熟悉Python和Javascript的人,你知道他们在变量/方法/属性方面有不同的标识符命名约定; Python使用names_with_underscores
而Javascript更喜欢camelCaseNames
。我想在各自的世界中保留这两个约定,并在数据交换发生时执行标识符的转换。
我决定在服务器上执行转换(Python)。在我看来,这种双向转换最合乎逻辑的地方是在JSON序列化/反序列化期间。我该如何实施这种方法?例子非常受欢迎。
请注意,我使用的是Python 2.7。
答案 0 :(得分:17)
使用正则表达式的一种方法,
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
表达式)来完成大小写更改,但这看起来非常简单。
修改强>
如果您真的想在Python和Javascript之间拦截和调整json对象,则必须重写json模块的功能。但我认为这比它的价值更麻烦。相反,像这样的东西是相同的,而不是太多的打击性能。
要转换表示json对象的dict
中的每个键,您可以执行以下操作,
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
一样使用。
答案 1 :(得分:11)
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'
}
]
}
答案 2 :(得分:2)
对于将来的Google员工,humps
软件包可以为您做到这一点。
import humps
humps.decamelize({'outerKey': {'innerKey': 'value'}})
# {'outer_key': {'inner_key': 'value'}}
答案 3 :(得分:1)
我对埃文·西罗基(Evan Siroky)的回答有所改善。
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 :(得分:1)
我自己在TornadoWeb的一个项目中自己做完之后,才找到这个答案。因此,我将其重写为使用递归,它是python 3.7,但只需将项目更改为iteritems,就可以轻松地适应python 2.7
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(dictionary)
您还可以使用lambda将字符串字符串化:
camel = lambda key: ''.join([key.split('_')[0].lower(), *map(str.title, key.split('_')[1:])])
答案 5 :(得分:0)
我有更好的东西!
mapper.py
import re
class Mapper:
def __init__(self):
pass
@staticmethod
def camelcase_to_underscore(camel_case):
if isinstance(camel_case, dict) or isinstance(camel_case, list):
return Mapper.dict_camelcase_to_underscore(camel_case)
else:
return re.sub('(((?<=[a-z])[A-Z])|([A-Z](?![A-Z]|$)))', '_\\1', camel_case).lower().strip('_')
@staticmethod
def underscore_to_camelcase(underscore):
if isinstance(underscore, dict) or isinstance(underscore, list):
return Mapper.dict_underscore_to_camelcase(underscore)
else:
return Mapper.string_underscore_to_camelcase(underscore)
@staticmethod
def string_underscore_to_camelcase(underscore):
if '_' in underscore:
return re.sub(r'(?!^)_([a-zA-Z])', lambda m: m.group(1).upper(), underscore)
else:
return underscore
@staticmethod
def underscore_to_titlecase(underscore):
if isinstance(underscore, dict) or isinstance(underscore, list):
return Mapper.dict_underscore_to_titlecase(underscore)
else:
title_name = underscore.replace('_', ' ').title().replace(' ', '')
return title_name
@staticmethod
def titlecase_to_camelcase(titlecase):
if isinstance(titlecase, dict) or isinstance(titlecase, list):
return Mapper.dict_titlecase_to_camelcase(titlecase)
else:
if titlecase.isupper():
return titlecase.lower()
else:
val = titlecase[0].lower() + titlecase[1:]
reg = re.compile('^[A-Z]+')
front = reg.findall(titlecase)
if len(front) > 0:
if front[0].isupper() and len(front[0]) > 1:
s1 = front[0][:-1].lower()
val = s1 + titlecase[len(s1):]
if val[-2:] == "ID":
val = val[:-2] + "Id"
elif val[-3:] == "IDs":
val = val[:-3] + "Ids"
return val
@staticmethod
def dict_camelcase_to_underscore(obj):
if isinstance(obj, dict):
new_dict = {}
for key, value in obj.items():
underscore = Mapper.camelcase_to_underscore(key)
if isinstance(value, dict) or isinstance(value, list):
value = Mapper.camelcase_to_underscore(value)
new_dict[underscore] = value
return new_dict
elif isinstance(obj, list):
new_list = []
for o in obj:
new_item = {}
if isinstance(o, list):
new_item = Mapper.camelcase_to_underscore(o)
elif isinstance(o, dict):
for key, value in o.items():
underscore = Mapper.camelcase_to_underscore(key)
if isinstance(value, dict) or isinstance(value, list):
value = Mapper.camelcase_to_underscore(value)
new_item[underscore] = value
else:
new_item = o
new_list.append(new_item)
return new_list
@staticmethod
def dict_underscore_to_camelcase(obj):
if isinstance(obj, dict):
return {
Mapper.string_underscore_to_camelcase(key) : Mapper.dict_underscore_to_camelcase(value)
for key, value in obj.items()
}
if isinstance(obj, list):
return [Mapper.dict_underscore_to_camelcase(x) for x in obj]
return obj
@staticmethod
def dict_underscore_to_titlecase(obj):
if isinstance(obj, dict):
new_dict = {}
for key, value in obj.items():
titlecase = Mapper.underscore_to_titlecase(key)
if isinstance(value, dict) or isinstance(value, list):
value = Mapper.underscore_to_titlecase(value)
new_dict[titlecase] = value
return new_dict
elif isinstance(obj, list):
new_list = []
for o in obj:
new_dict = {}
for key, value in o.items():
titlecase = Mapper.underscore_to_titlecase(key)
if isinstance(value, dict) or isinstance(value, list):
value = Mapper.underscore_to_titlecase(value)
new_dict[titlecase] = value
new_list.append(new_dict)
return new_list
@staticmethod
def dict_titlecase_to_camelcase(obj):
if isinstance(obj, dict):
new_dict = {}
for key, value in obj.items():
camelcase = Mapper.titlecase_to_camelcase(key)
if isinstance(value, dict) or isinstance(value, list):
value = Mapper.titlecase_to_camelcase(value)
new_dict[camelcase] = value
return new_dict
elif isinstance(obj, list):
new_list = []
for o in obj:
new_dict = {}
if isinstance(o, dict):
for key, value in o.items():
camelcase = Mapper.titlecase_to_camelcase(key)
if isinstance(value, dict) or isinstance(value, list):
value = Mapper.titlecase_to_camelcase(value)
new_dict[camelcase] = value
new_list.append(new_dict)
else:
new_list.append(o)
return new_list
当然,你必须进行测试!
test_mapper.py
import random
import unittest
import uuid
from unittest.mock import MagicMock
from rest_framework_simplify.mapper import Mapper
class MapperTests(unittest.TestCase):
def test_camelcase_to_underscore_not_capitalized(self):
camel_case = 'camelCase'
underscore = 'camel_case'
val = Mapper.camelcase_to_underscore(camel_case)
self.assertEqual(val, underscore)
def test_camelcase_to_underscore_capitalized(self):
camel_case = 'CamelCase'
underscore = 'camel_case'
val = Mapper.camelcase_to_underscore(camel_case)
self.assertEqual(val, underscore)
def test_camelcase_to_underscore_array_of_numbers(self):
camel_case = {'camelCase': [1, 10]}
underscore = {'camel_case': [1, 10]}
val = Mapper.camelcase_to_underscore(camel_case)
self.assertEqual(val, underscore)
def test_camelcase_to_underscore_array_of_strings(self):
camel_case = {'camelCase': ['camelCase']}
underscore = {'camel_case': ['camelCase']}
val = Mapper.camelcase_to_underscore(camel_case)
self.assertEqual(val, underscore)
def test_camelcase_to_underscore_array_of_bools(self):
camel_case = {'camelCase': [True, False]}
underscore = {'camel_case': [True, False]}
val = Mapper.camelcase_to_underscore(camel_case)
self.assertEqual(val, underscore)
def test_camelcase_to_underscore_empty_array(self):
camel_case = {'camelCase': []}
underscore = {'camel_case': []}
val = Mapper.camelcase_to_underscore(camel_case)
self.assertEqual(val, underscore)
def test_camelcase_to_underscore_array_of_objects(self):
camel_case = {'camelCase': [{'camelCase': 1}]}
underscore = {'camel_case': [{'camel_case': 1}]}
val = Mapper.camelcase_to_underscore(camel_case)
self.assertEqual(val, underscore)
def test_camelcase_to_underscore_array_of_mixed_types(self):
int_type_value = random.randint(1, 10)
str_type_value = str(uuid.uuid4())[:4]
bool_type_value = False
obj_type_value = {'camelCase': 1}
ary_type_value = [int_type_value, obj_type_value]
underscore_mock = MagicMock(obj_type_value={'camel_case': 1}, ary_type_value=[int_type_value, {'camel_case': 1}])
camel_case = {'camelCase': [int_type_value, str_type_value, obj_type_value, ary_type_value, bool_type_value]}
underscore = {'camel_case': [int_type_value, str_type_value, underscore_mock.obj_type_value, underscore_mock.ary_type_value, bool_type_value]}
val = Mapper.camelcase_to_underscore(camel_case)
self.assertEqual(val, underscore)
def test_underscore_to_camelcase_array_of_mixed_types(self):
int_type_value = random.randint(1, 10)
str_type_value = str(uuid.uuid4())[:4]
bool_type_value = False
obj_type_value = {'camel_case': 1}
ary_type_value = [int_type_value, obj_type_value]
camel_case_mock = MagicMock(obj_type_value={'camelCase': 1}, ary_type_value=[int_type_value, {'camelCase': 1}])
underscore = {'camel_case': [int_type_value, str_type_value, obj_type_value, ary_type_value, bool_type_value]}
camel_case = {'camelCase': [int_type_value, str_type_value, camel_case_mock.obj_type_value, camel_case_mock.ary_type_value, bool_type_value]}
val = Mapper.underscore_to_camelcase(underscore)
self.assertEqual(val, camel_case)
def test_underscore_to_camelcase(self):
underscore = 'camel_case'
camel_case = 'camelCase'
val = Mapper.underscore_to_camelcase(underscore)
self.assertEqual(val, camel_case)
# I know this is horrible, but we have api's relying on this bug and we cannot fix it safely
def test_underscore_to_backwards_compatible(self):
underscore = 'address_line_1'
camel_case = 'addressLine_1'
val = Mapper.underscore_to_camelcase(underscore)
self.assertEqual(val, camel_case)
def test_underscore_to_camelcase_embedded(self):
underscore = [{'camel_case': [{'more_camel_case': 5}]}]
camel_case = [{'camelCase': [{'moreCamelCase': 5}]}]
val = Mapper.underscore_to_camelcase(underscore)
self.assertEqual(val, camel_case)
def test_title_case_full_upper(self):
upper = 'SSN'
lower = 'ssn'
val = Mapper.titlecase_to_camelcase(upper)
self.assertEqual(val, lower)
def test_title_case_mixed_bag(self):
title = 'PMSystemID'
camel = 'pmSystemId'
val = Mapper.titlecase_to_camelcase(title)
self.assertEqual(val, camel)
def test_underscore_t0_titlecase(self):
underscore = 'sum_charges'
title = 'SumCharges'
val = Mapper.underscore_to_titlecase(underscore)
self.assertEqual(val, title)
当然,您总是可以只pip install rest_framework_simplify
自己使用它!