2012-12-18 16 views
10

Aby serialize słownik z NodaTime.Instance do JSON przy użyciu Json.NET działa dobrze, ale po deserializacji rzuca Newtonsoft.Json. JsonSerializationException. poniżej Test pokazuje problem:Jak deserializować słownik z NodaTime.Instant przy użyciu Json.net bez uzyskiwania wyjątku?

[Test] 
public void DeserializeDictionaryThowsException() { 
    JsonConverter[] converters = { NodaConverters.IntervalConverter, NodaConverters.InstantConverter }; 

    var dictionary = new Dictionary<Instant, int>() { 
     {Instant.FromUtc(2012, 1, 2, 3, 4, 5), 0} 
    };    
    var json = JsonConvert.SerializeObject(dictionary, Formatting.None, converters); 
    Assert.AreEqual("{\"2012-01-02T03:04:05Z\":0}", json); //ok 
    var result = JsonConvert.DeserializeObject<Dictionary<Instant, int>>(json, converters); // throws 
} 

DeserializeObject rzuca:

Newtonsoft.Json.JsonSerializationException: nie można przekonwertować ciąg '2012-01-02T03: 04: 05Z' do słownika typ klucza „NodaTime .Natychmiastowy'. Utwórz TypeConverter, aby przekonwertować ciąg znaków na obiekt typu klucza. Wiersz 1, pozycja 24. ----> Newtonsoft.Json.JsonSerializationException: Błąd konwersji wartości "2012-01-02T03: 04: 05Z", aby wpisać "NodaTime.Instant". Linia 1, pozycja 24. ----> Wyjątek System.Exception: Nie można przesłać lub przekonwertować z System.String na NodaTime.Instant.

Na marginesie, deserializacja słownika DateTime działa poprawnie. Chyba dlatego, że String ma konwerter dla DateTime.

[Test] 
public void DeserializeDiciotnaryOfDateTime() // OK 
{ 
    var expected = new DateTime(2012, 1, 2, 3, 4, 5, DateTimeKind.Utc); 
    var dictionary = new Dictionary<DateTime, int>() { { expected, 0 } }; 
    var json = JsonConvert.SerializeObject(dictionary);  
    var result = JsonConvert.DeserializeObject<Dictionary<DateTime, int>>(json); 
    Assert.AreEqual(expected, dictionary.Keys.First()); // OK 
} 
+1

Niestety nie widziałem tego wcześniej. Nie wiem wystarczająco dużo o Json.NET, aby dać ci odpowiedź od razu, ale czy możesz zgłosić błąd na http://noda-time.googlecode.com? –

+1

Niestety, tęskniliśmy za tym wcześniej i dziękujemy za napisanie. Śledzimy ten problem [tutaj] (https://code.google.com/p/noda-time/issues/detail?id=237). –

Odpowiedz

0

Musisz dodać więcej konwerterów JSON.NET, aby serializować czas NodaTime.Instance, jak pokazano poniżej.

public void DeserializeDictionaryThowsException() 
{ 
    var dtzProvider = DateTimeZoneCache.GetSystemDefault(); 
    JsonConverter[] converters = { NodaConverters.IntervalConverter, 
            NodaConverters.InstantConverter, 
            NodaConverters.LocalDateConverter, 
            NodaConverters.LocalDateTimeConverter, 
            NodaConverters.LocalTimeConverter, 
            NodaConverters.OffsetConverter, 
            NodaConverters.DurationConverter, 
            NodaConverters.RoundtripPeriodConverter, 
            NodaConverters.OffsetDateTimeConverter, 
            NodaConverters.CreateDateTimeZoneConverter(dtzProvider), 
            NodaConverters.CreateZonedDateTimeConverter(dtzProvider) 
           }; 

    var dictionary = new Dictionary<Instant, int>() { { Instant.FromUtc(2012, 1, 2, 3, 4, 5), 0 } }; 
    var json = JsonConvert.SerializeObject(dictionary, Formatting.None, converters); 
    Assert.AreEqual("{\"2012-01-02T03:04:05Z\":0}", json); 
    var result = JsonConvert.DeserializeObject<Dictionary<Instant, int>>(json, converters); 
} 
+0

'var dtzProvider = DateTimeZoneCache.GetSystemDefault();' daje: ** odwołanie do obiektu jest wymagane dla niestatycznego pola, metody lub właściwości 'DateTimeZoneCache.GetSystemDefault()' ** –

+0

może użyć 'var dtzProvider = DateTimeZoneProviders.Tzdb ; 'lub BCL? –

+0

To nie działa dla mnie. Nadal mam ten sam wyjątek co plakat. – Steven

0

Ten problem jest dołączona w https://github.com/nodatime/nodatime.serialization/issues/2

mam obejść modyfikowany http://stackoverflow.com/q/6845364/634824. Nie jest to w żadnym wypadku skuteczne i nie zostało w pełni przetestowane.

public class DictionaryWithNodaTimeKeyConverter : JsonConverter 
{ 
    private static IDateTimeZoneProvider dtzProvider = DateTimeZoneProviders.Tzdb; 
    private JsonSerializerSettings _settings; 

    public DictionaryWithNodaTimeKeyConverter(IDateTimeZoneProvider dtzProvider) 
     : base() 
    { 
     _settings = new JsonSerializerSettings().ConfigureForNodaTime(dtzProvider); 
    } 

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) 
    { 
     IDictionary dictionary = (IDictionary)value; 
     writer.WriteStartObject(); 

     foreach (object key in dictionary.Keys) 
     { 
      writer.WritePropertyName(ConvertToPropertyKey(key)); 
      serializer.Serialize(writer, dictionary[key]); 
     } 

     writer.WriteEndObject(); 
    } 

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) 
    { 
     if (reader.TokenType == JsonToken.Null) 
     { 
      return null; 
     } 

     Type keyType = objectType.GetGenericArguments()[0]; 
     Type valueType = objectType.GetGenericArguments()[1]; 

     Type intermediateDictionaryType = typeof(Dictionary<,>).MakeGenericType(typeof(string), valueType); 
     IDictionary intermediateDictionary = (IDictionary)Activator.CreateInstance(intermediateDictionaryType); 
     serializer.Populate(reader, intermediateDictionary); 

     IDictionary finalDictionary = (IDictionary)Activator.CreateInstance(objectType); 
     foreach (DictionaryEntry pair in intermediateDictionary) 
     { 
      object parsedObject; 
      if (TryConvertKey(pair.Key.ToString(), keyType, out parsedObject)) 
      { 
       finalDictionary.Add(parsedObject, pair.Value); 
      } 
     } 

     return finalDictionary; 
    } 

    public override bool CanConvert(Type objectType) 
    { 
     bool canConvert = objectType.IsA(typeof(IDictionary<,>)); 

     if (canConvert) 
     { 
      Type keyType = objectType.GetGenericArguments()[0]; 
      canConvert = canConvert && IsNodaTimeType(keyType); 
     } 

     return canConvert; 
    } 

    private bool IsNodaTimeType(Type type) 
    { 
     return type.IsA(typeof(Instant)) 
       || type.IsA(typeof(OffsetDateTime)) 
       || type.IsA(typeof(DateTimeZone)) 
       || type.IsA(typeof(ZonedDateTime)) 
       || type.IsA(typeof(LocalDateTime)) 
       || type.IsA(typeof(LocalDate)) 
       || type.IsA(typeof(LocalTime)) 
       || type.IsA(typeof(Offset)) 
       || type.IsA(typeof(Duration)) 
       || type.IsA(typeof(Period)); 
     // Interval is not Support because Interval is serialized as a compound object. 
    } 

    private string ConvertToPropertyKey(object property) 
    { 
     if (!IsNodaTimeType(property.GetType())) 
     { 
      throw new InvalidOperationException(); 
     } 

     string result = JsonConvert.SerializeObject(property, _settings); 
     if (!string.IsNullOrWhiteSpace(result)) 
     { 
      // Remove the "" from JsonConvert 
      int first = result.IndexOf('"'); 
      int last = result.LastIndexOf('"'); 
      if (first != -1 && last != -1 && first < last) 
      { 
       result = result.Substring(first + 1, last - (first + 1)); 
      } 
     } 

     return result; 
    } 

    private bool TryConvertKey(string text, Type keyType, out object value) 
    { 
     if (!IsNodaTimeType(keyType)) 
     { 
      throw new InvalidOperationException(); 
     } 

     value = keyType.CreateDefault(); 

     try 
     { 
      value = JsonConvert.DeserializeObject($"\"{text}\"", keyType, _settings); 
      return true; 
     } 
     catch 
     { 
      return false; 
     } 
    } 
} 

ja również zdefiniowane Niektóre rozszerzenia

public static class TypeExtensions 
{ 
    public static bool IsA(this Type type, Type typeToBe) 
    { 
     if (!typeToBe.IsGenericTypeDefinition) 
      return typeToBe.IsAssignableFrom(type); 

     List<Type> toCheckTypes = new List<Type> { type }; 
     if (typeToBe.IsInterface) 
      toCheckTypes.AddRange(type.GetInterfaces()); 

     Type basedOn = type; 
     while (basedOn.BaseType != null) 
     { 
      toCheckTypes.Add(basedOn.BaseType); 
      basedOn = basedOn.BaseType; 
     } 

     return toCheckTypes.Any(x => x.IsGenericType && x.GetGenericTypeDefinition() == typeToBe); 
    } 

    public static object CreateDefault(this Type type) 
    { 
     return type.IsValueType ? Activator.CreateInstance(type) : null; 
    } 
} 

Aby go użyć:

IDateTimeZoneProvider provider = DateTimeZoneProviders.Tzdb; 
JsonConverter[] converters = { NodaConverters.IntervalConverter, NodaConverters.InstantConverter, new DictionaryWithNodaTimeKeyConverter(provider) }; 

var dictionary = new Dictionary<Instant, int> { 
    { Instant.FromUtc(2012, 1, 2, 3, 4, 5), 0 } 
}; 

var json = JsonConvert.SerializeObject(dictionary, Formatting.None, converters); 
Console.WriteLine(json); 
var result = JsonConvert.DeserializeObject<Dictionary<Instant, int>>(json, converters); 
Powiązane problemy