JsonKnownTypes icon indicating copy to clipboard operation
JsonKnownTypes copied to clipboard

Deserializing Existing Json with Basic Example Results in Exception

Open jonmotos opened this issue 1 year ago • 3 comments

Describe the bug When using JsonKnownTypes on existing json content, adding a new derived type and then decorating the base class with the basic [JsonConverter...] attribute results in an exception:

To Reproduce Steps to reproduce the behavior:

  1. Create a base class
  2. Serialize this class using standard Newtonsoft behaviors, no type handling
  3. Implement a new derived class
  4. Decorate the base class with the JsonConverter for JsonKnownTypes<Base>
  5. Deserialize

Expected behavior I would expect an instance without a discriminator to automatically deserialize to the base class

Screenshots If applicable, add screenshots to help explain your problem.

Additional context Exception content: JsonKnownTypes.Exceptions.JsonKnownTypesException: discriminator is not registered for T type

I then tried adding [JsonKnownTypeFallback(typeof(Base))] This results in another exception: System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation. ---> System.InvalidCastException: Unable to cast object of type 'Base' to type 'Derived'.

Two unexpected behaviors here!

I would be very happy to help implement a solution here!

jonmotos avatar May 05 '23 16:05 jonmotos

What about that exception

Unable to cast object of type 'Base' to type 'Derived'.

can you pls provide exact code(just past all code here), cause its a bit weird why it cast Base to Derived.

dmitry-bym avatar May 17 '23 09:05 dmitry-bym

Some more context: I have old data that was deserialized as one type. A derived type was later added. After setting up testing, I'm not entirely sure I agree with myself about scenario 2. Perhaps the shown behavior is OK. Deserializing as the base type works fine. I do think scenario 1 is valid, though!

using JsonKnownTypes;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

namespace JsonKnownTypesRepro
{
	[TestClass]
	public class KnownTypesDerivedExceptionRepro
	{
		[JsonConverter(typeof(JsonKnownTypesConverter<Base>))]
        public class Base
        {
			public string BaseProp { get; set; }
		}

        public class Derived : Base
        {
			public string DerivedProp { get; set; }
		}

        [TestMethod]
        public void TestLegacyBaseClassDeserialization()
        {
			//in the scenario where a base type was serialized in old code, before the jsonknowntype was associated with it,
			//adding the jsonknowntypes alone conversion results in an exception
			//expected: the conversion to succeed, deserializing the base class

			var @base = new Base { BaseProp = "base" };

			var json = JsonConvert.SerializeObject(@base, Formatting.Indented);

			Console.WriteLine(json);

			//remove the $type discriminator to simulate the scenario
			var token = JToken.Parse(json);
            token["$type"].Parent.Remove();

			Console.WriteLine(token.ToString(Formatting.Indented));

            var deserialized = JsonConvert.DeserializeObject<Base>(token.ToString());
		}

        [JsonConverter(typeof(JsonKnownTypesConverter<Base2>))]
        [JsonKnownTypeFallback(typeof(Base2))]
        public class Base2
        {
            public string BaseProp { get; set; }
        }

        public class Derived2 : Base2
        {
            public string DerivedProp { get; set; }
        }

        [TestMethod]
        public void TestLegacyBaseClassDeserialization2()
        {
            //in the scenario where a base type was serialized in old code, before the jsonknowntype was associated with it,
            //adding the jsonknowntypes conversion and a fallback
            //attempting to deserialize as the derived type results in an exception
            //expected: i'm not sure - perhaps it's perfectly valid to fail
            //also, this would be fixed by JsonDiscriminator.UseBaseTypeForCanConvert 

            var @base = new Base2 { BaseProp = "base2" };

            var json = JsonConvert.SerializeObject(@base, Formatting.Indented);

            Console.WriteLine(json);

            //no $type discriminator is emitted

            var deserialized2 = JsonConvert.DeserializeObject<Derived2>(json);
        }
	}
}

jonmotos avatar May 01 '24 15:05 jonmotos

For now don't have time to figure this out, if u can figure this out and fix if needed ill merge and publish that quickly

dmitry-bym avatar May 04 '24 11:05 dmitry-bym