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

.NET API中如何区分DTO属性显式null与未传入?

区分DTO属性显式传入null与未传入的解决方案

在ASP.NET Core中,默认的JSON序列化会把未传入的可空属性和显式传入null的属性都映射为null,要区分这两种场景,有以下几种实用方案:

方法1:利用JsonExtensionData追踪传入的属性键

在DTO中添加标记了[JsonExtensionData]的字典,它会捕获请求体中所有的属性键(包括已定义的属性),通过检查字典中是否存在目标属性名,就能判断是显式传了null还是根本没传。

using System.Text.Json.Serialization;

public class ExampleDto
{
    public int? PropertyA { get; init; }
    public string PropertyB { get; init; }

    // 捕获所有请求体中的属性键(注意JSON键是驼峰格式)
    [JsonExtensionData]
    private IDictionary<string, JsonElement> _extensionData;

    // 对外提供判断方法
    public bool IsPropertyASupplied()
    {
        return _extensionData?.ContainsKey("propertyA") ?? false;
    }
}

接口中使用示例:

public IActionResult Update([FromBody] ExampleDto dto)
{
    if (dto.IsPropertyASupplied())
    {
        // 显式传入了PropertyA(值可能为null),执行设为null的逻辑
        // 例如:dbEntity.PropertyA = dto.PropertyA;
    }
    else
    {
        // 未传入PropertyA,不处理该属性
    }

    // 其他业务逻辑...
    return Ok();
}

方法2:自定义Optional<T>包装类型

创建泛型包装类明确区分「未设置」和「设置为null」的状态,配合自定义JSON转换器实现序列化/反序列化逻辑。

首先定义包装类和转换器:

using System.Text.Json;
using System.Text.Json.Serialization;

public class Optional<T>
{
    public bool HasValue { get; }
    public T Value { get; }

    private Optional(T value, bool hasValue)
    {
        Value = value;
        HasValue = hasValue;
    }

    public static Optional<T> Unset() => new(default, false);
    public static Optional<T> Set(T value) => new(value, true);

    // 隐式转换,简化赋值
    public static implicit operator Optional<T>(T value) => Set(value);
}

public class OptionalJsonConverter<T> : JsonConverter<Optional<T>>
{
    public override Optional<T> Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        // 只要JSON中存在该属性,就标记为已设置(不管值是不是null)
        var value = JsonSerializer.Deserialize<T>(ref reader, options);
        return Optional<T>.Set(value);
    }

    public override void Write(Utf8JsonWriter writer, Optional<T> value, JsonSerializerOptions options)
    {
        if (value.HasValue)
        {
            JsonSerializer.Serialize(writer, value.Value, options);
        }
        else
        {
            writer.WriteNullValue();
        }
    }
}

修改DTO并使用:

public class ExampleDto
{
    [JsonConverter(typeof(OptionalJsonConverter<int?>))]
    public Optional<int?> PropertyA { get; init; } = Optional<int?>.Unset();

    public string PropertyB { get; init; }
}

接口逻辑:

public IActionResult Update([FromBody] ExampleDto dto)
{
    if (dto.PropertyA.HasValue)
    {
        // 显式传入了PropertyA,执行对应逻辑
        // dbEntity.PropertyA = dto.PropertyA.Value;
    }
    else
    {
        // 未传入PropertyA,跳过处理
    }

    return Ok();
}

这种方法语义清晰,适合需要频繁区分该场景的项目。

方法3:直接解析JsonDocument(手动映射)

如果不想修改现有DTO结构,可以直接接收JsonDocument,手动解析并判断属性是否存在,再映射到DTO对象。

public IActionResult Update([FromBody] JsonDocument requestDoc)
{
    var root = requestDoc.RootElement;
    var dto = new ExampleDto();

    // 处理PropertyB
    if (root.TryGetProperty("propertyB", out var propB))
    {
        dto.PropertyB = propB.GetString();
    }

    // 判断PropertyA是否被传入
    bool isPropertyASupplied = root.TryGetProperty("propertyA", out var propA);
    if (isPropertyASupplied)
    {
        dto.PropertyA = propA.ValueKind == JsonValueKind.Null ? null : propA.GetInt32();
        // 执行设为null的逻辑
    }
    else
    {
        // 未传入,不处理
    }

    // 其他业务逻辑...
    return Ok();
}

这种方法无需修改DTO,但需要手动处理属性映射,适合简单场景。


内容的提问来源于stack exchange,提问作者Ruben Hart

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.22 21:30:27