2

Note: The question is restricted to C# UWP.

The Dream:

public static class Serializer {
    // return json string
    public string Serialize(object obj) { ??? }
    // return T from json string
    public T Deserialize<T>(string jsonString) { ??? }
}

Closest I've Come:

public static class Serializer
{
    public static string Serialize(object obj, DataContractJsonSerializerSettings settings=null)
    {
        if (obj == null) {
            throw new NullReferenceException();
        }

        settings = settings ?? new DataContractJsonSerializerSettings();
        DataContractJsonSerializer jsonizer = new DataContractJsonSerializer(obj.GetType(), settings);
        string jsonString = null;
        using ( MemoryStream stream = new MemoryStream() )
        {
            jsonizer.WriteObject(stream, obj);
            stream.Position = 0;
            StreamReader sr = new StreamReader(stream);
            jsonString = sr.ReadToEnd();
        }
        return jsonString;
    }

    public static T Deserialize<T>(string jsonString)
    {
        DataContractJsonSerializer jsonizer = new DataContractJsonSerializer(typeof(T));
        T obj;
        using (Stream stream = GenerateStreamFromString(jsonString))
        {
            obj = (T)jsonizer.ReadObject(stream);
        }
        return obj;
    }

    private static Stream GenerateStreamFromString(string s)
    {
        MemoryStream stream = new MemoryStream();
        StreamWriter writer = new StreamWriter(stream);
        writer.Write(s);
        writer.Flush();
        stream.Position = 0;
        return stream;
    }
}

The Problem

The partial solution I posted works in simple cases. However, it fails when the subtype of the object being deserialized is difficult (or impossible) to determine from the json string. For instance,

IList<Animal> animals = new List<Animal>();
animals.add(new Dog("Woofy"));
animals.add(new Cat("Fluffy"));

string json = Serializer.Serialize(animals);
IList<Animal> result = Serializer.Deserialize<List<Animal>>(json);
// ^ fails because subtype information was lost during serialization

bool succeeded = result.get(0).Name.Equals("Woofy") && result.get(1).Name.Equals("Fluffy");

What I'm Looking For:

An implementation of the skeleton specified in "The Dream" which passes the driver specified in "The Problem". Comments welcome.

C. McCoy IV
  • 887
  • 7
  • 14
  • 1
    If you are using `DataContractJsonSerializer`, to serialize and deserialize polymorphic types, you need to inform it of all possible subtypes that could be encountered. See [Data Contract Known Types](https://msdn.microsoft.com/en-us/library/ms730167.aspx), [DataContractSerializer and Known Types](https://stackoverflow.com/questions/9422662) and/or [parse.com: SerializationException deserializing JSON objects with “__type” property](https://stackoverflow.com/questions/33746518). – dbc May 19 '16 at 04:35
  • 2
    Or, switch to [tag:json.net] and enable [`TypeNameHandling`](http://www.newtonsoft.com/json/help/html/SerializeTypeNameHandling.htm). See e.g. [JSON serialization of array with polymorphic objects](https://stackoverflow.com/questions/5186973). – dbc May 19 '16 at 04:37

3 Answers3

1

Your Serializer works perfectly fine, if you add the KnownType-attributes to your base class:

[DataContract]
[KnownType(typeof(Dog))] // add these
[KnownType(typeof(Cat))] // lines
public class Animal
{
    [DataMember]
    public string Name { get; set; }
}

[DataContract]
public class Dog : Animal
{
}

[DataContract]
public class Cat : Animal
{
}

It's necessary for the DataContractJsonSerializer to preserve the type-information of the instances being serialized. You can see this in the resulting serialized JSON:

[{\"__type\":\"Dog:#My.Serialization.Sample.Project\",\"Name\":\"Woofy\"},{\"__type\":\"Cat:#My.Serialization.Sample.Project\",\"Name\":\"Fluffy\"}]

There is the extra key __type which holds the concrete information that the first object is a Dog form namespace My.Serialization.Sample.Project.


But as @dbc already mentioned, you might be slightly better off using JSON.NET which easily allows you to serialize your list without having the need to decorate your data transfer object with all those attributes. Even DataContract and DataMember are not needed.

public class Animal
{
    public string Name { get; set; }
}

public class Dog : Animal { }

public class Cat : Animal { }

Using it this way

var settings = new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.Auto };
string jsonNet = JsonConvert.SerializeObject(animals, settings);
var jsonNetResult = JsonConvert.DeserializeObject<IList<Animal>>(jsonNet);

yields that result:

[{\"$type\":\"My.Serialization.Sample.Project.Dog, My.Assembly\",\"Name\":\"Woofy\"},{\"$type\":\"My.Serialization.Sample.Project.Cat, My.Assembly\",\"Name\":\"Fluffy\"}]
Jan Köhler
  • 5,817
  • 5
  • 26
  • 35
1

With JsonSubTypes you have at least two possibilities:

[JsonConverter(typeof(JsonSubtypes))]
[JsonSubtypes.KnownSubTypeWithProperty(typeof(Dog), "HadWalkToday")]
[JsonSubtypes.KnownSubTypeWithProperty(typeof(Cat), "ColorOfWhiskers")]
public class Animal
{
    public string Name { get; set; }
}

public class Dog : Animal
{
    public bool HadWalkToday { get; set; }
}

public class Cat : Animal
{
    public string ColorOfWhiskers { get; set; }
}

or

[JsonConverter(typeof(JsonSubtypes), "Sound")]
[JsonSubtypes.KnownSubType(typeof(Dog), "Bark")]
[JsonSubtypes.KnownSubType(typeof(Cat), "Meow")]
public class Animal
{
    public virtual string Sound { get; }
    public string Color { get; set; }
}

public class Dog : Animal
{
    public override string Sound { get; } = "Bark";
    public string Breed { get; set; }
}

public class Cat : Animal
{
    public override string Sound { get; } = "Meow";
    public bool Declawed { get; set; }
}

That works with:

[Test]
public void Proof()
{
    Dog dog = new Dog()
    {
        Name = "Woofy",
        HadWalkToday = true
    };
    Cat cat = new Cat()
    {
        Name = "Fluffy",
        ColorOfWhiskers = "Brown"
    };
    IList<Animal> animals = new List<Animal>()
    {
        dog,
        cat
    };

    string json = JsonConvert.SerializeObject(animals);
    IList<Animal> result = JsonConvert.DeserializeObject<List<Animal>>(json);

    Assert.IsTrue(result[0].GetType() == typeof(Dog));
    Assert.IsTrue(result[1].GetType() == typeof(Cat));
}
manuc66
  • 2,701
  • 29
  • 28
0

The answer I arrived at:

using System;
using System.Collections.Generic;
using Newtonsoft.Json;

namespace SerializationDemo
{
    class Program
    {
        static void Main(string[] args)
        {
            Dog dog = new Dog()
            {
                Name = "Woofy",
                HadWalkToday = true
            };
            Cat cat = new Cat()
            {
                Name = "Fluffy",
                ColorOfWhiskers = "Brown"
            };
            IList<Animal> animals = new List<Animal>()
            {
                dog,
                cat
            };

            string json = Serializer.Serialize(animals);
            IList<Animal> result = Serializer.Deserialize<List<Animal>>(json);

            bool serializerSuccessful = dog.Equals(result[0]) && cat.Equals(result[1]);
        }
    }

    public class Animal
    {
        public string Name { get; set; }

        public override bool Equals(object obj)
        {
            var animal = obj as Animal;
            return this.Name == animal.Name;
        }
    }

    public class Dog : Animal
    {
        public bool HadWalkToday { get; set; }

        public override bool Equals(object obj)
        {
            var dog = obj as Dog;
            return this.HadWalkToday == dog.HadWalkToday && base.Equals(obj);
        }
    }

    public class Cat : Animal
    {
        public string ColorOfWhiskers { get; set; }

        public override bool Equals(object obj)
        {
            var cat = obj as Cat;
            return this.ColorOfWhiskers == cat.ColorOfWhiskers && base.Equals(obj);
        }
    }

    public static class Serializer
    {
        private static readonly JsonSerializerSettings settings = new JsonSerializerSettings()
        {
            TypeNameHandling = TypeNameHandling.All,
            Formatting = Formatting.Indented
        };

        public static string Serialize(object obj)
        {
            if (obj == null)
            {
                throw new NullReferenceException();
            }

            string jsonString = JsonConvert.SerializeObject(obj, settings);
            return jsonString;
        }

        public static T Deserialize<T>(string jsonString)
        {
            T obj = JsonConvert.DeserializeObject<T>(jsonString, settings);
            return obj;
        }
    }
}
C. McCoy IV
  • 887
  • 7
  • 14