0

Below is a simplified example of what I'm trying to accomplish.

I have a class DoNotSerializeMe which is part of an external library and cannot be serialized.

using System;

namespace CustomJsonSerialization
{
    public class DoNotSerializeMe
    {
        public string WhyAmIHere;

        public DoNotSerializeMe(string mystring)
        {
            Console.WriteLine("    In DoNotSerializeMe constructor.");
            WhyAmIHere = "( constructed with " + mystring + " )";
        }
    }
}

I also have a class SerializeMe which has a member of type DoNotSerializeMe. I can make this class implement ISerializable and get around the issue of DoNotSerializeMe not being serializable by pulling data and calling the constructor.

using System.Runtime.Serialization;
using System.Security.Permissions;

namespace CustomJsonSerialization
{
    [System.Serializable]
    public class SerializeMe : ISerializable
    {
        public DoNotSerializeMe SerializeMeThroughISerializable;

        public SerializeMe(string mystring)
        {
            SerializeMeThroughISerializable = new DoNotSerializeMe(mystring);
        }

        protected SerializeMe(SerializationInfo info, StreamingContext context)
        {
            System.Console.WriteLine("    In SerializeMe constructor (ISerializable)");
            SerializeMeThroughISerializable = new DoNotSerializeMe(info.GetString("SerializeMeThroughISerializable"));
        }

        [SecurityPermission(SecurityAction.Demand, SerializationFormatter = true)]
        public virtual void GetObjectData(SerializationInfo info, StreamingContext context)
        {
            System.Console.WriteLine("    In SerializeMe.GetObjectData()");
            info.AddValue("SerializeMeThroughISerializable", 
                    "( deserialized through getObjectData " + 
                    SerializeMeThroughISerializable.WhyAmIHere + " )");
        }
    }
}

Below is a short program that serializes and deserializes the object:

using System;
using System.IO;
using System.Runtime.Serialization.Json;
using System.Text;

namespace CustomJsonSerialization
{
    public class Program
    {
        public static void Main(string[] args)
        {
            SerializeMe serializeme = new SerializeMe("initial");

            Console.WriteLine("I created it: {0}", serializeme.SerializeMeThroughISerializable.WhyAmIHere);
            Console.WriteLine();

            MemoryStream memstream = new MemoryStream();
            DataContractJsonSerializer serializer = new DataContractJsonSerializer(typeof(SerializeMe));
            serializer.WriteObject(memstream, serializeme);

            Console.WriteLine("I serialized it: {0}", serializeme.SerializeMeThroughISerializable.WhyAmIHere);
            Console.WriteLine();

            Console.WriteLine("Json:");
            Console.WriteLine(Encoding.ASCII.GetString(memstream.ToArray()));
            Console.WriteLine();

            memstream.Seek(0, SeekOrigin.Begin);
            SerializeMe anotherSerializeMe = (SerializeMe)serializer.ReadObject(memstream);
            Console.WriteLine("I deserialized it: {0}", anotherSerializeMe.SerializeMeThroughISerializable.WhyAmIHere);

        }
    }
}

When running through .NET (4.5), I get the following:

    In DoNotSerializeMe constructor.
I created it: ( constructed with initial )

    In SerializeMe.GetObjectData()
I serialized it: ( constructed with initial )

Json:
{"SerializeMeThroughISerializable":"( deserialized through getObjectData ( constructed with initial ) )"}

    In SerializeMe constructor (ISerializable)
    In DoNotSerializeMe constructor.
I deserialized it: ( constructed with ( deserialized through getObjectData ( constructed with initial ) ) )

The serializer called the ISerializable construction and the GetObjectData when serializing / deserializing (as expected). I never serialize or deserialize the DoNotSerializeMe object directly.

However, when running the same build through mono (tried 3.10.0 and 4.0.2), I get the following: In DoNotSerializeMe constructor. I created it: ( constructed with initial )

I serialized it: ( constructed with initial )

Json:
{"SerializeMeThroughISerializable":{"WhyAmIHere":"( constructed with initial )"}}

I deserialized it: ( constructed with initial )

Obviously, if DoNotSerializeMe was truly not serializable, this would lead to an error.

Is there an elegant way to get around this without using Json.NET? I'm not sure why mono isn't behaving the same way as .NET.

dbc
  • 104,963
  • 20
  • 228
  • 340
Jaws
  • 121
  • 1
  • 3

1 Answers1

0

I'm going to suggest an alternative approach: use a surrogate data transfer type to serialize your DoNotSerializeMe class. One way to do this is with Data Contract Surrogates. But are data contract surrogates supported on mono? The current version of the source code shows that they are, but this version shows it as [MonoTODO], so I cannot guarantee that your version of mono has a working implementation for DataContractJsonSerializer.DataContractSurrogate.

However, a manual surrogate property will always work. For instance, the following SerializeMe type can be serialized and serialized even though its DoNotSerializeMe member cannot be serialized directly:

public class DoNotSerializeMe
{
    public readonly string WhyAmIHere;
    readonly bool ProperlyConstructed; // data contract serializer does not call the constructor

    public DoNotSerializeMe(string mystring)
    {
        Console.WriteLine(string.Format("    In DoNotSerializeMe constructor, mystring = \"{0}\"", mystring));
        WhyAmIHere = mystring;
        ProperlyConstructed = true;
    }

    public void Validate()
    {
        if (!ProperlyConstructed)
            throw new InvalidOperationException("!ProperlyConstructed");
    }
}

public class SerializeMe
{
    [IgnoreDataMember]
    public DoNotSerializeMe CannotBeSerializedDirectly;

    public DoNotSerializeMeSurrogate DoNotSerializeMeSurrogate
    {
        get
        {
            if (CannotBeSerializedDirectly == null)
                return null;
            return new DoNotSerializeMeSurrogate { WhyAmIHereSurrogate = CannotBeSerializedDirectly.WhyAmIHere };
        }
        set
        {
            if (value == null)
                CannotBeSerializedDirectly = null;
            else
                CannotBeSerializedDirectly = new DoNotSerializeMe(value.WhyAmIHereSurrogate);
        }
    }

    public string SomeOtherField { get; set; }
}

public class DoNotSerializeMeSurrogate
{
    public string WhyAmIHereSurrogate { get; set; }
}

Using this approach looks simpler than implementing ISerializable since all the members other than CannotBeSerializedDirectly continue to be serialized automatically. Note the use of [IgnoreDataMember]. It prevents the CannotBeSerializedDirectly member from being included in the implicit data contract for the class.

If you would prefer your surrogate to be private, or need to control the surrogate member names, you will need to give the containing type an explicit data contract:

[DataContract]
public class SerializeMe
{
    [IgnoreDataMember]
    public DoNotSerializeMe CannotBeSerializedDirectly;

    [DataMember]
    DoNotSerializeMeSurrogate DoNotSerializeMeSurrogate
    {
        get
        {
            if (CannotBeSerializedDirectly == null)
                return null;
            return new DoNotSerializeMeSurrogate { WhyAmIHereSurrogate = CannotBeSerializedDirectly.WhyAmIHere };
        }
        set
        {
            if (value == null)
                CannotBeSerializedDirectly = null;
            else
                CannotBeSerializedDirectly = new DoNotSerializeMe(value.WhyAmIHereSurrogate);
        }
    }

    [DataMember]
    public string SomeOtherField { get; set; }
}

[DataContract]
class DoNotSerializeMeSurrogate
{
    [DataMember]
    public string WhyAmIHereSurrogate { get; set; }
}
dbc
  • 104,963
  • 20
  • 228
  • 340
  • 1
    that is an elegant solution, and you're right, that should work. I ended up using private fields and [OnSerializing] [OnDeserialized] but I don't like it. So I'll definitely give your solution a shot and let you know. Thank you! – Jaws Jun 23 '16 at 04:44