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?
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.
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}