我想创建和端点符合" JSON Merge Patch" https://tools.ietf.org/html/rfc7396
请不要将它与" JavaScript Object Notation(JSON)Patch"混淆。 https://tools.ietf.org/html/rfc6902
但是,我在区分请求中的两种情况时遇到了一些问题:
删除属性值,此处删除了电子邮件值:
{
surname: "Kowalski"
email: null
}
属性未包括在内,因为客户端根本不想更新它,此处不包含电子邮件,因为它不应该更新:
{
surname: "Kowalski"
}
出现问题是因为在模型绑定后的两种情况下,电子邮件的值都为null。
您是否有建议如何实施?
答案 0 :(得分:4)
此处您需要3种不同的电子邮件状态:
test@mail.com
)null
值所以问题实际上是如何在模型的string
属性中表达这3个状态。您不能仅使用原始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
属性,则OptionalValue
将HasValue
设置为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
}
}
答案 1 :(得分:2)
当使用不支持JavaScript和TypeScript那样的undefined
和null
之间的区别的语言时,这是一个特殊的问题。您可能还会考虑其他选项:
""
删除它,因为空字符串通常不是有效值(也不总是可行的)X-MYAPP-SET-EMAIL=true
将删除电子邮件,如果它为null)。不利之处在于,这可能会炸毁您的请求,并给客户开发人员带来痛苦上面的每个选项都有其自身的缺点,因此在决定走哪条路之前要仔细考虑。
答案 2 :(得分: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
答案 3 :(得分:1)
我以相同的问题来到这个话题。我的解决方案与“ CodeFuller”类似,但由于它使用更少的代码,涵盖了API文档,而且更加完善,因此更加完整。它还使用System.text.json
instead of the Newtonsoft
库。
通过充分利用the existent Optional
struct来定义模型(无需创建新的OptionalValue
类)
{
public string Surname { get; set; }
[JsonConverter(typeof(OptionalConverter<string>))]
public Optional<string> Email { get; set; } = default;
}
告诉Swagger(如果适用)将其格式化为字符串输入/类型,以获得更好的客户体验:
c.MapType<Optional<string>>(() => new OpenApiSchema { Type = "string" });
添加基于System.text.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");
}
就是这样。现在您有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
}
}