Skip to content

Commit

Permalink
Merge pull request #39 from manuc66/feature/handleDifferentPropertyNa…
Browse files Browse the repository at this point in the history
…meCase

Handle different type property name case
  • Loading branch information
manuc66 authored Apr 17, 2018
2 parents 2095cdd + 95acc66 commit e52102e
Show file tree
Hide file tree
Showing 6 changed files with 182 additions and 11 deletions.
13 changes: 13 additions & 0 deletions JsonSubTypes.Tests/DemoKnownSubTypeWithProperty.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,19 @@ public void Demo()
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()
{
Expand Down
1 change: 1 addition & 0 deletions JsonSubTypes.Tests/JsonSubTypes.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="BaseIsAnInterfaceTests.cs" />
<Compile Include="DiscriminatorOfDifferentKindTests.cs" />
<Compile Include="TypePropertyCase.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\JsonSubTypes\JsonSubTypes.csproj">
Expand Down
134 changes: 134 additions & 0 deletions JsonSubTypes.Tests/TypePropertyCase.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using NUnit.Framework;

namespace JsonSubTypes.Tests
{

public class TypePropertyCase
{
public class TypePropertyCase_LowerWithHigher
{
[JsonConverter(typeof(JsonSubtypes), "msgType")]
[JsonSubtypes.KnownSubType(typeof(Foo), 1)]
public abstract class DtoBase
{
public virtual int MsgType { get; set; }
}

class Foo : DtoBase
{
}

[Test]
public void FooParsingCamelCase()
{
var serializeObject = "{\"MsgType\":1}";
var msgType = JsonConvert.DeserializeObject<Foo>(serializeObject).MsgType;
Assert.AreEqual(1, msgType);
Assert.IsInstanceOf<Foo>(JsonConvert.DeserializeObject<DtoBase>(serializeObject));
}

[Test]
public void FooParsingLowerPascalCase()
{
var serializeObject = "{\"msgType\":1}";
Assert.AreEqual(1, JsonConvert.DeserializeObject<Foo>(serializeObject).MsgType);
Assert.IsInstanceOf<Foo>(JsonConvert.DeserializeObject<DtoBase>(serializeObject));
}
}

public class TypePropertyCase_HigherWithLower
{
[JsonConverter(typeof(JsonSubtypes), "MsgType")]
[JsonSubtypes.KnownSubType(typeof(Foo), 1)]
public abstract class DtoBase
{
[JsonProperty("msgType")]
public virtual int MsgType { get; set; }
}

class Foo : DtoBase
{
}

[Test]
public void FooParsingCamelCase()
{
var serializeObject = "{\"MsgType\":1}";
Assert.AreEqual(1, JsonConvert.DeserializeObject<Foo>(serializeObject).MsgType);
Assert.IsInstanceOf<Foo>(JsonConvert.DeserializeObject<DtoBase>(serializeObject));
}

[Test]
public void FooParsingLowerPascalCase()
{
var serializeObject = "{\"msgType\":1}";
Assert.AreEqual(1, JsonConvert.DeserializeObject<Foo>(serializeObject).MsgType);
Assert.IsInstanceOf<Foo>(JsonConvert.DeserializeObject<DtoBase>(serializeObject));
}
}

public class TypePropertyCase_RedirectLowerWithHigher
{
[JsonConverter(typeof(JsonSubtypes), "messageType")]
[JsonSubtypes.KnownSubType(typeof(Foo), 1)]
public abstract class DtoBase
{
[JsonProperty("MessageType")]
public virtual int MsgType { get; set; }
}

class Foo : DtoBase
{
}

[Test]
public void FooParsingCamelCase()
{
var serializeObject = "{\"MessageType\":1}";
Assert.AreEqual(1, JsonConvert.DeserializeObject<Foo>(serializeObject).MsgType);
Assert.IsInstanceOf<Foo>(JsonConvert.DeserializeObject<DtoBase>(serializeObject));
}

[Test]
public void FooParsingLowerPascalCase()
{
var serializeObject = "{\"messageType\":1}";
Assert.AreEqual(1, JsonConvert.DeserializeObject<Foo>(serializeObject).MsgType);
Assert.IsInstanceOf<Foo>(JsonConvert.DeserializeObject<DtoBase>(serializeObject));
}
}

public class TypePropertyCase_RedirectHigherWithLower
{
[JsonConverter(typeof(JsonSubtypes), "MessageType")]
[JsonSubtypes.KnownSubType(typeof(Foo), 1)]
public abstract class DtoBase
{
[JsonProperty("messageType")]
public virtual int MsgType { get; set; }
}

class Foo : DtoBase
{
}

[Test]
public void FooParsingCamelCase()
{
var serializeObject = "{\"MessageType\":1}";
Assert.AreEqual(1, JsonConvert.DeserializeObject<Foo>(serializeObject).MsgType);
Assert.IsInstanceOf<Foo>(JsonConvert.DeserializeObject<DtoBase>(serializeObject));
}

[Test]
public void FooParsingLowerPascalCase()
{
var serializeObject = "{\"messageType\":1}";
Assert.AreEqual(1, JsonConvert.DeserializeObject<Foo>(serializeObject).MsgType);
Assert.IsInstanceOf<Foo>(JsonConvert.DeserializeObject<DtoBase>(serializeObject));
}
}
}
}
40 changes: 30 additions & 10 deletions JsonSubTypes/JsonSubtypes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ public KnownSubTypeWithPropertyAttribute(Type subType, string propertyName)
}
}

protected readonly string TypeMappingPropertyName;
protected readonly string JsonDiscriminatorPropertyName;

[ThreadStatic] private static bool _isInsideRead;

Expand All @@ -88,9 +88,9 @@ public JsonSubtypes()
{
}

public JsonSubtypes(string typeMappingPropertyName)
public JsonSubtypes(string jsonDiscriminatorPropertyName)
{
TypeMappingPropertyName = typeMappingPropertyName;
JsonDiscriminatorPropertyName = jsonDiscriminatorPropertyName;
}

public override bool CanConvert(Type objectType)
Expand Down Expand Up @@ -206,7 +206,7 @@ private static JsonReader CreateAnotherReader(JToken jToken, JsonReader reader)

private Type GetType(JObject jObject, Type parentType)
{
if (TypeMappingPropertyName == null)
if (JsonDiscriminatorPropertyName == null)
{
return GetTypeByPropertyPresence(jObject, parentType);
}
Expand All @@ -222,7 +222,7 @@ private static Type GetTypeByPropertyPresence(IDictionary<string, JToken> jObjec
.Select(knownType =>
{
JToken ignore;
if (jObject.TryGetValue(knownType.PropertyName, out ignore))
if (TryGetValueInJson(jObject, knownType.PropertyName, out ignore))
return knownType.SubType;

return null;
Expand All @@ -232,20 +232,40 @@ private static Type GetTypeByPropertyPresence(IDictionary<string, JToken> jObjec

private Type GetTypeFromDiscriminatorValue(IDictionary<string, JToken> jObject, Type parentType)
{
JToken discriminatorToken;
if (!jObject.TryGetValue(TypeMappingPropertyName, out discriminatorToken))
JToken discriminatorValue;
if (!TryGetValueInJson(jObject, JsonDiscriminatorPropertyName, out discriminatorValue))
return null;

if (discriminatorToken.Type == JTokenType.Null)
if (discriminatorValue.Type == JTokenType.Null)
return null;

var typeMapping = GetSubTypeMapping(parentType);
if (typeMapping.Any())
{
return GetTypeFromMapping(typeMapping, discriminatorToken);
return GetTypeFromMapping(typeMapping, discriminatorValue);
}

return GetTypeByName(discriminatorToken.Value<string>(), parentType);
return GetTypeByName(discriminatorValue.Value<string>(), parentType);
}

private static bool TryGetValueInJson(IDictionary<string, JToken> jObject, string propertyName, out JToken value)
{
if (jObject.TryGetValue(propertyName, out value))
{
return true;
}

var matchingProperty = jObject
.Keys
.FirstOrDefault(jsonProperty => string.Equals(jsonProperty, propertyName, StringComparison.OrdinalIgnoreCase));

if (matchingProperty == null)
{
return false;
}

value = jObject[matchingProperty];
return true;
}

private static Type GetTypeByName(string typeName, Type parentType)
Expand Down
2 changes: 1 addition & 1 deletion JsonSubTypes/JsonSubtypesConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ public override void WriteJson(JsonWriter writer, object value, JsonSerializer s

var supportedType = _supportedTypes[value.GetType()];
var typeMappingPropertyValue = JToken.FromObject(supportedType, serializer);
jsonObj.Add(TypeMappingPropertyName, typeMappingPropertyValue);
jsonObj.Add(JsonDiscriminatorPropertyName, typeMappingPropertyValue);

jsonObj.WriteTo(writer);
}
Expand Down
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,13 @@ public class Cat : IAnimal {
}
```

The second parameter of the `JsonConverter` attribute is the JSON property name that will be use to retreive the type information from JSON.

```csharp
var animal = JsonConvert.DeserializeObject<IAnimal>("{\"Kind\":\"Dog\",\"Breed\":\"Jack Russell Terrier\"}");
Assert.AreEqual("Jack Russell Terrier", (Animal as Dog)?.Breed);
```

N.B.: Also works with fully qualified type name

## DeserializeObject with custom type mapping
Expand Down

0 comments on commit e52102e

Please sign in to comment.