在ASP.NET Core中实现“JSON Merge Patch” - 最好的方法是区分null和未定义的属性

问题描述 投票:4回答:3

我想创建和端点符合“JSON合并补丁”https://tools.ietf.org/html/rfc7396

请不要将它与“JavaScript Object Notation(JSON)Patch”https://tools.ietf.org/html/rfc6902混淆

但是,我在区分请求中的两种情况时遇到了一些问题:

  • 删除属性值,此处删除了电子邮件值: { surname: "Kowalski" email: null }
  • 不包括属性,因为客户端根本不想更新它,这里不包括电子邮件,因为它不应该更新: { surname: "Kowalski" }

出现问题的原因是在模型绑定后的两种情况下,电子邮件的值都为null。

您是否有建议如何实施?

rest asp.net-core api-design
3个回答
4
投票

您需要3种不同的电子邮件状态:

  1. 更新的填充值(例如[email protected]
  2. 如果要删除电子邮件,null
  3. 如果不接触电子邮件,则缺少值。

所以问题实际上是如何在模型的string属性中表达这三种状态。你不能只使用原始的string属性来执行此操作,因为null值和缺失值会因为您正确描述而发生冲突。解决方案是使用一些标志来指示值是否在请求中提供。您可以将此标志作为模型中的另一个属性,或者在string上创建一个简单的包装器,与Nullable<T>类非常相似。我建议创建简单的通用OptionalValue<T>类:

public class OptionalValue<T>
{
    private T value;
    public T Value
    {
        get => value;

        set
        {
            HasValue = true;
            this.value = value;
        }
    }

    public bool HasValue { get; set; }
}

然后你需要自定义JsonConverter,可以将通常的json值反序列化为OptionalValue<T>

class OptionalValueConverter<T> : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return objectType == typeof(OptionalValue<T>);
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        return new OptionalValue<T>
        {
            Value = (T) reader.Value,
        };
    }

    public override bool CanWrite => false;

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }
}

你的模型看起来像这样:

public class SomeModel
{
    public string Surname { get; set; }

    [JsonConverter(typeof(OptionalValueConverter<string>))]
    public OptionalValue<string> Email { get; set; } = new OptionalValue<string>();
}

请注意,您使用空的OptionalValue<string>()分配电子邮件。如果输入json不包含email值而不是Email属性将保持OptionalValueHasValue设置为false。如果输入json包含一些email,甚至null,那么OptionalValueConverter将创建OptionalValue的实例,其中HasValue设置为true

现在在控制器操作中,您可以确定email的3种状态中的任何一种:

[HttpPatch]
public void Patch([FromBody]SomeModel data)
{
    if (data.Email.HasValue)
    {
        //  Email presents in Json
        if (data.Email.Value == null)
        {
            //  Email should be removed
        }
        else
        {
            //  Email should be updated
        }
    }
    else
    {
        //  Email does not present in Json and should not be affected
    }
}

2
投票

当使用不支持undefinednull(如JavaScript和TypeScript)之间区别的语言时,这是一个特殊问题。您可以考虑其他选项:

  • 使用PUT(并不总是可行的)
  • 对于字符串使用""删除它,因为空字符串通常不是有效值(也不总是可行)
  • 添加一个额外的自定义标头,以指示您是否确实要删除该值,并将默认值设置为false(例如,X-MYAPP-SET-EMAIL=true将删除电子邮件,如果它为null)。缺点是这可能会破坏您的请求和客户开发人员的痛苦

上面的每个选项都有其自身的缺点,因此在决定走哪条路之前要仔细考虑。


1
投票

你能使用JsonMergePatch库吗? https://github.com/Morcatko/Morcatko.AspNetCore.JsonMergePatch

用法很简单:

[HttpPatch]
[Consumes(JsonMergePatchDocument.ContentType)]
public void Patch([FromBody] JsonMergePatchDocument<Model> patch)
{
   ...
   patch.ApplyTo(backendModel);
   ...
}

它似乎支持将一些属性设置为null,并保持其他属性不变。在内部,JsonMergePatchDocument创建一个JsonPatch文档,其中一个OperationType.Replace用于请求中的每个项目。 https://github.com/Morcatko/Morcatko.AspNetCore.JsonMergePatch/blob/master/src/Morcatko.AspNetCore.JsonMergePatch/Formatters/JsonMergePatchInputFormatter.cs

© www.soinside.com 2019 - 2024. All rights reserved.