0

I have a Dictionary<string,object> where I save objects to parse to my plugin system, some of these objects need to be a copy not the original so I use this DeepClone method:

public static T DeepCloneJSON<T>(this T Obj)
        {
            var text = JsonSerializer.Serialize(Obj);
            return JsonSerializer.Deserialize<T>(text);
        }

for example, if I do :

var dict = new Dictionary<string,object>(){
    {"someKey",new List<MyClass>(){new MyClass()}}
}
var copy = dict["someKey"].DeepCloneJSON();
var cast = (List<MyClass>)copy;

i get a System.InvalidCastException, but if i use a MemoryStream DeepCopy method i don't get this exception, like this one:

public static T DeepCloneMemoryStream<T>(this T obj)
        {
            using (var ms = new MemoryStream())
            {
                var formatter = new BinaryFormatter();
                formatter.Serialize(ms, obj);
                ms.Position = 0;

                return (T)formatter.Deserialize(ms);
            }
        }

so, I would like to know why I get this exception using a System.text.json based DeepClone Method and if it is possible to do this with a System.text.json because in my tests it presents to be faster and use less memory than a MemoryStream based onde.

Pedro
  • 3
  • 1
  • 3
    Because you are calling `DeepCloneJSON` which in turn calls `JsonSerializer.Deserialize` not `JsonSerializer.Deserialize`. You are going to need reflection to call `DeepCloneJSON` with the correct type parameter. Although whether you should deep-clone via JSON at all is a different question... – Charlieface May 10 '21 at 19:15

1 Answers1

0

Your DeepCloneMemoryStream recreates the exact type via Reflection, even if T is object.

Your DeepCloneJSON roundtrip via JSON string loses type information, and so deserialization requires the exact type to be specified. In your case, you are only passing object as T and therefore you get back a JsonElement instead of a List<MyClass>.

The following will work if you change how you make your call:

var copy = DeepCloneJSON((List<MyClass>)dict["someKey"]);

Alternatively, change your implementation such that deserialization happens according to that actual type of Obj instead of the specified type T - this would have the analogous effect to your other implementation, where T is only used for casting.

public static T DeepCloneJSON<T>(T Obj)
{
    var text = JsonSerializer.Serialize(Obj);
    return (T)JsonSerializer.Deserialize(text, Obj.GetType());
}

A completely different approach

You can avoid the cast exception and the overhead of serializing and deserializing, and the possible issues with that (e.g. private members, non-serializable instances and such) by choosing among various open source NuGet packages that use techniques such as reflection emit or expression trees to avoid the serialization.

One example is FastDeepCloner as suggested in the comments. There are several others that provide similar features.

Kit
  • 20,354
  • 4
  • 60
  • 103
Lemon Sky
  • 677
  • 4
  • 10