27

我正在开发一个 python/django 应用程序,该应用程序用作其前端对应物的 Web API 服务器。服务器和客户端之间的数据交换采用 JSON 格式,使用 XMLHttpRequest (Javascript)。对于熟悉 Python 和 Javascript 的人来说,您知道它们在变量/方法/属性方面有不同的标识符命名约定;Python 使用names_with_underscores,而 Javascript 更喜欢camelCaseNames. 我想在各自的世界中保留这两种约定,并在发生数据交换时对标识符执行转换。

我决定在服务器(Python)上执行转换。在我自己看来,这种双向转换最合乎逻辑的地方是在 JSON 序列化/反序列化期间。我应该如何实施这种方法?高度赞赏示例。

请注意,我使用的是 Python 2.7。

4

7 回答 7

25

一种使用正则表达式的方法,

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 模块的功能。但我认为这比它的价值要麻烦得多。相反,这样的事情将是等效的,并且在性能方面不会受到太大影响。

要转换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}}}

您可以将所有这些逻辑包装在 newloaddump函数中,

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.loadand json.dump

于 2013-06-17T20:38:53.117 回答
16

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'
        }
    ]
}
于 2014-02-13T00:05:52.837 回答
13

对于未来的谷歌员工,该humps软件包可以为您完成此任务。

import humps
humps.decamelize({'outerKey': {'innerKey': 'value'}})
# {'outer_key': {'inner_key': 'value'}}
于 2019-10-02T02:29:38.677 回答
3

我自己为 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 骆驼化字符串:

camel = lambda key: ''.join([key.split('_')[0].lower(), *map(str.title, key.split('_')[1:])])
于 2018-08-15T06:15:40.947 回答
2

我改进了 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'}]}
于 2018-06-25T18:40:33.027 回答
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
于 2021-10-29T11:28:02.740 回答
0

我有更好的东西!

映射器.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为自己使用它!

https://github.com/Skylude/django-rest-framework-simplify

于 2021-05-06T21:08:16.440 回答