You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Django Rest框架:视图中如何兼容JSON与URL编码格式的数据访问?

统一处理DRF中JSON与表单提交的嵌套字段访问

我在Django Rest Framework的视图POST方法中需要访问request.data里的嵌套信息,请求数据的预期格式如下:

{
    ...
    "licence": {
        "tenant_name": "tenant1",
        ...
    }
}

因为使用默认的JSONParser和FormParser解析器,请求可能携带JSON或HTML表单内容,且不想修改默认的parser_classes。这导致request.data的结构会随内容类型变化:

  • HTML表单编码时:<QueryDict: {..., 'licence.tenant_name': ['tenant1']}>
  • JSON编码时:{..., 'licence': {'tenant_name': 'tenant1'}}

目前我通过判断request.data的类型来处理,但想知道有没有更通用的解决方案,当前代码如下:

class SubscribeView(views.APIView):
    serializer_class = SubscriptionSerializer
    permission_classes = (AllowAny, )

    def post(self, request):
        serializer = self.serializer_class(data=request.data)
        serializer.is_valid(raise_exception=True)

        tenant_name = request.data['licence']['tenant_name'] if type(request.data) is dict else request.data['licence.tenant_name']
        # 对tenant_name执行操作

        serializer.save()

        status_code = status.HTTP_201_CREATED
        response = {
            'success': True,
            'status_code': status_code,
            'message': 'New subscription successfully created',
            'subscription': serializer.data
        }
        return Response(response, status=status_code)

方案1:直接利用序列化器的validated_data

既然已经调用了serializer.is_valid(),最简洁的方式是直接从serializer.validated_data中取值。DRF序列化器会自动处理两种提交格式的字段映射,不管是JSON的嵌套结构还是表单的点分隔字段,只要序列化器定义正确,validated_data都会统一成嵌套字典格式:

def post(self, request):
    serializer = self.serializer_class(data=request.data)
    serializer.is_valid(raise_exception=True)

    # 直接从validated_data获取,格式统一为嵌套字典
    tenant_name = serializer.validated_data['licence']['tenant_name']
    # 执行操作

    serializer.save()
    # 后续响应代码不变

前提:需要在序列化器中正确定义嵌套字段,示例如下:

class LicenceSerializer(serializers.Serializer):
    tenant_name = serializers.CharField()
    # 其他licence相关字段...

class SubscriptionSerializer(serializers.Serializer):
    licence = LicenceSerializer()
    # 其他订阅相关字段...

方案2:编写工具函数统一解析嵌套字段

如果不想依赖序列化器,可以写一个工具函数,同时支持字典和QueryDict的嵌套字段获取:

def get_nested_data(data, key_path):
    """
    从data中按路径获取嵌套字段,兼容字典(JSON)和QueryDict(表单)
    key_path格式示例:'licence.tenant_name'
    """
    keys = key_path.split('.')
    # 先尝试字典式嵌套访问
    try:
        current = data
        for key in keys:
            current = current[key]
        return current
    except (KeyError, TypeError):
        # 尝试直接访问点分隔的键(QueryDict场景)
        return data.get(key_path)

视图中使用方式:

def post(self, request):
    serializer = self.serializer_class(data=request.data)
    serializer.is_valid(raise_exception=True)

    tenant_name = get_nested_data(request.data, 'licence.tenant_name')
    # 执行操作

    serializer.save()
    # 后续响应代码不变

方案3:将QueryDict转换为嵌套字典

把QueryDict转成普通字典后,再展开点分隔的键为嵌套结构,统一数据格式:

def flatten_to_nested(data):
    """将带点分隔键的字典/QueryDict转换为嵌套字典"""
    nested = {}
    for key, value in data.items():
        keys = key.split('.')
        current = nested
        for k in keys[:-1]:
            current = current.setdefault(k, {})
        # 处理QueryDict中值为列表的情况
        current[keys[-1]] = value[0] if isinstance(value, list) else value
    return nested

视图中使用方式:

def post(self, request):
    # 统一转换为嵌套字典格式
    normalized_data = flatten_to_nested(request.data)
    serializer = self.serializer_class(data=normalized_data)
    serializer.is_valid(raise_exception=True)

    tenant_name = normalized_data['licence']['tenant_name']
    # 执行操作

    serializer.save()
    # 后续响应代码不变

内容的提问来源于stack exchange,提问作者browser-bug

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.20 01:01:13