【问题标题】:How to configure JSON.net deserializer to track missing properties?如何配置 JSON.net 反序列化器以跟踪丢失的属性?
【发布时间】:2015-05-18 10:25:38
【问题描述】:

示例类:

public class ClassA
{
    public int Id { get; set; }
    public string SomeString { get; set; }
    public int? SomeInt { get; set; }
}

默认反序列化器:

var myObject = JsonConvert.DeserializeObject<ClassA>(str);

为两个不同的输入创建相同的对象

{"Id":5}

{"Id":5,"SomeString":null,"SomeInt":null} 

如何跟踪反序列化过程中丢失的属性并保持相同的行为?有没有办法覆盖一些 JSON.net 序列化方法(例如 DefaultContractResolver 类方法)来实现这一点。例如:

List<string> missingProps;
var myObject = JsonConvert.DeserializeObject<ClassA>(str, settings, missingProps);

第一个输入列表应包含缺失属性的名称(“SomeString”、“SomeInt”),第二个输入应为空。反序列化的对象保持不变。

【问题讨论】:

  • 这听起来有点像XY problem。您能否详细说明为什么需要那些“缺失的属性”?
  • 您真的只想跟踪它们而不是将它们设置为某个有效值吗?

标签: c# json.net


【解决方案1】:

1。 JSON 有一个你的类中缺少的属性

使用属性JsonSerializerSettings.MissingMemberHandling 可以说明是否将缺少的属性作为错误处理。

您可以安装Error 委托,它会记录错误。

这将检测 JSON 字符串中是否存在一些“垃圾”属性。

public class ClassA
{
    public int Id { get; set; }
    public string SomeString { get; set; }
}

internal class Program
{
    private static void Main(string[] args)
    {
        const string str = "{'Id':5, 'FooBar': 42 }";
        var myObject = JsonConvert.DeserializeObject<ClassA>(str
            , new JsonSerializerSettings
            {
                Error = OnError,
                MissingMemberHandling = MissingMemberHandling.Error
            });

        Console.ReadKey();
    }

    private static void OnError(object sender, ErrorEventArgs args)
    {
        Console.WriteLine(args.ErrorContext.Error.Message);
        args.ErrorContext.Handled = true;
    }
}

2。您的班级有一个 JSON 中缺少的属性

选项1:

使其成为必需的属性:

    public class ClassB
    {
        public int Id { get; set; }

        [JsonProperty(Required = Required.Always)]
        public string SomeString { get; set; }

    }
选项 2:

使用一些“特殊”值作为默认值,然后检查。

public class ClassB
{
    public int Id { get; set; }

    [DefaultValue("NOTSET")]
    public string SomeString { get; set; }
    public int? SomeInt { get; set; }
}

internal class Program
{
    private static void Main(string[] args)
    {
        const string str = "{ 'Id':5 }";
        var myObject = JsonConvert.DeserializeObject<ClassB>(str
            , new JsonSerializerSettings
            {
                DefaultValueHandling = DefaultValueHandling.Populate
            });

        if (myObject.SomeString == "NOTSET")
        {
            Console.WriteLine("no value provided for property SomeString");
        }

        Console.ReadKey();
    }
}
选项 3:

另一个好主意是将这个检查封装在类本身之外。如下图创建Verify()方法,反序列化后调用。

public class ClassC
{
    public int Id { get; set; }

    [DefaultValue("NOTSET")]
    public string SomeString { get; set; }
    public int? SomeInt { get; set; }

    public void Verify()
    {
        if (SomeInt == null ) throw new JsonSerializationException("SomeInt not set!");
        if (SomeString == "NOTSET") throw new JsonSerializationException("SomeString not set!");
    }
}

【讨论】:

  • 如果您认为:MissingMemberHandling = MissingMemberHandling.Error,它不起作用,请参阅stackoverflow.com/questions/18147404/…
  • Case 1 并不能完全正常工作,永远不会因为问题中的缺失属性而引发错误。见这个例子dotnetfiddle.net/sQ1vQp
  • @Milos 案例 1:当 JSON 有一些你的类没有的垃圾属性时。情况 2:当您的类具有在反序列化期间未填写的属性时。由于反序列化器必须高效,因此它不会检查您的类中是否存在“额外”属性。如果反序列化程序未设置某些属性,您的类必须小心。
  • @Milos 我想我找到了更好的解决方案。请参阅我的编辑选项 1。
【解决方案2】:

在反序列化期间查找空/未定义令牌的另一种方法是编写自定义 JsonConverter ,这是一个自定义转换器的示例,它可以报告省略的令牌(例如 "{ 'Id':5 }")和空令牌(例如 @987654323 @)

public class NullReportConverter : JsonConverter
{
    private readonly List<PropertyInfo> _nullproperties=new List<PropertyInfo>();
    public bool ReportDefinedNullTokens { get; set; }

    public IEnumerable<PropertyInfo> NullProperties
    {
        get { return _nullproperties; }
    }

    public void Clear()
    {
        _nullproperties.Clear();
    }

    public override bool CanConvert(Type objectType)
    {
        return true;
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        existingValue = existingValue ?? Activator.CreateInstance(objectType, true);

        var jObject = JObject.Load(reader);
        var properties =
            objectType.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);

        foreach (var property in properties)
        {
            var jToken = jObject[property.Name];
            if (jToken == null)
            {
                _nullproperties.Add(property);
                continue;
            }

            var value = jToken.ToObject(property.PropertyType);
            if(ReportDefinedNullTokens && value ==null)
                _nullproperties.Add(property);

            property.SetValue(existingValue, value, null);
        }

        return existingValue;
    }

    //NOTE: we can omit writer part if we only want to use the converter for deserializing
    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        var objectType = value.GetType();
        var properties =
            objectType.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);

        writer.WriteStartObject();
        foreach (var property in properties)
        {
            var propertyValue = property.GetValue(value, null);
            writer.WritePropertyName(property.Name);
            serializer.Serialize(writer, propertyValue);
        }

        writer.WriteEndObject();
    }
}

注意:如果我们不需要将 Writer 部分用于序列化对象,我们可以省略它。

用法示例:

class Foo
{
    public int Id { get; set; }
    public string SomeString { get; set; }
    public int? SomeInt { get; set; }
}

class Program
{
    static void Main(string[] args)
    {
        var nullConverter=new NullReportConverter();

        Console.WriteLine("Pass 1");
        var obj0 = JsonConvert.DeserializeObject<Foo>("{\"Id\":5, \"Id\":5}", nullConverter);
        foreach(var p in nullConverter.NullProperties)
            Console.WriteLine(p);

        nullConverter.Clear();

        Console.WriteLine("Pass2");
        var obj1 = JsonConvert.DeserializeObject<Foo>("{\"Id\":5,\"SomeString\":null,\"SomeInt\":null}" , nullConverter);
        foreach (var p in nullConverter.NullProperties)
            Console.WriteLine(p);

        nullConverter.Clear();

        nullConverter.ReportDefinedNullTokens = true;
        Console.WriteLine("Pass3");
        var obj2 = JsonConvert.DeserializeObject<Foo>("{\"Id\":5,\"SomeString\":null,\"SomeInt\":null}", nullConverter);
        foreach (var p in nullConverter.NullProperties)
            Console.WriteLine(p);
    }
}

【讨论】:

    【解决方案3】:

    我遇到了这个问题,但由于 POCO 对象,defaultValue 不是解决方案。我认为这是比 NullReportConverter 更简单的方法。 有三个单元测试。 Root 是封装整个 json 的类。键是属性的类型。希望这对某人有所帮助。

    using Microsoft.VisualStudio.TestTools.UnitTesting;
    using Newtonsoft.Json;
    
    namespace SomeNamespace {
      [TestClass]
      public class NullParseJsonTest {
        [TestMethod]
        public void TestMethod1()
        {
          string slice = "{Key:{guid:\"asdf\"}}";
          var result = JsonConvert.DeserializeObject<Root>(slice);
    
          Assert.IsTrue(result.OptionalKey.IsSet);
          Assert.IsNotNull(result.OptionalKey.Value);
          Assert.AreEqual("asdf", result.OptionalKey.Value.Guid);
        }
    
        [TestMethod]
        public void TestMethod2()
        {
          string slice = "{Key:null}";
          var result = JsonConvert.DeserializeObject<Root>(slice);
    
          Assert.IsTrue(result.OptionalKey.IsSet);
          Assert.IsNull(result.OptionalKey.Value);
        }
    
        [TestMethod]
        public void TestMethod3()
        {
          string slice = "{}";
          var result = JsonConvert.DeserializeObject<Root>(slice);
    
          Assert.IsFalse(result.OptionalKey.IsSet);
          Assert.IsNull(result.OptionalKey.Value);
        }
      }
    
      class Root {
    
        public Key Key {
          get {
            return OptionalKey.Value;
          }
          set {
            OptionalKey.Value = value;
            OptionalKey.IsSet = true;   // This does the trick, it is never called by JSON.NET if attribute missing
          }
        }
    
        [JsonIgnore]
        public Optional<Key> OptionalKey = new Optional<Key> { IsSet = false };
      };
    
    
      class Key {
        public string Guid { get; set; }
      }
    
      class Optional<T> {
        public T Value { get; set; }
        public bool IsSet { get; set; }
      }
    }
    

    【讨论】:

      猜你喜欢
      • 2016-02-29
      • 2019-08-27
      • 2021-05-26
      • 1970-01-01
      • 2019-07-09
      • 2011-01-16
      • 1970-01-01
      • 1970-01-01
      • 2015-02-16
      相关资源
      最近更新 更多