2013-11-26 2 views
1

Я отразил код класса JSON.NET JavaScriptDateTimeConverter, скопировал его и переименовал класс AS3DateTimeConverter, чтобы изменить его для форматирования объектов DateTime в более точной и строго типизированной усадьбе.Исключение JSON.NET при десериализации значения DateTime

она у меня выходной тип в соответствии с тем, как JSON.NET выходы строго типизированных объектов, таких как так: {"$type":"System.DateTime, mscorlib","ticks":0}

Переопределенный метод WriteJson из JsonConverter работает, чтобы произвести это значение.

Однако, когда я пытаюсь десериализации строки, используя те же параметры с тем же конвертером переопределен метод ReadJson никогда не получает шанс запустить и построить DateTime от тиков собственности, поскольку происходят следующие ошибки:

не может десериализации текущего объекта JSON (например, { «имя»: «значение»}) в тип «System.DateTime», так как тип требует примитивного значения JSON (например, строка, число, логическое значение, нуль) для десериализуем правильно.

Чтобы исправить эту ошибку, либо изменить 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 
+0

На самом деле это не будет хранить тики, он будет хранить миллисекунды, прошедшие с 1 января 1970 года, возможно, наряду с тиками для лучшей точности округления, но дело в том, что переопределенный метод ReadJson от JsonConverter никогда не дает возможности run, предположительно потому, что DateTime - это тип значения вместо класса? – Triynko

+0

Можете ли вы показать код конвертера? –

+0

Я мог бы, но ничего не стоило бы. Он просто расширяет встроенный JsonConverter и переопределяет методы WriteJson и ReadJson. Метод WriteJson работает нормально и создает строку JSON, которую я отобразил. Проблема в том, что несмотря на строку, включающую '' $ type ":« System.DateTime », метод CanConvert преобразователя никогда не вызывается с этим типом, и метод ReadJson преобразователя никогда не вызывается. Это похоже на то, что он даже не пытается использовать мой конвертер или даже тестирование, чтобы проверить, можно ли его использовать. Возможно, это серьезная проблема, но я думал, что это было автоматически. – Triynko

ответ

0

Проблема, кажется, что-то делать с десериализации голой даты. Когда дата завернута в другой объект, он работает. Этот код работает для меня:

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 

UPDATE

Чтобы проверить теорию о том, дозвонились конвертеры для объектов уровня пользовательского топа с информацией внедренного типа, я сделал конвертер для объекта-оболочки даты и вместо этого сериализуется. Это сработало, но только если я дал ему подсказку, используя 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 
+0

Это работает, но опять же, класс верхнего уровня никогда не будет иметь конвертер. Вы можете подтвердить это поведение, добавив еще один конвертер для типа «TestObject» и запустив тот же код. Он никогда не вызовет метод CanConvert TestObjectConverter. – Triynko

+0

Я начинаю задаваться вопросом, является ли это проблемой разрешения сборки. Будет ли иметь значение, что мой AS3DateTimeConverter находится в другой сборке из основного приложения? Это статическая ссылка, поэтому она не должна быть проблемой, хотя я заметил, что она не может найти тип, если я не вставляю имя сборки в тип $. – Triynko

+0

Если он находится в другой сборке, вам, вероятно, нужно встроить имя сборки в '$ type' так же, как и для' DateTime' (часть 'mscorlib'). –

0

Обход я нашел.

Если объект завернут в список перед его сериализацией, то он работает нормально, но только если вы украсите класс атрибутом JsonConverter, определяющим преобразователь. Недостаточно добавить конвертер в список преобразователей для настроек сериализаторов.

Например, если у вас есть класс «Node», у которого есть член «Child» Node (т. Е. Тип имеет свой собственный тип), и вы вставляете некоторые узлы, то то, что я нашел, конвертер не вызывается во время сериализации ни для чего, кроме верхнего узла, когда вы только добавляете конвертер в список преобразователей. С другой стороны, если вы явно украшаете класс конвертером, то все дочерние узлы запускаются через метод WriteJson преобразователя, как и ожидалось. Таким образом, это в основном делает набор «Конвертеры» настроек сериализатора нефункциональным.

Когда объекты являются элементами массива и их тип украшен явным конвертером, тогда их методы ReadJson и WriteJson своего конвертера вызывают, когда типы встречаются во время сериализации и десериализации.

При получении строки JSON от клиента есть только два варианта, чтобы заставить его работать. Либо вы вручную переносите строку в объект, включая общий список «Тип», и вставляете полученное значение в качестве единственного значения в массив «$ values», или вы должны просто избегать всего этого и жестко кодировать ожидаемый тип получаемого объекта позвонив набранный метод DeserializeObject<T>. Какой беспорядок.

Единственная причина, по которой я могу судить об этом, заключается в том, что метод DeserializeObject (не общий) был явно предназначен для НЕ вызывать преобразователи для объекта верхнего уровня, предположительно, чтобы его можно было использовать в WriteJson пользовательского конвертера метод, не вызывающий рекурсивных вызовов в конвертер. Если это так, дизайн ужасен, потому что он приводит ко всем проблемам, которые я обсуждал.

 Смежные вопросы

  • Нет связанных вопросов^_^