How to serialize an ISerializable object into SOAP

2019-03-04 07:30发布

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?

2条回答
狗以群分
2楼-- · 2019-03-04 07:56

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}
查看更多
我欲成王,谁敢阻挡
3楼-- · 2019-03-04 08:10

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.

查看更多
登录 后发表回答