簡體   English   中英

在 JSON 序列化/反序列化期間在駝峰命名和下划線之間轉換標識符命名

[英]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}}}

您可以在新的loaddump函數中包含所有這些邏輯,

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.loadjson.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並自己使用它!

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM