反序列化DateTime值时出现JSON.NET异常



我反映了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基元值(例如字符串、数字、布尔值、null)或更改反序列化的类型以便它是正常的.NET类型(例如,不是像integer,而不是像数组或List这样的集合类型)从JSON对象反序列化。还可以添加JsonObjectAttribute到类型,以强制它从JSON对象反序列化。

路径"ticks",第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

这个问题似乎与反序列化空日期有关。当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<T>而不是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

我找到了解决方法。

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

例如,如果你有一个"Node"类,它有一个"Child"Node成员(即该类型有它自己类型的成员),并且你嵌套了一些节点,那么我发现,当你只将转换器添加到转换器列表中时,除了顶部节点之外,在序列化过程中不会调用转换器。另一方面,如果用转换器显式地修饰类,那么所有子节点都会按预期通过转换器的WriteJson方法运行。因此,这基本上会使序列化程序设置的"Converters"集合不起作用。

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

当从客户端接收JSON字符串时,只有两个选项可以使其工作。您可以手动将字符串包装在一个包含泛型List"$type"的对象中,并将接收到的值作为唯一值嵌入到"$values"数组中,也可以通过调用类型化的DeserializeObject<T>方法来避免所有这些并硬编码预期的接收对象类型。真是一团糟。

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

相关内容

  • 没有找到相关文章

最新更新