[英]Why does pandas have a naming convention which mixes CamelCase and lowercase with underscores?
[英]Converting identifier naming between camelCase and underscores during JSON serialization/deserialization
我正在開發一個 python/django 應用程序,它作為前端對應的 Web API 服務器。 服務器和客戶端之間的數據交換采用 JSON 格式,使用 XMLHttpRequest (Javascript)。 對於那些既熟悉 Python 又熟悉 Javascript 的人,你知道它們在變量/方法/屬性方面有不同的標識符命名約定; Python 使用names_with_underscores
而 Javascript 更喜歡camelCaseNames
。 我想在各自的世界中保留這兩個約定,並在發生數據交換時對標識符進行轉換。
我決定在服務器(Python)上執行轉換。 在我看來,發生這種雙向轉換最合乎邏輯的地方是在 JSON 序列化/反序列化期間。 我應該如何實施這種方法? 示例受到高度贊賞。
請注意,我使用的是 Python 2.7。
使用正則表達式的一種方法,
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
。
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'
}
]
}
對於未來的Google,在humps
包能為你做到這一點。
import humps
humps.decamelize({'outerKey': {'innerKey': 'value'}})
# {'outer_key': {'inner_key': 'value'}}
我對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'}]}
我自己為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(字典)
你也可以使用lambda對一個字符串進行camelize:
camel = lambda key: ''.join([key.split('_')[0].lower(), *map(str.title, key.split('_')[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)
def convert_json(d, convert):
if isinstance(d, list):
return [convert_json(item, convert) for item in d]
new_d = {}
for k, v in d.items():
new_d[convert(k)] = convert_json(v,convert) if isinstance(v,dict) else v
return new_d
我還有更好的!
映射器.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
並自己使用它!
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.