0

I have a complex object which is ISerializable and i want to serialize it into an XML document (node that i rather to not change the source code and add XML serialization attribute stuff). ISerializable works fine with BinaryFormatter, but there is no standard way to serialize it into XML or Json. The Json.NET library does support for serializing a ISerializable object into json, but there is a very small problem with that implementation, and that is the serializable constructor of class should be public in order to Json.net detect it (see this issue) and this does make Json.net unusable for my case.

Is there any other way to serialize/deserialize ISerializable object to/from xml, Json or any other plane text formats?

epsi1on
  • 561
  • 5
  • 16

2 Answers2

1

Json.NET does in fact support nonpublic streaming serialization constructors for ISerializable types. For confirmation see the source code for DefaultContractResolver.CreateISerializableContract().

Your actual problem is that the ISerializable type in question is also a collection, and it appears Json.NET uses an array contract in preference to a JsonISerializableContract for such types, as shown in DefaultContractResolver.CreateContract():

        if (typeof(IEnumerable).IsAssignableFrom(t))
        {
            return CreateArrayContract(objectType);
        }

        if (CanConvertToString(t))
        {
            return CreateStringContract(objectType);
        }

#if !(DOTNET || PORTABLE40 || PORTABLE)
        if (!IgnoreSerializableInterface && typeof(ISerializable).IsAssignableFrom(t))
        {
            return CreateISerializableContract(objectType);
        }
#endif

To work around this problem, you can create your own custom contract resolver that reverses this logic:

public class ISerializableCollectionContractResolver : DefaultContractResolver
{
    protected override JsonContract CreateContract(Type objectType)
    {
        var contract = base.CreateContract(objectType);
        var underlyingType = Nullable.GetUnderlyingType(objectType) ?? objectType;

        if (!IgnoreSerializableInterface 
            && typeof(ISerializable).IsAssignableFrom(underlyingType)
            && contract is JsonArrayContract
            && !underlyingType.GetCustomAttributes<JsonContainerAttribute>().Any())
        {
            contract = CreateISerializableContract(objectType);
        }

        return contract;
    }
}

Your custom collections should now be serialized through their ISerializable interface.

You may want to cache the contract resolver for best performance.

dbc
  • 104,963
  • 20
  • 228
  • 340
0

DataContractSerializer and DataContractJsonSerializer both support ISerializable. See Types Supported by the Data Contract Serializer.

For instance, consider the following class:

[Serializable]
public class SerializableClass : ISerializable
{
    readonly int valueField;

    public SerializableClass(int valueField)
    {
        this.valueField = valueField;
    }

    public int Value { get { return valueField; } }

    #region ISerializable Members

    protected SerializableClass(SerializationInfo info, StreamingContext context)
    {
        this.valueField = info.GetInt32("valueField");
    }

    void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
    {
        info.AddValue("valueField", valueField);
    }

    #endregion
}

And following helper methods:

public static partial class DataContractSerializerHelper
{
    public static string SerializeXml<T>(T obj, DataContractSerializer serializer = null, XmlWriterSettings settings = null)
    {
        serializer = serializer ?? new DataContractSerializer(obj.GetType());
        using (var textWriter = new StringWriter())
        {
            settings = settings ?? new XmlWriterSettings { Indent = true, IndentChars = "    " };
            using (var xmlWriter = XmlWriter.Create(textWriter, settings))
            {
                serializer.WriteObject(xmlWriter, obj);
            }
            return textWriter.ToString();
        }
    }

    public static T DeserializeXml<T>(string xml, DataContractSerializer serializer = null)
    {
        using (var textReader = new StringReader(xml ?? ""))
        using (var xmlReader = XmlReader.Create(textReader))
        {
            return (T)(serializer ?? new DataContractSerializer(typeof(T))).ReadObject(xmlReader);
        }
    }
}

public static partial class DataContractJsonSerializerHelper
{
    private static MemoryStream GenerateStreamFromString(string value)
    {
        return new MemoryStream(Encoding.Unicode.GetBytes(value ?? ""));
    }

    public static string SerializeJson<T>(T obj, DataContractJsonSerializer serializer = null)
    {
        serializer = serializer ?? new DataContractJsonSerializer(obj.GetType());
        using (var memory = new MemoryStream())
        {
            serializer.WriteObject(memory, obj);
            memory.Seek(0, SeekOrigin.Begin);
            using (var reader = new StreamReader(memory))
            {
                return reader.ReadToEnd();
            }
        }
    }

    public static T DeserializeJson<T>(string json, DataContractJsonSerializer serializer = null)
    {
        serializer = serializer ?? new DataContractJsonSerializer(typeof(T));
        using (var stream = GenerateStreamFromString(json))
        {
            var obj = serializer.ReadObject(stream);
            return (T)obj;
        }
    }
}

Then

var test = new SerializableClass(42);

var xml = DataContractSerializerHelper.SerializeXml(test);
Debug.WriteLine(xml);

Produces

<SerializableClass xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns:x="http://www.w3.org/2001/XMLSchema" xmlns="http://schemas.datacontract.org/2004/07/Question38188639">
    <valueField i:type="x:int" xmlns="">42</valueField>
</SerializableClass>

And

var json = DataContractJsonSerializerHelper.SerializeJson(test);
Debug.WriteLine(json);

Produces

{"valueField":42}
dbc
  • 104,963
  • 20
  • 228
  • 340
  • thanks for your comment, i tried to serialize a BriefFiniteElement.Model instanse in my example with DataContractSerializerHelper.SerializeXml() but it says "type with data contract name is not expected. consider using a datacontractresolver ..." for 'BriefFiniteElementNet.ElementCollection' class. do you have any workarounds? – epsi1on Jul 08 '16 at 14:14
  • Probably you have some polymorphic types. I would need to see a [mcve] to know for sure. See https://blogs.msdn.microsoft.com/youssefm/2009/06/05/configuring-known-types-dynamically-introducing-the-datacontractresolver/ – dbc Jul 08 '16 at 18:35
  • please have a look at first post of this issue: https://github.com/JamesNK/Newtonsoft.Json/issues/851 when i use your DataContractSerializerHelper.SerializeXml(model) it gives me error. thanks – epsi1on Jul 12 '16 at 06:19
  • @epsi1on - 1) You've accepted the other answer. Are you still pursuing this also? Is there still a problem with Json.NET? (JamesNK's answer about the constructor needing to be public was wrong, by the way.) 2) If you're asking me to download all the code from https://brieffiniteelementnet.codeplex.com, build it, and diagnose its problem with the data contract serializers, that's a little outside the scope of a stackoverflow question. Try to reduce the problem to something simpler; see https://stackoverflow.com/help/how-to-ask. – dbc Jul 12 '16 at 06:29
  • @epsi1on - but do see [“Type not expected”, using DataContractSerializer - but it's just a simple class, no funny stuff?](https://stackoverflow.com/questions/8794594) and [Unexpected Type - Serialization Exception](http://stackoverflow.com/questions/6153093). – dbc Jul 12 '16 at 06:30