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

时间:2013-11-26 17:32:40

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

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

我根据JSON.NET如何输出强类型对象输出类型,如下所示: {"$type":"System.DateTime, mscorlib","ticks":0}

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

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

  

无法反序列化当前的JSON对象(例如{“name”:“value”})   进入'System.DateTime'类型,因为该类型需要一个JSON原语   正确反序列化的值(例如string,number,boolean,null)。

     

要修复此错误,请将JSON更改为JSON原始值   (例如string,number,boolean,null)或更改反序列化类型   所以它是一个普通的.NET类型(例如,不像原始类型   可以是整数,而不是类似数组或List的集合类型   从JSON对象反序列化。也可以添加JsonObjectAttribute   到类型强制它从JSON对象反序列化。

     

路径'滴答',第1行,第45位。

这是某种bug还是限制,它不允许我恢复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

2 个答案:

答案 0 :(得分:0)

这个问题似乎与反序列化一个简单的日期有关。当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

答案 1 :(得分:0)

我找到的解决方法。

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

例如,如果你有一个&#34; Node&#34;有一个&#34;孩子&#34;节点成员(即类型具有它自己类型的成员),并且您嵌套了一些节点,然后我发现在序列化期间除了顶级节点之外没有调用转换器将转换器添加到转换器列表中。另一方面,如果使用转换器显式地修饰类,则所有子节点都按预期运行转换器的WriteJson方法。所以这基本上呈现了转换器&#34;序列化程序设置的集合无法使用。

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

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

我能理解任何这个有意义的唯一原因是,如果DeserializeObject(非泛型)方法明确意图不调用顶级对象的转换器,可能是因为它可以用于自定义转换器&#39 ; s WriteJson方法,不会导致转换器的递归调用。如果是这样,设计很糟糕,因为它会导致我讨论过的所有问题。