.net 忽略JSON请求中不存在的属性

egdjgwm8  于 2023-05-08  发布在  .NET
关注(0)|答案(4)|浏览(288)

我有一个在API函数中使用的DTO,并使用[FromBody]进行Map:

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

  public string PropertyB { get; init; }
}

public ... Update([FromBody] ExampleDto dto, ...)
{
}

如果JSON请求体中省略了PropertyA,它在dto中仍被视为null,但我希望允许提供实际的null值,以便执行一些逻辑。
那么,如何区分显式的null值和从请求中省略的属性值呢?
为了清楚起见:

{
  "propertyB": "Some value"
}

PropertyA已从请求中省略,因此不使用该属性执行任何逻辑。但是,从请求到DTO的Map导致该属性仍然具有null值。

{
  "propertyA": null,
  "propertyB": "Some value"
}

PropertyA已作为null提供,表示数据库中的值应设置为null

zi8p0yeb

zi8p0yeb1#

我能想到的唯一方法是通过继承JsonConverter并实现自己的JsonDeserializer来编写自己的自定义JsonDeserializer。在这种情况下,你可以看到你解析出来的属性名,然后你可以设置存在的属性名,或者作为另一个属性设置器,或者只是作为目标类上的字符串列表或其他东西。那你就能做出区分了

zzwlnbp8

zzwlnbp82#

你可以用JsonConverter属性来修饰你的类,它会处理你特殊的反序列化逻辑:

[JsonConverter(typeof(ExampleDtoConverter))]
public class ExampleDto
{
    [JsonIgnore]
    public int? PropertyA { get; init; }

    public string PropertyB { get; init; }
}

JsonConverter看起来像这样:

internal class ExampleDtoConverter : JsonConverter<ExampleDto>
{
    public override ExampleDto Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        if (typeToConvert != typeof(ExampleDto))
        {
            throw new NotSupportedException($"Unsupported type: {typeToConvert.Name}.");
        }
        JsonElement jsonObject = JsonSerializer.Deserialize<JsonElement>(ref reader, options);
        // is propertyA in JSON?
        if(jsonObject.TryGetProperty("PropertyA", out JsonElement propertyA))
        {
            // property exists and value is null?
            var value = propertyA.GetString();
            if(value is null)
            {
                // perform some special logic
            }
        }
        return new ExampleDto()
        {
            PropertyB = jsonObject.GetProperty("PropertyB").GetString() ?? string.Empty
            // Set PropertyB according to special logic
        };
    }
}
zhte4eai

zhte4eai3#

由于OP指出一个公共基类可以用于所有DTO(无论如何,这是一个很好的实践),因此这里有一个解决方案,它不需要自定义反序列化器,并且可以使数据库更新逻辑变得简单。

public class DTOBase
{
    Dictionary<string, object> _values = new Dictionary<string, object>();

    protected T GetValue<T>([CallerMemberName] string? key = null)
    {
        if (_values.TryGetValue(key, out var val) && val is T tval)
            return tval;
        return default;
    }

    protected void SetValue<T>(T val, [CallerMemberName] string key = null)
    {
        _values[key] = val;
    }

    public void Update(DTOBase updater)
    {
        foreach (var pair in updater._values)
        {
            this._values[pair.Key] = pair.Value; 
        }
    }
}

这利用了这样一个事实:如果属性不在JSON字符串中,JSON反序列化器默认不会调用属性设置器,但如果属性为null或默认值,则会调用属性设置器(除非您通过装饰器或自定义反序列化设置更改此行为-所以不要这样做)。
Update方法将允许您获取现有的数据库记录,并提供更新/请求对象,仅分配那些通过JSON显式设置的值。
因此,使用您的示例,您的DTO将变为:

public class ExampleDto : DTOBase
{
    public int? PropertyA
    {
        get => GetValue<int?>();
        set => SetValue(value);
    }

    public string PropertyB
    {
        get => GetValue<string>();
        set => SetValue(value);
    }
}

下面是一些使用JSON.NET或System.Text.JSON测试的代码:

string omitted = @"
        {
            ""PropertyA"": 5
        }";

        var obj = JsonConvert.DeserializeObject<ExampleDto>(omitted);
        var obj2 = System.Text.Json.JsonSerializer.Deserialize<ExampleDto>(omitted);

        string notOmitted = @"
        {
            ""PropertyA"": 5,
            ""PropertyB"": null
        }";

        var obj3 = JsonConvert.DeserializeObject<ExampleDto>(notOmitted);
        var obj4 = System.Text.Json.JsonSerializer.Deserialize<ExampleDto>(notOmitted);

如果进行调试,您将看到后两个对象在其_values字典中有两个成员,而前两个对象只有一个成员。
在实际的API中,您将-

  • 从数据库中提取整个现有记录
  • 使用body参数调用Update
  • 将记录插入/更新回数据库

另外,如果您正在构建自己的UPDATE语句,那么您可以扩展DTOBase来访问_values字典,从而形成更新的基础。
唯一的缺点是需要重构所有的DTO属性getter和setter,但请记住,这种抽象不仅可以获得所需的信息,还可以获得JSON对象与不,但也使得在数据库更新过程中使用这些信息变得非常容易和可扩展。

igetnqfo

igetnqfo4#

你可以试试这个:

using Newtonsoft.Json;

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

var dto = new ExampleDto { PropertyA = null, PropertyB = "example" };
var settings = new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore };
var json = JsonConvert.SerializeObject(dto, settings);


像这样使用属性:

public class ExampleDto
{
    [JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
    public int? PropertyA { get; init; }

    public string PropertyB { get; init; }
}

相关问题