Musiałem ostatnio serializować (no i oczywiście deserializować) kolekcje obiektów z hierarchii wyglądające na przykład tak:
1: public interface IExecutableAction 2: { 3: 4: } 5: 6: public class ReduceValue : IExecutableAction 7: { 8: public decimal ReduceBy; 9: 10: public ReduceValue() 11: { 12: 13: } 14: 15: public ReduceValue(decimal reduceBy) 16: { 17: ReduceBy = reduceBy; 18: } 19: } 20: 21: public class LockObject : IExecutableAction 22: { 23: public double Duration; 24: public string Reason; 25: 26: public TimeSpan GetDurationSpan() 27: { 28: return TimeSpan.FromMilliseconds(Duration); 29: } 30: 31: public LockObject() 32: { 33: 34: } 35: 36: public LockObject(TimeSpan duration, string reason) 37: { 38: Duration = duration.TotalMilliseconds; 39: Reason = reason; 40: } 41: }
Pierwszym kierunkiem był XML, jednak (zgodnie z oczekiwaniami zresztą) umożliwienie serializacji czegoś takiego do XMLa wiązałoby się z wieloma nieprzyjemnościami. A to nie można serializować interfejsów więc konieczne by było dodawanie jakiejś klasy abstrakcyjnej, a to trzeba stosować przebrzydłe atrybuty Xmlinclude żeby umożliwić deserializację… a dodatkowo korzystanie z serializatora xml jest niewygodne.
Co zrozumiałe – serializacja do JSON odpadała bo JSON nie zawiera informacji o źródłowym typie co, jak można się domyślić, znacząco utrudnia deserializację obiektu.
ALE! Po chwili moją uwagę przykuł konstruktor klasy JavaScriptSerializer przyjmujący parametr tajemniczego typu JavaScriptTypeResolver. Szczęśliwie okazało się, że wystarczy podać jego standardowo dostarczaną implementację (SimpleTypeResolver) i życie staje się prostsze! Wynikowy tekst zawiera wówczas, oprócz suchych danych, także informacje o typie serializowanej klasy. Takie coś śmiga aż miło:
1: [Test] 2: public void actions_can_be_serialized_and_deserialized() 3: { 4: var actions = new IExecutableAction[] 5: { 6: new ReduceValue(3), 7: new LockObject(5.Minutes(), "reason") 8: }; 9: 10: var javaScriptSerializer = new JavaScriptSerializer(new SimpleTypeResolver()); 11: string json = javaScriptSerializer.Serialize(actions); 12: var deserialized = javaScriptSerializer.Deserialize<IExecutableAction[]>(json); 13: 14: Assert.AreEqual(2, deserialized.Length); 15: Assert.AreEqual(3, ((ReduceValue)deserialized[0]).ReduceBy); 16: Assert.AreEqual(5.Minutes(), ((LockObject)deserialized[1]).GetDurationSpan()); 17: }
Czyż nie cool? Jak to fajnie, że człowiek uczy się całe życie.
A żeby nie musieć za każdym razem pamiętać o tym "jak te akcje (de)serializować" banalna własna implementacja serializatora wydaje się sensownym krokiem dalej:
1: public class ExecutableActionsSerializer : JavaScriptSerializer 2: { 3: public ExecutableActionsSerializer() : base(new SimpleTypeResolver()) 4: { 5: } 6: }
Dobre!
Dzięki,
Marek
Wklej przykładowego JSON’a jakiego to generuje. Zakładam że gdzieś w cwany sposób ukrywa typ?
jj,
Po prostu każdy obiekt dostaje dodatkowe pole o nazwie "__type". Ale można to sobie zmienic, o tym w kolejnym odcinku:).
Maćjek…. nie kasz czekać!! wklei kawałek kodó!
Ależ jaki niecierpliwy!
Teraz kodu obok nie mam, ale jak normalny obiekt Person wygląda tak:
{ Age: 13, Name: ‘name’ }
to po potraktowaniu tym mechanizmem:
{ __type:’Some.Namespace.Person, some.dll, version=…., key=…. ‘, Age: 13, Name: ‘name’ }