【问题标题】:JSON.NET Exception when deserializing a DateTime value反序列化 DateTime 值时出现 JSON.NET 异常
【发布时间】:2013-12-12 00:09:06
【问题描述】:

我反映了 JSON.NET JavaScriptDateTimeConverter 类代码,复制了它,并将类重命名为 AS3DateTimeConverter,以便我可以修改它以更精确和强类型的庄园格式化 DateTime 对象。

我让它根据 JSON.NET 输出强类型对象的方式输出一个类型,如下所示: {"$type":"System.DateTime, mscorlib","ticks":0}

JsonConverter 的重写 WriteJson 方法运行以生成该值。

但是,当我尝试使用完全相同的设置和相同的转换器反序列化字符串时,被覆盖的 ReadJson 方法永远不会有机会运行并从 ticks 属性构造 DateTime,因为会发生以下错误:

无法反序列化当前 JSON 对象(例如 {"name":"value"}) 进入类型“System.DateTime”,因为该类型需要 JSON 原语 正确反序列化的值(例如字符串、数字、布尔值、null)。

要修复此错误,请将 JSON 更改为 JSON 原始值 (例如字符串、数字、布尔值、空值)或更改反序列化类型 所以它是一个普通的 .NET 类型(例如,不是像 整数,而不是数组或列表之类的集合类型) 从 JSON 对象反序列化。也可以添加 JsonObjectAttribute 强制它从 JSON 对象反序列化。

路径“记号”,第 1 行,位置 45。

这是否是某种错误或限制,因为它是值类型,所以不允许我恢复 DateTime 类型?还是我错过了什么?

这里是序列化设置:

    JsonSerializerSettings settings = new JsonSerializerSettings();
    settings.ReferenceLoopHandling = ReferenceLoopHandling.Serialize;
    settings.PreserveReferencesHandling = PreserveReferencesHandling.All;
    settings.ObjectCreationHandling = ObjectCreationHandling.Replace;
    settings.ConstructorHandling = ConstructorHandling.Default;
    settings.TypeNameHandling = TypeNameHandling.All;
    settings.TypeNameAssemblyFormat = System.Runtime.Serialization.Formatters.FormatterAssemblyStyle.Simple;
    settings.DateFormatHandling = DateFormatHandling.IsoDateFormat;
    settings.DateParseHandling = DateParseHandling.DateTime;
    settings.Converters.Add( new AS3DateTimeConverter() );
    //settings.Binder = new AS3SerializationBinder();
    string s = JsonConvert.SerializeObject( new DateTime( 1970, 1, 1, 0, 0, 0, DateTimeKind.Utc ), settings );
    object o = JsonConvert.DeserializeObject( s, settings ); //s = "{\"$type\":\"System.DateTime, mscorlib\",\"ticks\":0}" //ERROR OCCURS HERE

【问题讨论】:

  • 这实际上不会存储滴答声,它将存储自 1970 年 1 月 1 日以来经过的毫秒数,可能还会存储滴答声以获得更好的往返精度,但关键是 JsonConverter 的重写 ReadJson 方法永远不会甚至有机会运行,大概是因为 DateTime 是值类型而不是类?
  • 你能显示你的转换器代码吗?
  • 我可以,但这不值得。它只是扩展了内置的 JsonConverter 并覆盖了 WriteJson 和 ReadJson 方法。 WriteJson 方法运行良好并生成我显示的 JSON 字符串。问题是,尽管字符串包括"$type":"System.DateTime",但转换器的 CanConvert 方法永远不会使用该类型调用,并且转换器的 ReadJson 方法永远不会被调用。就好像它甚至没有尝试使用我的转换器,甚至没有测试它是否可以使用。也许这是一个绑定问题,但我认为这是自动的。
  • 我还尝试修改 WriteJson 方法以将对象写入称为“DateTimeWrapper”的自定义引用类型类,该类仅存储单个整数“ticks”,因此字符串{"$type":"mynamespace.DateTimeWrapper","ticks":0} 由序列化,但奇怪的是,在尝试反序列化字符串时,我的 JsonConverter 的 CanConvert 方法被调用,但它接收整数类型而不是 mynamespace.DateTimeWrapper 类型。我觉得这很奇怪。就像认为它在反序列化过程中忽略了嵌入在 JSON 对象字符串中的“$type”。
  • 我只是能够重现该问题。你是对的,如果你只是想反序列化一个裸露的DateTime,转换器不会被调用。但是,如果您将 DateTime 包装在另一个对象中,它就可以工作。

标签: json serialization json.net converter strong-typing


【解决方案1】:

这个问题似乎与反序列化裸日期有关。当 Date 被包裹在另一个对象中时,它似乎可以工作。这段代码对我有用:

public class Program
{
    public static void Main(string[] args)
    {
        JsonSerializerSettings settings = new JsonSerializerSettings();
        settings.ReferenceLoopHandling = ReferenceLoopHandling.Serialize;
        settings.PreserveReferencesHandling = PreserveReferencesHandling.All;
        settings.ObjectCreationHandling = ObjectCreationHandling.Replace;
        settings.ConstructorHandling = ConstructorHandling.Default;
        settings.TypeNameHandling = TypeNameHandling.All;
        settings.TypeNameAssemblyFormat = System.Runtime.Serialization.Formatters.FormatterAssemblyStyle.Simple;
        settings.DateFormatHandling = DateFormatHandling.IsoDateFormat;
        settings.DateParseHandling = DateParseHandling.DateTime;
        settings.Converters.Add(new AS3DateTimeConverter());

        TestObject obj = new TestObject { Date = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc) };
        string s = JsonConvert.SerializeObject(obj, settings);
        Console.WriteLine(s);
        object o = JsonConvert.DeserializeObject(s, settings);
        Console.WriteLine(((TestObject)o).Date.ToString());
    }
}

public class TestObject
{
    public DateTime Date { get; set; }
}

public class AS3DateTimeConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return objectType == typeof(DateTime);
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        JObject jo = new JObject();
        jo.Add("$type", "System.DateTime, mscorlib");
        jo.Add("ticks", ((DateTime)value).Ticks);
        jo.WriteTo(writer);
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        JObject jo = JObject.Load(reader);
        return new DateTime(jo["ticks"].Value<long>());
    }
}

输出:

{"$id":"1","$type":"Q20224027.TestObject, JsonTest","Date":{"$type":"System.DateTime, mscorlib","ticks":621355968000000000}}
1/1/1970 12:00:00 AM

更新

为了测试转换器是否会为具有嵌入式类型信息的自定义顶级对象调用的理论,我为日期包装器对象制作了一个转换器并将其序列化。这有效,但前提是我使用DeserializeObject&lt;T&gt; 而不是DeserializeObject 给它一个提示。代码如下:

namespace Q20224027
{
    public class Program
    {
        public static void Main(string[] args)
        {
            JsonSerializerSettings settings = new JsonSerializerSettings();
            settings.ReferenceLoopHandling = ReferenceLoopHandling.Serialize;
            settings.PreserveReferencesHandling = PreserveReferencesHandling.All;
            settings.ObjectCreationHandling = ObjectCreationHandling.Replace;
            settings.ConstructorHandling = ConstructorHandling.Default;
            settings.TypeNameHandling = TypeNameHandling.All;
            settings.TypeNameAssemblyFormat = System.Runtime.Serialization.Formatters.FormatterAssemblyStyle.Simple;
            settings.DateFormatHandling = DateFormatHandling.IsoDateFormat;
            settings.DateParseHandling = DateParseHandling.DateTime;
            settings.Converters.Add(new DateWrapperConverter());

            DateWrapper obj = new DateWrapper { Date = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc) };
            string s = JsonConvert.SerializeObject(obj, settings);
            Console.WriteLine(s);
            object o = JsonConvert.DeserializeObject<DateWrapper>(s, settings);
            Console.WriteLine(((DateWrapper)o).Date.ToString());
        }
    }

    public class DateWrapper
    {
        public DateTime Date { get; set; }
    }

    public class DateWrapperConverter : JsonConverter
    {
        public override bool CanConvert(Type objectType)
        {
            return objectType == typeof(DateWrapper);
        }

        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            DateWrapper obj = (DateWrapper)value;
            JObject jo = new JObject();
            jo.Add("$type", typeof(DateWrapper).AssemblyQualifiedName);
            jo.Add("ticks", obj.Date.Ticks);
            jo.WriteTo(writer);
        }

        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {
            JObject jo = JObject.Load(reader);
            return new DateWrapper { Date = new DateTime(jo["ticks"].Value<long>()) };
        }
    }
}

输出:

{"$type":"Q20224027.DateWrapper, JsonTest, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null","ticks":621355968000000000}
1/1/1970 12:00:00 AM

【讨论】:

  • 这行得通,但同样,顶级类永远不会调用转换器。您可以通过为“TestObject”类型添加另一个转换器并运行相同的代码来确认该行为。它永远不会调用TestObjectConverter的CanConvert方法。
  • 我开始怀疑这是否是程序集解析问题。我的 AS3DateTimeConverter 与主应用程序位于不同的程序集中会有所不同吗?这是一个静态引用,所以应该不是问题,尽管我注意到它无法找到类型,除非我将程序集名称嵌入到 $type 中。
  • 如果它在不同的程序集中,那么您可能确实需要将程序集名称嵌入到 $type 中,就像对 DateTime 所做的那样(mscorlib 部分)。跨度>
  • 不,这也不是问题。我使用了您的代码并再次对单个程序集中的所有内容进行了测试,但问题仍然存在,顶级对象没有使用分配的转换器进行反序列化,尽管已使用它们进行序列化。
  • 我为 TestObject 包装器创建了一个转换器,我再次发现虽然转换器的 WriteJson 方法在序列化过程中被调用并且类型被正确嵌入,但转换器在反序列化时被忽略,它既不是 CanConvert 也不是它的 ReadJson 方法被调用。
【解决方案2】:

我找到了解决方法。

如果对象在序列化之前被包装在一个列表中,那么它可以正常工作,但前提是您使用指定转换器的 JsonConverter 属性装饰类。将转换器添加到序列化程序设置的转换器列表中是不够的。

例如,如果您有一个“节点”类,它有一个“子”节点成员(即该类型有它自己类型的成员),并且您嵌套了一些节点,那么我发现转换器当您仅将转换器添加到转换器列表时,在序列化期间除了顶部节点之外的任何内容都不会被调用。另一方面,如果您使用转换器显式装饰类,则所有子节点都会按预期通过转换器的 WriteJson 方法运行。所以这基本上会使序列化程序设置的“转换器”集合不起作用。

当对象是数组的成员并且它们的类型用显式转换器修饰时,当在序列化和反序列化过程中遇到类型时,将调用其转换器的 ReadJson 和 WriteJson 方法。

当从客户端接收 JSON 字符串时,只有两个选项可以让它工作。您可以手动将字符串包装在包含通用列表“$type”的对象中并将接收到的值作为唯一值嵌入“$values”数组中,或者您必须避免所有这些并硬编码预期的接收对象类型通过调用键入的DeserializeObject&lt;T&gt; 方法。真是一团糟。

我能理解这些有意义的唯一原因是 DeserializeObject(非泛型)方法是否明确打算不为顶级对象调用转换器,大概是为了它可以在自定义转换器的 WriteJson 方法中使用不会导致对转换器的递归调用。如果是这样,设计就很糟糕,因为它会导致我讨论过的所有问题。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-12-09
    • 1970-01-01
    • 1970-01-01
    • 2012-08-05
    相关资源
    最近更新 更多