Skip to content

Commit

Permalink
Add AnyOf.Newtonsoft.Json project (#12)
Browse files Browse the repository at this point in the history
* AnyOfJsonConverter

* .

* .
  • Loading branch information
StefH committed Sep 15, 2021
1 parent 029d650 commit 9b7259d
Show file tree
Hide file tree
Showing 4 changed files with 189 additions and 0 deletions.
7 changes: 7 additions & 0 deletions AnyOf Solution.sln
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ConsoleAppUsesClassLibrarie
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ClassLibraryCommon", "examples\ClassLibraryCommon\ClassLibraryCommon.csproj", "{C7307358-326A-42D2-889C-61D1DAE285D2}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AnyOf.Newtonsoft.Json", "src\AnyOf.Newtonsoft.Json\AnyOf.Newtonsoft.Json.csproj", "{3BD0104F-3E63-4223-A4AD-372E68A6B0CB}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -96,6 +98,10 @@ Global
{C7307358-326A-42D2-889C-61D1DAE285D2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C7307358-326A-42D2-889C-61D1DAE285D2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C7307358-326A-42D2-889C-61D1DAE285D2}.Release|Any CPU.Build.0 = Release|Any CPU
{3BD0104F-3E63-4223-A4AD-372E68A6B0CB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3BD0104F-3E63-4223-A4AD-372E68A6B0CB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3BD0104F-3E63-4223-A4AD-372E68A6B0CB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3BD0104F-3E63-4223-A4AD-372E68A6B0CB}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -113,6 +119,7 @@ Global
{6A1126B2-EFD3-4758-907C-31B010178D84} = {38805E90-A955-4097-97DC-537F7A0CD773}
{8F07BB3B-6C03-4533-89D0-D4C6106CCB0C} = {38805E90-A955-4097-97DC-537F7A0CD773}
{C7307358-326A-42D2-889C-61D1DAE285D2} = {38805E90-A955-4097-97DC-537F7A0CD773}
{3BD0104F-3E63-4223-A4AD-372E68A6B0CB} = {0B71158B-576B-4498-9D40-141D554D1272}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {3586BB89-A981-4CD0-88DB-2DF513FE2B90}
Expand Down
19 changes: 19 additions & 0 deletions src/AnyOf.Newtonsoft.Json/AnyOf.Newtonsoft.Json.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<Title>AnyOf.Newtonsoft.Json</Title>
<TargetFrameworks>net45;netstandard1.3;netstandard2.0;netstandard2.1</TargetFrameworks>
<ProjectGuid>{31D0104F-3E63-4223-A4AD-372E68A6B0CB}</ProjectGuid>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="9.0.1" />
</ItemGroup>

<ItemGroup Condition=" '$(TargetFramework)' == 'netstandard1.3' ">
<PackageReference Include="System.Reflection" Version="4.3.0" />
<PackageReference Include="System.Reflection.Extensions" Version="4.3.0" />
<PackageReference Include="System.Reflection.TypeExtensions" Version="4.3.0" />
</ItemGroup>

</Project>
149 changes: 149 additions & 0 deletions src/AnyOf.Newtonsoft.Json/AnyOfJsonConverter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.Serialization;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

namespace RestEaseClientGeneratorConsoleApp
{
public class AnyOfJsonConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
if (value == null)
{
if (serializer.NullValueHandling == NullValueHandling.Include)
{
serializer.Serialize(writer, value);
}
return;
}

var currentValue = GetPropertyValue(value, "CurrentValue");
serializer.Serialize(writer, currentValue);
}

/// <summary>
/// See
/// - https://stackoverflow.com/questions/8030538/how-to-implement-custom-jsonconverter-in-json-net
/// - https://stackoverflow.com/a/59286262/255966
/// </summary>
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var jObject = JObject.Load(reader);

Type mostSuitableType = null;
int countOfMaxMatchingProperties = -1;

// Take the names of elements from json data
var jObjectKeys = GetKeys(jObject);

// Take the public properties of the parent class
var objectTypeProps = objectType.GetProperties(BindingFlags.Instance | BindingFlags.Public)
.Select(p => p.Name).ToHashSet();

// Trying to find the right "KnownType"
foreach (var knownType in GetPropertyValue<Type[]>(existingValue, "Types"))
{
// Select properties of the inheritor, except properties from the parent class and properties with "ignore" attributes
var notIgnoreProps = knownType.GetProperties(BindingFlags.Instance | BindingFlags.Public)
.Where(p => !objectTypeProps.Contains(p.Name) && p.CustomAttributes.All(a => a.AttributeType != typeof(JsonIgnoreAttribute)));

// Get serializable property names
var jsonNameFields = notIgnoreProps.Select(prop =>
{
string jsonFieldName = null;
var jsonPropertyAttribute = prop.CustomAttributes.FirstOrDefault(a => a.AttributeType == typeof(JsonPropertyAttribute));
if (jsonPropertyAttribute != null)
{
// Take the name of the json element from the attribute constructor
int constructorArgumentsCount = jsonPropertyAttribute.ConstructorArguments.Count;
if (constructorArgumentsCount > 0)
{
var argument = jsonPropertyAttribute.ConstructorArguments.First();
if (argument.ArgumentType == typeof(string) && !string.IsNullOrEmpty(argument.Value as string))
{
jsonFieldName = (string)argument.Value;
}
}
}
// Otherwise, take the name of the property
if (string.IsNullOrEmpty(jsonFieldName))
{
jsonFieldName = prop.Name;
}
return jsonFieldName;
});

var jKnownTypeKeys = new HashSet<string>(jsonNameFields);

// By intersecting the sets of names we determine the most suitable inheritor
int count = jObjectKeys.Intersect(jKnownTypeKeys).Count();

if (count == jKnownTypeKeys.Count)
{
mostSuitableType = knownType;
break;
}

if (count > countOfMaxMatchingProperties)
{
countOfMaxMatchingProperties = count;
mostSuitableType = knownType;
}
}

if (mostSuitableType != null)
{
object target = Activator.CreateInstance(mostSuitableType);

using (JsonReader jObjectReader = CopyReaderForObject(reader, jObject))
{
serializer.Populate(jObjectReader, target);
}

return Activator.CreateInstance(objectType, target);
}

throw new SerializationException($"Could not deserialize {objectType}, no suitable type found.");
}

public override bool CanConvert(Type objectType)
{
return objectType.FullName.StartsWith("AnyOfTypes.AnyOf`");
}

private HashSet<string> GetKeys(JObject obj)
{
return new HashSet<string>(((IEnumerable<KeyValuePair<string, JToken>>)obj).Select(k => k.Key));
}

private static JsonReader CopyReaderForObject(JsonReader reader, JObject jObject)
{
var jObjectReader = jObject.CreateReader();
jObjectReader.CloseInput = reader.CloseInput;
jObjectReader.Culture = reader.Culture;
jObjectReader.DateFormatString = reader.DateFormatString;
jObjectReader.DateParseHandling = reader.DateParseHandling;
jObjectReader.DateTimeZoneHandling = reader.DateTimeZoneHandling;
jObjectReader.FloatParseHandling = reader.FloatParseHandling;
jObjectReader.MaxDepth = reader.MaxDepth;
jObjectReader.SupportMultipleContent = reader.SupportMultipleContent;
return jObjectReader;
}

private static T GetPropertyValue<T>(object value, string name)
{
return (T)GetPropertyValue(value, name);
}

private static object GetPropertyValue(object value, string name)
{
return value.GetType().GetProperty(name).GetValue(value);
}
}
}
14 changes: 14 additions & 0 deletions src/AnyOf.Newtonsoft.Json/Compatiblity/LinqExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#if !NETSTANDARD2_1_OR_GREATER
using System.Collections.Generic;

namespace System.Linq
{
internal static class LinqExtensions
{
public static HashSet<T> ToHashSet<T>(this IEnumerable<T> source, IEqualityComparer<T> comparer = null)
{
return new HashSet<T>(source, comparer);
}
}
}
#endif

0 comments on commit 9b7259d

Please sign in to comment.