代码之家  ›  专栏  ›  技术社区  ›  user44

在ASP中实现“JSON合并补丁”。NET Core-区分null和未定义属性的最佳方法

  •  8
  • user44  · 技术社区  · 7 年前

    我想创建符合“JSON合并补丁”的端点 https://www.rfc-editor.org/rfc/rfc7396

    请不要将其与“JavaScript对象表示法(JSON)补丁”混淆 https://www.rfc-editor.org/rfc/rfc6902

    然而,在区分请求中的两种情况方面,我有一个小问题:

    • 删除属性值,此处删除电子邮件值:

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

        {
            surname: "Kowalski"
        }
      

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

    你有什么建议可以实施吗?

    4 回复  |  直到 3 年前
        1
  •  4
  •   CodeFuller    7 年前

    在此处,您需要3种不同的状态才能获得电子邮件价值:

    1. 用于更新的填充值(例如。 test@mail.com )
    2. null 如果应删除电子邮件,则返回值
    3. 如果不应触摸电子邮件,则缺少值。

    所以问题实际上是如何表达这三种状态 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 财产将保留 OptionalValue 具有 HasValue 设置为 false . 如果输入json包含一些 电子邮件 即使 无效的 然后 OptionalValueConverter 将创建的实例 期权价值 具有 HasValue公司 设置为 true .

    现在,在控制器操作中,您可以确定以下三种状态中的任何一种: 电子邮件 :

    [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
  •  3
  •   Matt    6 年前

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

    用法非常简单:

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

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

        3
  •  2
  •   r0bnet    6 年前

    当使用不支持区分 undefined null 就像JavaScript和TypeScript一样。您还可以考虑其他选项:

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

    上面的每种选择都有自己的缺点,所以在决定走哪条路之前要仔细考虑。

        4
  •  1
  •   r.pedrosa    4 年前

    我带着同样的问题来到这个帖子。我的解决方案类似于“CodeFuller”解决方案,但更完整,因为它涵盖了API文档,并且更出色,因为使用的代码更少。它还使用 System.text.json instead of the Newtonsoft 图书馆

    1. 利用以下优势定义模型: the existent Optional struct (无需创建新的 OptionalValue 类)

      {
          public string Surname { get; set; }
      
          [JsonConverter(typeof(OptionalConverter<string>))]
          public Optional<string> Email { get; set; } = default;
      }
      
      
    2. 告诉Swagger(如果适用)将格式设置为字符串输入/类型,以获得更好的客户端体验:

      c.MapType<Optional<string>>(() => new OpenApiSchema { Type = "string" });

    3. 基于添加自定义JSON转换器 系统文本json :

      public class OptionalConverter<T> : JsonConverter<Optional<T>>
          {
              // https://docs.microsoft.com/en-us/dotnet/standard/serialization/system-text-json-converters-how-to
              public override bool CanConvert(Type typeToConvert) =>
                  typeToConvert == typeof(Optional<T>);
      
              public override Optional<T> Read(
                  ref Utf8JsonReader reader,
                  Type typeToConvert,
                  JsonSerializerOptions options) =>
                  new Optional<T>(JsonSerializer.Deserialize<T>(ref reader, options));
      
              public override void Write(
                  Utf8JsonWriter writer,
                  Optional<T> value,
                  JsonSerializerOptions options) =>
                  throw new NotImplementedException("OptionalValue is not suppose to be written");
          }
      
      
    4. 就是这样。现在有3种状态:

      [HttpPatch]
      [Consumes("application/merge-patch+json")]
      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
          }
      }