Skip to content

Declare known baseType(s) instead of subTypes #175

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions JsonSubTypes.Tests/AbstractBaseClassDiscriminatorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -57,4 +57,37 @@ public void DeserializingWithAbstractClassCircleThrows()
exception.Message);
}
}

[TestFixture]
public class KnownBaseType_AbstractBaseClassDiscriminatorTests
{
[JsonConverter(typeof(JsonSubtypes), "Discriminator")]
[JsonSubtypes.KnownBaseType(typeof(CC), "D")]
public abstract class AA
{
}

[JsonConverter(typeof(JsonSubtypes), "Discriminator")]
[JsonSubtypes.KnownBaseType(typeof(AA), "D")]
public abstract class BB
{
}

[JsonConverter(typeof(JsonSubtypes), "Discriminator")]
[JsonSubtypes.KnownBaseType(typeof(BB), "D")]
public abstract class CC
{
}

[Test]
[Timeout(2000)]
public void DeserializingWithAbstractClassCircleThrows()
{
var exception = Assert.Throws<JsonSerializationException>(() =>
JsonConvert.DeserializeObject<AA>("{\"Discriminator\":\"D\"}"));
Assert.AreEqual(
"Could not create an instance of type JsonSubTypes.Tests.KnownBaseType_AbstractBaseClassDiscriminatorTests+AA. Type is an interface or abstract class and cannot be instantiated. Path 'Discriminator', line 1, position 17.",
exception.Message);
}
}
}
50 changes: 50 additions & 0 deletions JsonSubTypes.Tests/BaseIsAnInterfaceTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,4 +54,54 @@ public void UnknownMappingFails()
Assert.AreEqual("Could not create an instance of type JsonSubTypes.Tests.BaseIsAnInterfaceTests+IAnimal. Type is an interface or abstract class and cannot be instantiated. Path 'Sound', line 1, position 9.", exception.Message);
}
}

[TestFixture]
public class KnownBaseType_BaseIsAnInterfaceTests
{
[JsonConverter(typeof(JsonSubtypes), "Sound")]
public interface IAnimal
{
string Sound { get; }
}

[JsonSubtypes.KnownBaseType(typeof(IAnimal), "Bark")]
public class Dog : IAnimal
{
public string Sound { get; } = "Bark";
public string Breed { get; set; }
}

[JsonSubtypes.KnownBaseType(typeof(IAnimal), "Meow")]
public class Cat : IAnimal
{
public string Sound { get; } = "Meow";
public bool Declawed { get; set; }
}

[Test]
public void Test()
{
var animal = JsonConvert.DeserializeObject<IAnimal>("{\"Sound\":\"Bark\",\"Breed\":\"Jack Russell Terrier\"}");
Assert.AreEqual("Jack Russell Terrier", (animal as Dog)?.Breed);
}

[Test]
public void ConcurrentThreadTest()
{
Action test = () =>
{
var animal = JsonConvert.DeserializeObject<IAnimal>("{\"Sound\":\"Bark\",\"Breed\":\"Jack Russell Terrier\"}");
Assert.AreEqual("Jack Russell Terrier", (animal as Dog)?.Breed);
};

Parallel.For(0, 100, index => test());
}

[Test]
public void UnknownMappingFails()
{
var exception = Assert.Throws<JsonSerializationException>(() => JsonConvert.DeserializeObject<IAnimal>("{\"Sound\":\"Scream\"}"));
Assert.AreEqual("Could not create an instance of type JsonSubTypes.Tests.KnownBaseType_BaseIsAnInterfaceTests+IAnimal. Type is an interface or abstract class and cannot be instantiated. Path 'Sound', line 1, position 9.", exception.Message);
}
}
}
36 changes: 36 additions & 0 deletions JsonSubTypes.Tests/DeeplyNestedDeserializationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,40 @@ public void DeserializingDeeplyNestedJsonWithHighMaxDepthParsesCorrectly()
Assert.That(obj, Is.Not.Null);
}
}

[TestFixture]
public class KnownBaseType_DeeplyNestedDeserializationTests
{
[JsonConverter(typeof(JsonSubtypes), nameof(SubTypeClass.Discriminator))]
public abstract class MainClass
{
}

[JsonSubtypes.KnownBaseType(typeof(MainClass), "SubTypeClass")]
public class SubTypeClass : MainClass
{
public string Discriminator => "SubTypeClass";

public MainClass Child { get; set; }
}

[Test]
public void DeserializingDeeplyNestedJsonWithHighMaxDepthParsesCorrectly()
{
var root = new SubTypeClass();

var current = root;
for (var i = 0; i < 64; i++)
{
var child = new SubTypeClass();
current.Child = child;
current = child;
}

var json = JsonConvert.SerializeObject(root);

var obj = JsonConvert.DeserializeObject<MainClass>(json, new JsonSerializerSettings { MaxDepth = 65 });
Assert.That(obj, Is.Not.Null);
}
}
}
40 changes: 40 additions & 0 deletions JsonSubTypes.Tests/DemoAlternativeTypePropertyNameTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,46 @@ public void WhenNoMappingPossible()
Assert.AreEqual("Octopus", (animal as UnknownAnimal)?.Kind);
}
}

[TestFixture]
public class KnownBaseType_DemoAlternativeTypePropertyNameTests
{
[JsonConverter(typeof(JsonSubtypes), "Kind")]
[JsonSubtypes.FallBackSubType(typeof(UnknownAnimal))]
public interface IAnimal
{
string Kind { get; }
}

public class UnknownAnimal : IAnimal
{
public string Kind { get; set; }
}

[JsonSubtypes.KnownBaseType(typeof(IAnimal), null)]
public class Dog : IAnimal
{
public string Kind { get; } = null;
public string Breed { get; set; }
}

[Test]
public void Demo()
{
var animal =
JsonConvert.DeserializeObject<IAnimal>(
"{\"Kind\":null,\"Breed\":\"Jack Russell Terrier\"}");
Assert.AreEqual("Jack Russell Terrier", (animal as Dog)?.Breed);
}

[Test]
public void WhenNoMappingPossible()
{
var animal = JsonConvert.DeserializeObject<IAnimal>("{\"Kind\":\"Octopus\",\"Specie\":\"Octopus tetricus\"}");

Assert.AreEqual("Octopus", (animal as UnknownAnimal)?.Kind);
}
}
}
}

35 changes: 35 additions & 0 deletions JsonSubTypes.Tests/DemoCustomSubclassMappingTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,39 @@ public void Demo()
Assert.AreEqual(true, (animal as Cat)?.Declawed);
}
}

[TestFixture]
public class KnownBaseType_DemoCustomSubclassMappingTests
{
[JsonConverter(typeof(JsonSubtypes), "Sound")]
public class Animal
{
public virtual string Sound { get; }
public string Color { get; set; }
}

[JsonSubtypes.KnownBaseType(typeof(Animal), "Bark")]
public class Dog : Animal
{
public override string Sound { get; } = "Bark";
public string Breed { get; set; }
}

[JsonSubtypes.KnownBaseType(typeof(Animal), "Meow")]
public class Cat : Animal
{
public override string Sound { get; } = "Meow";
public bool Declawed { get; set; }
}

[Test]
public void Demo()
{
var animal = JsonConvert.DeserializeObject<Animal>("{\"Sound\":\"Bark\",\"Breed\":\"Jack Russell Terrier\"}");
Assert.AreEqual("Jack Russell Terrier", (animal as Dog)?.Breed);

animal = JsonConvert.DeserializeObject<Animal>("{\"Sound\":\"Meow\",\"Declawed\":\"true\"}");
Assert.AreEqual(true, (animal as Cat)?.Declawed);
}
}
}
101 changes: 99 additions & 2 deletions JsonSubTypes.Tests/DemoKnownSubTypeWithProperties.cs
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ public void ThrowIfManyMatches()
var jsonSerializationException = Assert.Throws<JsonSerializationException>(() => JsonConvert.DeserializeObject<Person>(json));
Assert.AreEqual("Ambiguous type resolution, expected only one type but got: JsonSubTypes.Tests.DemoKnownSubTypeWithMultipleProperties+Employee, JsonSubTypes.Tests.DemoKnownSubTypeWithMultipleProperties+Artist", jsonSerializationException.Message);
}

[JsonConverter(typeof(JsonSubtypes))]
[JsonSubtypes.KnownSubTypeWithProperty(typeof(ClassC), nameof(ClassC.Other), StopLookupOnMatch = true)]
[JsonSubtypes.KnownSubTypeWithProperty(typeof(ClassB), nameof(ClassB.Optional))]
Expand All @@ -97,7 +97,104 @@ public void StopLookupOnMatch()
string json = "{\"CommonProp\": null, \"Optional\": null, \"Other\": null}";

ClassA deserializeObject = JsonConvert.DeserializeObject<ClassA>(json);


Assert.IsInstanceOf<ClassC>(deserializeObject);
}
}

[TestFixture]
public class KnownBaseType_DemoKnownSubTypeWithMultipleProperties
{
[JsonConverter(typeof(JsonSubtypes))]
public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
}

[JsonSubtypes.KnownBaseTypeWithProperty(typeof(Person), "JobTitle")]
[JsonSubtypes.KnownBaseTypeWithProperty(typeof(Person), "Department")]
public class Employee : Person
{
public string Department { get; set; }
public string JobTitle { get; set; }
}

[JsonSubtypes.KnownBaseTypeWithProperty(typeof(Person), "Skill")]
public class Artist : Person
{
public string Skill { get; set; }
}

[Test]
public void Demo()
{
string json = "[{\"Department\":\"Department1\",\"FirstName\":\"FirstName1\",\"LastName\":\"LastName1\"}," +
"{\"JobTitle\":\"JobTitle1\",\"FirstName\":\"FirstName1\",\"LastName\":\"LastName1\"}," +
"{\"Skill\":\"Painter\",\"FirstName\":\"FirstName1\",\"LastName\":\"LastName1\"}]";


var persons = JsonConvert.DeserializeObject<ICollection<Person>>(json);
Assert.AreEqual("Painter", (persons.Last() as Artist)?.Skill);
}

[Test]
public void DemoDifferentCase()
{
string json = "[{\"Department\":\"Department1\",\"JobTitle\":\"JobTitle1\",\"FirstName\":\"FirstName1\",\"LastName\":\"LastName1\"}," +
"{\"Department\":\"Department1\",\"JobTitle\":\"JobTitle1\",\"FirstName\":\"FirstName1\",\"LastName\":\"LastName1\"}," +
"{\"skill\"" +
":\"Painter\",\"FirstName\":\"FirstName1\",\"LastName\":\"LastName1\"}]";


var persons = JsonConvert.DeserializeObject<ICollection<Person>>(json);
Assert.AreEqual("Painter", (persons.Last() as Artist)?.Skill);
}

[Test]
public void FallBackToPArentWhenNotFound()
{
string json = "[{\"Skl.\":\"Painter\",\"FirstName\":\"FirstName1\",\"LastName\":\"LastName1\"}]";

var persons = JsonConvert.DeserializeObject<ICollection<Person>>(json);
Assert.AreEqual(typeof(Person), persons.First().GetType());
}

[Test]
public void ThrowIfManyMatches()
{
string json = "{\r\n \"Name\": \"Foo\",\r\n \"Skill\": \"A\",\r\n \"JobTitle\": \"B\"\r\n}";

var jsonSerializationException = Assert.Throws<JsonSerializationException>(() => JsonConvert.DeserializeObject<Person>(json));
Assert.AreEqual("Ambiguous type resolution, expected only one type but got: JsonSubTypes.Tests.KnownBaseType_DemoKnownSubTypeWithMultipleProperties+Employee, JsonSubTypes.Tests.KnownBaseType_DemoKnownSubTypeWithMultipleProperties+Artist", jsonSerializationException.Message);
}

[JsonConverter(typeof(JsonSubtypes))]
[JsonSubtypes.FallBackSubType(typeof(ClassB))]
public class ClassA
{
public string CommonProp { get; set; }
}

[JsonSubtypes.KnownBaseTypeWithProperty(typeof(ClassA), nameof(ClassB.Optional))]
public class ClassB : ClassA
{
public bool? Optional { get; set; }
}

[JsonSubtypes.KnownBaseTypeWithProperty(typeof(ClassA), nameof(ClassC.Other), StopLookupOnMatch = true)]
public class ClassC : ClassB
{
public string Other { get; set; }
}

[Test]
public void StopLookupOnMatch()
{
string json = "{\"CommonProp\": null, \"Optional\": null, \"Other\": null}";

ClassA deserializeObject = JsonConvert.DeserializeObject<ClassA>(json);

Assert.IsInstanceOf<ClassC>(deserializeObject);
}
}
Expand Down
Loading