我在ASP.NET Webapi代码库中工作,我们在很大程度上依赖对通过JSON.NET将消息主体从JSON反序列化为.NET对象的自动支持。
作为构建对我们资源之一的补丁程序支持的一部分,我非常想区分JSON对象中不存在的可选属性和显式为null的相同属性。我的意图是将第一个用于“不要更改其中的内容”还是“删除此内容”。
有谁知道是否可以标记我的C#DTO,以便在对它们进行反序列化时JSON.NET可以告诉我是哪种情况?现在,它们只是空值,我不知道为什么。
相反,如果任何人都可以提出一个更好的设计,而该设计不需要我在仍支持补丁动词的情况下以这种方式进行操作,那么我很想听听您的建议。
作为一个具体示例,请考虑将要传递的有效负载:
{
"field1": "my field 1",
"nested": {
"nested1": "something",
"nested2": "else"
}
}
现在,如果我只想更新field1,我应该能够将其作为HTTP补丁发送:
{
"field1": "new field1 value"
}
并且嵌套的值将保持不变。但是,如果我发送此邮件:
{
"nested": null
}
我想知道这意味着我应该显式删除嵌套数据。
如果使用Json.Net的LINQ-to-JSON
API
(JTokens,JObjects等)来解析JSON,则可以分辨出JSON
null
中根本不存在的值和字段之间的区别。例如:
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");
}
小提琴:https://dotnetfiddle.net/VJO7ay
更新
如果要使用Web
API反序列化为具体对象,则仍可以通过创建自定义JsonConverter
来处理DTO来使用上述概念。问题是在反序列化期间,您的DTO上需要有一个位置来存储字段状态。我建议使用像这样的基于字典的方案:
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-to-
JSON技术来读取要反序列化的对象的JSON。对于目标对象中的每个字段,它将在该对象的FieldStatus
字典中添加一个项目,指示该字段是否具有值,是否已显式设置为null或在JSON中不存在。代码如下所示:
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,因此在序列化期间将不使用转换器。)
现在,要在Web API中使用转换器,您需要将其插入配置中。将此添加到您的Application_Start()
方法:
var config = GlobalConfiguration.Configuration;
var jsonSettings = config.Formatters.JsonFormatter.SerializerSettings;
jsonSettings.Converters.Add(new DtoConverter());
如果愿意,可以用这样的[JsonConverter]
属性装饰每个DTO,而不用在全局配置中设置转换器:
[JsonConverter(typeof(DtoConverter))]
class FooDTO : IHasFieldStatus
{
...
}
有了转换器基础结构之后,您可以FieldStatus
在反序列化之后在DTO上查询字典,以查看任何特定字段发生了什么。这是完整的演示(控制台应用程序):
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)
小提琴:https :
//dotnetfiddle.net/xyKrg2
我有一些可为空的值,希望在访问它们的值时避免异常。下面展示了我想要实现的一个示例。我目前有 但我看到可为空的值有一个属性。等价物将是 有什么方法可以把代码简化成这样吗? 以便它只在不为空时运行?
问题内容: 我想使用CSS来呈现两列布局。我正在使用的标记是 有没有一种方法可以使一列的宽度为20px,而一列的宽度为80px? 问题答案: 不,没有办法。 该功能专为在相等的列之间流动的内容而设计。
问题内容: 我想使用java.util.Preferences API,但是我不想让程序尝试读取或写入Windows注册表。我将如何处理? 问题答案: 我相信您已经使用Java读取了对Windows注册表的读/写操作,然后您希望在使用API 时拥有另一个不同于注册表的后端 如本文所述,您可以像Bernhard或Croft一样扩展API: 因为首选项API是后端中立的,所以您不必关心数据是存储在文件
我有一个包含映射属性的json字符串,如 当Jackson将其反序列化到包含an属性的类中时,它似乎执行了。而在序列化时,它将把对象序列化为相同的格式。 是否有一种方法让jackson正确反序列化到一个只有注解的映射中,或者我需要做一个自定义序列化器。 带有getter/setter的示例对象:
简而言之:有没有一种方法可以在gcc或CLANG中不推荐命名空间? 长: 现在我想知道是否有更好的方法来做类似的事情,比如将名称空间util的使用标记为不推荐使用。 我们使用GCC4.7.3作为生产编译器,但是针对clang进行构建和测试,以尝试捕捉gcc的细节;因此,在这些编译器上工作的东西会有所帮助。
当记录一个会话与wiremock独立,我得到的映射文件生成其中包括一个链接到身体与相同的鉴别器。 是否有一种方法可以控制(种子)鉴别器的命名,以便我可以让录制在每个录制会话上生成相同的存根名称? 这会让我的git历史不那么混乱!