.NET 7中DTO字段的条件JSON序列化/反序列化问题
解决方案
方案1:拆分DTO(推荐,符合单一职责)
将用于接收请求和返回响应的DTO分开,避免一个DTO承担两种职责,逻辑更清晰,无序列化冲突问题。
定义专用DTO
// 用于Post/Put请求的DTO(无需Id字段) public class CreateUpdateUserDTO { public string Email { get; set; } public string Password { get; set; } public string? FirstName { get; set; } public string? LastName { get; set; } public bool Status { get; set; } = true; public List<RoleDTO> Roles { get; set; } = new List<RoleDTO>(); } // 用于Get响应的DTO(包含Id字段) public class UserResponseDTO { public string Id { get; set; } public string Email { get; set; } public string? FirstName { get; set; } public string? LastName { get; set; } public bool Status { get; set; } public List<RoleDTO> Roles { get; set; } = new List<RoleDTO>(); }
修改控制器方法
[HttpPost] [Route("users")] public async Task<IActionResult> CreateUserAsync(CreateUpdateUserDTO u) { var user = _mapper.Map<User>(u); await _usersApplication.CreateAsync(user); var response = _mapper.Map<UserResponseDTO>(user); return CreatedAtAction(nameof(GetUserByIdAsync), new { userId = response.Id }, response); } [HttpGet("user/{userId}")] public async Task<IActionResult> GetUserByIdAsync(string userId) { var u = await _usersApplication.GetByIdAsync(userId); var userResponse = _mapper.Map<UserResponseDTO>(u); return Ok(userResponse); }
此方式下Swagger会自动匹配请求/响应的正确DTO结构,Post请求不会显示Id,Get请求正常返回Id。
方案2:System.Text.Json条件序列化+Swagger过滤器(不拆分DTO场景)
若不想拆分DTO,可通过Json特性控制序列化逻辑,再配合Swagger过滤器隐藏请求中的Id字段。
步骤1:配置DTO的Json特性
使用JsonPropertyAccess.ReadOnly标记Id字段,实现序列化时输出、反序列化时忽略:
public class UserDTO { [JsonPropertyName("id")] [JsonProperty(Access = JsonPropertyAccess.ReadOnly)] public string Id { get; set; } public string Email { get; set; } public string Password { get; set; } public string? FirstName { get; set; } public string? LastName { get; set; } public bool Status { get; set; } = true; public List<RoleDTO> Roles { get; set; } = new List<RoleDTO>(); }
步骤2:添加Swagger过滤器隐藏Id字段
创建Swagger操作过滤器,移除Post请求中的Id属性显示:
public class RemoveIdFromCreateUserFilter : IOperationFilter { public void Apply(OpenApiOperation operation, OperationFilterContext context) { if (operation.RequestBody != null && context.ApiDescription.HttpMethod == HttpMethod.Post.ToString() && context.ApiDescription.RelativePath == "users") { var schema = operation.RequestBody.Content["application/json"].Schema; if (schema.Properties.ContainsKey("id")) { schema.Properties.Remove("id"); } } } }
在Program.cs中注册过滤器:
builder.Services.AddSwaggerGen(c => { c.OperationFilter<RemoveIdFromCreateUserFilter>(); });
设置完成后:Post请求会忽略传入的Id,Get请求正常返回Id,Swagger的Post示例不再显示Id字段。
方案3:自定义JsonConverter(灵活控制场景)
编写自定义转换器实现更精细的序列化/反序列化逻辑:
public class UserDTOConverter : JsonConverter<UserDTO> { public override UserDTO Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { // 反序列化时手动解析,跳过Id字段 var jsonElement = JsonElement.ParseValue(ref reader); var dto = new UserDTO { Email = jsonElement.GetProperty("Email").GetString(), Password = jsonElement.GetProperty("Password").GetString(), FirstName = jsonElement.TryGetProperty("FirstName", out var fn) ? fn.GetString() : null, LastName = jsonElement.TryGetProperty("LastName", out var ln) ? ln.GetString() : null, Status = jsonElement.TryGetProperty("Status", out var status) ? status.GetBoolean() : true, Roles = jsonElement.TryGetProperty("Roles", out var roles) ? JsonSerializer.Deserialize<List<RoleDTO>>(roles.GetRawText(), options) : new List<RoleDTO>() }; return dto; } public override void Write(Utf8JsonWriter writer, UserDTO value, JsonSerializerOptions options) { // 序列化时正常输出所有字段(含Id) JsonSerializer.Serialize(writer, value, options); } }
在DTO上添加转换器特性:
[JsonConverter(typeof(UserDTOConverter))] public class UserDTO { // 字段定义不变 }
内容的提问来源于stack exchange,提问作者Diego Perez
相关产品推荐
相关产品推荐

