问题描述
我在哪里,我们在很大程度上依赖于通过JSON.NET邮件正文为.NET对象的JSON反序列化的自动支持一个ASP.NET的WebAPI codeBase的工作。
I'm working in an ASP.NET webapi codebase where we rely heavily on the automatic support for JSON deserialization of message bodies into .NET objects via JSON.NET.
作为建设为我们的资源之一补丁支持的一部分,我非常喜欢在JSON对象,这不是present,对同一财产这是明确为null一个可选属性来区分。我的目的是使用第一个为不改变那里的东西与删除这件事了。
As part of building out patch support for one of our resources, I'd very much like to distinguish between an optional property in the JSON object that's not present, vs. that same property that's explicitly to null. My intention is to use the first for "don't change what's there" vs. "delete this thing."
有谁知道,如果有可能,这样,当他们正在反序列化的JSON.NET能告诉我,这是这种情况下,以纪念我的C#的DTO?现在,他们只是拿出为空,我不知道为什么。
Does anyone know if it's possible to mark up my C# DTOs so that when they're deserialized that JSON.NET can tell me which case it was? Right now they're just come up as null, and I can't tell why.
相反,如果任何人都可以想出一个更好的设计,不需要我做这种方式,同时仍然支持补丁动词,我很想听听你的建议。
Conversely, if anyone can come up with a better design that doesn't require me to do it this way while still supporting the patch verb, I'd love to hear your proposal.
作为一个具体的例子,可以考虑这样的有效载荷,将被传递给放
As a concrete example, consider this payload that would be passed to put:
{
"field1": "my field 1",
"nested": {
"nested1": "something",
"nested2": "else"
}
}
现在,如果我只是想更新FIELD1,我应该能够发送此作为HTTP补丁:
Now, if I just wanted to update field1, I should be able to send this as an HTTP patch:
{
"field1": "new field1 value"
}
和嵌套值将保持不变。不过,如果我送这样的:
and the nested values would remain untouched. However, if I sent this:
{
"nested": null
}
我想知道这意味着我应该明确地删除嵌套数据。
I want to know this means I should explicitly remove the nested data.
推荐答案
如果您使用Json.Net的(JTokens,JObjects等)来解析JSON,你可以告诉一个空
值和根本没有按字段之间的区别'T在JSON存在。例如:
If you use Json.Net's LINQ-to-JSON API (JTokens, JObjects, etc.) to parse the JSON, you can tell the difference between a null
value and a field that simply doesn't exist in the JSON. For example:
JToken root = JToken.Parse(json);
JToken nested = root["nested"];
if (nested != null)
{
if (nested.Type == JTokenType.Null)
{
Console.WriteLine("nested is set to null");
}
else
{
Console.WriteLine("nested has a value: " + nested.ToString());
}
}
else
{
Console.WriteLine("nested does not exist");
}
小提琴:
更新
如果您正在使用Web API反序列化到具体的对象,您仍然可以通过创建一个自定义的 JsonConverter
来处理你的DTO的使用上面的概念。美中不足的是,需要有对你的DTO的地方反序列化期间存储领域的地位。我会建议使用基于字典的方案是这样的:
If you're deserializing into concrete objects using Web API, you can still use the above concept by creating a custom JsonConverter
to handle your DTOs. The catch is that there needs to be a place on your DTOs to store the field status during deserialization. I would suggest using a dictionary-based scheme like this:
enum FieldDeserializationStatus { WasNotPresent, WasSetToNull, HasValue }
interface IHasFieldStatus
{
Dictionary<string, FieldDeserializationStatus> FieldStatus { get; set; }
}
class FooDTO : IHasFieldStatus
{
public string Field1 { get; set; }
public BarDTO Nested { get; set; }
public Dictionary<string, FieldDeserializationStatus> FieldStatus { get; set; }
}
class BarDTO : IHasFieldStatus
{
public int Num { get; set; }
public string Str { get; set; }
public bool Bool { get; set; }
public decimal Dec { get; set; }
public Dictionary<string, FieldDeserializationStatus> FieldStatus { get; set; }
}
在自定义转换器,然后将使用上述LINQ到JSON技术来读取JSON的对象被反序列化。对于目标对象的每个字段,将项目添加到该对象的 FieldStatus
字典指示字段是否有一个值,被明确地设置为null或不存在JSON。这里是什么code可能是这样的:
The custom converter would then use above LINQ-to-JSON technique to read the JSON for the object being deserialized. For each field in the target object, it would add an item to that object's FieldStatus
dictionary indicating whether the field had a value, was explicitly set to null or did not exist in the JSON. Here is what the code might look like:
class DtoConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return (objectType.IsClass &&
objectType.GetInterfaces().Any(i => i == typeof(IHasFieldStatus)));
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var jsonObj = JObject.Load(reader);
var targetObj = (IHasFieldStatus)Activator.CreateInstance(objectType);
var dict = new Dictionary<string, FieldDeserializationStatus>();
targetObj.FieldStatus = dict;
foreach (PropertyInfo prop in objectType.GetProperties())
{
if (prop.CanWrite && prop.Name != "FieldStatus")
{
JToken value;
if (jsonObj.TryGetValue(prop.Name, StringComparison.OrdinalIgnoreCase, out value))
{
if (value.Type == JTokenType.Null)
{
dict.Add(prop.Name, FieldDeserializationStatus.WasSetToNull);
}
else
{
prop.SetValue(targetObj, value.ToObject(prop.PropertyType, serializer));
dict.Add(prop.Name, FieldDeserializationStatus.HasValue);
}
}
else
{
dict.Add(prop.Name, FieldDeserializationStatus.WasNotPresent);
}
}
}
return targetObj;
}
public override bool CanWrite
{
get { return false; }
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
以上转换器将实现 IHasFieldStatus
接口的任何对象上工作。 (请注意,您不需要实现转换器中的 WriteJson
方法,除非你打算做序列化的东西定制为好。因为 CanWrite
返回false,转换器将不会被序列化时使用。)
The above converter will work on any object that implements the IHasFieldStatus
interface. (Note that you do not need to implement the WriteJson
method in the converter unless you intend to do something custom on serialization as well. Since CanWrite
returns false, the converter will not be used during serialization.)
现在,要在网页API的转换器,你需要将其插入配置。添加到您的的Application_Start()
方法:
Now, to use the converter in Web API, you need to insert it into the configuration. Add this to your Application_Start()
method:
var config = GlobalConfiguration.Configuration;
var jsonSettings = config.Formatters.JsonFormatter.SerializerSettings;
jsonSettings.Converters.Add(new DtoConverter());
如果您preFER,可以装饰每个DTO与 [JsonConverter]
属性这样的,而不是设置在全局配置的转换器:
If you prefer, you can decorate each DTO with a [JsonConverter]
attribute like this instead of setting the converter in the global config:
[JsonConverter(typeof(DtoConverter))]
class FooDTO : IHasFieldStatus
{
...
}
有了转换器的基础设施,就可以询问在DTO的 FieldStatus
词典反序列化后,看看发生了什么事情对于任何特定的领域。这里是一个完整的演示(控制台应用程序):
With the converter infrastructure in place, you can then interrogate the FieldStatus
dictionary on the DTO after deserialization to see what happened for any particular field. Here is a full demo (console app):
public class Program
{
public static void Main()
{
ParseAndDump("First run", @"{
""field1"": ""my field 1"",
""nested"": {
""num"": null,
""str"": ""blah"",
""dec"": 3.14
}
}");
ParseAndDump("Second run", @"{
""field1"": ""new field value""
}");
ParseAndDump("Third run", @"{
""nested"": null
}");
}
private static void ParseAndDump(string comment, string json)
{
Console.WriteLine("--- " + comment + " ---");
JsonSerializerSettings settings = new JsonSerializerSettings();
settings.Converters.Add(new DtoConverter());
FooDTO foo = JsonConvert.DeserializeObject<FooDTO>(json, settings);
Dump(foo, "");
Console.WriteLine();
}
private static void Dump(IHasFieldStatus dto, string indent)
{
foreach (PropertyInfo prop in dto.GetType().GetProperties())
{
if (prop.Name == "FieldStatus") continue;
Console.Write(indent + prop.Name + ": ");
object val = prop.GetValue(dto);
if (val is IHasFieldStatus)
{
Console.WriteLine();
Dump((IHasFieldStatus)val, " ");
}
else
{
FieldDeserializationStatus status = dto.FieldStatus[prop.Name];
if (val != null)
Console.Write(val.ToString() + " ");
if (status != FieldDeserializationStatus.HasValue)
Console.Write("(" + status + ")");
Console.WriteLine();
}
}
}
}
输出:
--- First run ---
Field1: my field 1
Nested:
Num: 0 (WasSetToNull)
Str: blah
Bool: False (WasNotPresent)
Dec: 3.14
--- Second run ---
Field1: new field value
Nested: (WasNotPresent)
--- Third run ---
Field1: (WasNotPresent)
Nested: (WasSetToNull)
小提琴:
这篇关于是否有Json.NET序列化的方式之间的&QUOT区分,null,因为没有present&QUOT;和&QUOT; null,因为空&QUOT;?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!