2011-11-28 6 views
16

Mam deserializing niektóre właściwości do Dictionary<string, object>.Jak zmienić domyślny typ dla deserializacji liczbowej?

Kiedy deserializuję niektóre json, zapełnia obiekty Dictionary z Int64 zamiast Int32. Chciałbym wybrać domyślną opcję Int32, wiedząc, że mógłbym mieć elementy numeryczne javascript, które przepełniłyby konwersję. Wyrzucenie wyjątku w takim przypadku byłoby całkowicie dopuszczalne.

Czy jest jakiś sposób, aby to osiągnąć? Mam nadzieję na kilka ładnych atrybutów lub wygodny interfejs, który można zaimplementować i dodać do JsonSerializer. Obawiam się, że muszę zagłębić się w głąb Json.NET.

Zasadniczo chciałbym mieć jakiś sposób kontrolować znane typy dla obiektów tak, że mogę dostać Int32 „S zamiast Int64 i DateTimes zamiast Strings.

+0

Komentarze sprzątana: PO zdaje sobie sprawę z opcją POCO, ale nie chce tego robić –

Odpowiedz

18

O ile mi wiadomo, nie ma wbudowanego sposobu, aby to zrobić.

Był issue na ten temat, ale został zamknięty. Niektóre komentarze do autora w kwestii:

Json.NET domyślnie odczytuje wartości całkowitych jako Int64, ponieważ nie ma sposobu, aby wiedzieć, czy wartość powinna być Int32 lub Int64, Int64 i jest mniej prawdopodobne, aby przepełnić. W przypadku właściwości typizowanej deserializator wie, aby przekonwertować Int64 na Int32, ale ponieważ twoja własność jest nietknięta dostajesz Int64. [...] Tak właśnie musi działać Json.NET.

Najprostszym rozwiązaniem byłoby z Coure się zmienić typ do Dictionary<string, int>, ale przypuszczam, że jesteś nie tylko czytanie numeryczne, a zatem są skazani object.

Inną opcją byłoby użyć Serialization Callbacks i ręcznie konwertować te Int64 s do Int32 lub stworzyć swój własny Contract ResolverJsonConverter i bezpośrednio kontrolować (de-) serializacji.


Edit: stworzyłem mały przykład, aby być bardziej szczegółowe.

Oto bardzo prosty konwerter, który działa tylko z SZCZEGÓLNE Dictionary:

public class Int32Converter : JsonConverter { 
    public override bool CanConvert(Type objectType) { 
     // may want to be less concrete here 
     return objectType == typeof(Dictionary<string, object>); 
    } 

    public override bool CanWrite { 
     // we only want to read (de-serialize) 
     get { return false; } 
    } 

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { 
     // again, very concrete 
     Dictionary<string, object> result = new Dictionary<string, object>(); 
     reader.Read(); 

     while (reader.TokenType == JsonToken.PropertyName) { 
      string propertyName = reader.Value as string; 
      reader.Read(); 

      object value; 
      if (reader.TokenType == JsonToken.Integer) 
       value = Convert.ToInt32(reader.Value);  // convert to Int32 instead of Int64 
      else 
       value = serializer.Deserialize(reader);  // let the serializer handle all other cases 
      result.Add(propertyName, value); 
      reader.Read(); 
     } 

     return result; 
    } 

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { 
     // since CanWrite returns false, we don't need to implement this 
     throw new NotImplementedException(); 
    } 
} 

można użyć atrybutów do dekorowania członkom swojej konwertera lub pass it as parameter do (de-) metody serializacji.Oto przykład, gdzie kiedyś atrybut:

[JsonObject] 
public class MyObject { 
    [JsonConverter(typeof(Int32Converter))] 
    public Dictionary<string, object> Properties { get; set; } 
} 

A oto kod użyłem do testowania realizacji:

class Program { 
    static void Main(string[] args) { 
     MyObject test = new MyObject(); 
     test.Properties = new Dictionary<string, object>() { { "int", 15 }, { "string", "hi" }, { "number", 7 } }; 
     Print("Original:", test); 

     string json = JsonConvert.SerializeObject(test); 
     Console.WriteLine("JSON:\n{0}\n", json); 

     MyObject parsed = JsonConvert.DeserializeObject<MyObject>(json); 
     Print("Deserialized:", parsed); 
    } 

    private static void Print(string heading, MyObject obj) { 
     Console.WriteLine(heading); 
     foreach (var kvp in obj.Properties) 
      Console.WriteLine("{0} = {1} of {2}", kvp.Key, kvp.Value, kvp.Value.GetType().Name); 
     Console.WriteLine(); 
    } 
} 

bez konwertera, wynik będzie:

Deserialized: 
int = 15 of Int64 
string = hi of String 
number = 7 of Int64 

z konwerterem:

Deserialized: 
int = 15 of Int32 
string = hi of String 
number = 7 of Int32 
+0

Co za niesamowite odpowiedź. Klasa Int32Converter jest dokładnie tym, czego szukałem. Nie zamierzam go używać, odkąd zmieniłem sposób, w jaki robię te rzeczy, ale w tym czasie byłaby to idealna odpowiedź. :) – Mithon

+0

Działa to poprawnie, gdy masz wartości całkowite w słowniku. Jednakże, jeśli masz wartości liczb całkowitych w obiektach w słowniku, niż nie działa. Wszelkie sugestie na ten temat proszę? Działa, jeśli słownik ma {{"pierwszy", 1}, {"drugi", 2}} nie działa, jeśli słownik ma {{"pierwszy", {wiek: "1"}}, {"drugi ", {Wiek:" 2 "}} –

+0

@HamidShahid zależy od sposobu dekompresji serialu. Jeśli posiadasz obiekt w Słowniku (ten z polem "Wiek"), możesz napisać dla niego konwerter zamiast dla słownika. Jednak jeśli masz do czynienia z różnymi obiektami, nie polecałbym tego. Można utworzyć ogólny konwerter liczbowy, taki jak [tutaj] (http://stackoverflow.com/questions/17745866/how-can-i-restore-the-de-deserialization-behavior-after-upgrading-json-net). Należy pamiętać, że moja odpowiedź jest już dość stara i może istnieć wbudowane rozwiązanie. – enzi

1

Przyjmuję odpowiedź Enzi, ponieważ o to prosiłem.

Jednak od tego czasu zmieniłem swoją strategię.

W tej chwili deserializuję się na ChangeSet<T>, który zamiast ze słownika ma mocno wpisany obiekt Entity (T) ze zmianami. Ma również List<string> z nazwami właściwości właściwości, które były obecne w przychodzącym jsonie. Następnie zapełniam tę listę podczas deserializacji za pomocą niestandardowego MediaFormattera. W ten sposób otrzymuję obiekt silnie typowany i poprawną deserializację wszystkich właściwości, i wiem z listy, jakie właściwości powinienem ustawić na mojej kolekcji T, kiedy chcę wykonać operację wsadową.

W ten sposób używam swoich jednostek jako DTO bez konieczności posiadania wielu różnych DTO dla różnych operacji wsadowych. Jest dość elegancki, jeśli sam tak powiem. :)

1

Spróbuj

var variable = Convert.ToInt32(object) 

iterację po Dictionary<string,object> i przepisać jej object z tym Int32, czy konwersja Int32 każdym razem przeczytać object.

1

To działa dobrze dla mnie:

public class ParseNumbersAsInt32Converter : JsonConverter 
{ 
    public override bool CanConvert(Type objectType) 
    { 
     return objectType == typeof(long) || objectType == typeof(long?) || objectType == typeof(object); 
    } 

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) 
    { 
     serializer.Serialize(writer, value); 
    } 

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) 
    { 
     if (reader.Value != null && reader.Value is long) 
     { 
      return Convert.ToInt32(reader.Value); 
     } 
     return reader.Value; 
    } 
} 
+0

Ta odpowiedź była o wiele łatwiejsza w kontakcie z nadpisaniem JsonConvert i dodaniem większej funkcjonalności. Dzięki –