django-graphql-jwt用django-phone-field致PhoneNumber JSON序列化错误解
报错核心运行原理
django-phone-field的默认设计是,当从数据库读取PhoneField对应的值时,不会直接返回存储的字符串,而是将值封装为PhoneNumber类实例——目的是提供内置的手机号校验、多格式转换、号码拆分等便捷能力。django-graphql-jwt的tokenAuth突变流程中,会按照自定义用户模型配置的USERNAME_FIELD读取对应用户字段值,直接将值合并进JWT payload字典,后续通过JSON序列化逻辑生成token字符串。这一步框架默认预期用户名字段是原生支持序列化的字符串、数字类型,没有为自定义复杂类型做兼容处理。- 报错触发的完整链路是:因为你的模型设置
USERNAME_FIELD = 'phone',框架从用户对象上读取到的phone属性是PhoneNumber类实例,该类没有实现原生JSON序列化接口,在payload序列化阶段直接抛出Object of type PhoneNumber is not JSON serializable错误。
临时猴子补丁的潜在风险
你当前通过重写CustomPhoneField.to_python实现的方案,本质是强制把字段所有环节的返回值都转成字符串,会带来几个可预见的问题:
- 彻底丢失
django-phone-field的原生能力:字段自带的手机号格式校验、PhoneNumber类提供的便捷方法(如提取国家码、本地号码段)、Admin后台的自动格式化展示逻辑全部失效。 - 存在唯一约束冲突隐患:你的
phone字段设置了unique=True,当前重写逻辑在值为空时返回None,如果后续流程没有对空值做拦截,会出现多个空值触发数据库唯一约束报错的问题。 - 校验链路逻辑断裂:
to_python是Django模型字段反序列化、表单clean校验的核心入口,直接强制返回字符串会让字段无法识别非法的手机号格式,所有提交的不规范号码都会被当作合法值存入数据库。
优雅的分层解决方案
完全不需要修改模型字段的原生逻辑,只需要精准定位出问题的环节做适配即可:
- 移除之前自定义
CustomPhoneField中重写的to_python方法,让字段保持默认的PhoneNumber实例返回逻辑,保留原有的校验、格式化能力。 - 利用
django-graphql-jwt自带的自定义payload扩展机制,仅在JWT生成环节对PhoneNumber类型做序列化转换:- 编写自定义的payload生成函数,在框架默认逻辑基础上,将
PhoneNumber实例转为可序列化的字符串:from graphql_jwt.utils import jwt_payload from phonenumber.phonenumber import PhoneNumber def custom_jwt_payload(user): payload = jwt_payload(user) if isinstance(user.phone, PhoneNumber): payload["phone"] = str(user.phone) return payload - 在Django配置文件中,将上述函数注册为JWT的payload生成入口:
GRAPHQL_JWT = { "JWT_PAYLOAD_HANDLER": "你的项目工具模块路径.custom_jwt_payload", }
- 编写自定义的payload生成函数,在框架默认逻辑基础上,将
- 针对GraphQL层的字段暴露需求,只需为Schema添加
PhoneNumber到字符串的Scalar转换规则,就可以保证前端查询phone字段时不会触发序列化错误,全程不需要修改模型层的逻辑。
内容的提问来源于stack exchange,提问作者Cristian Rojas
相关产品推荐
相关产品推荐

