Skip to content

Commit

Permalink
JsonTypeConverter
Browse files Browse the repository at this point in the history
  • Loading branch information
o.nadymov committed Jun 17, 2024
1 parent 54b09a8 commit d1f0874
Show file tree
Hide file tree
Showing 6 changed files with 257 additions and 4 deletions.
25 changes: 25 additions & 0 deletions src/Spoleto.Common.Tests/JsonHelperTests.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Spoleto.Common.Helpers;
using Spoleto.Common.Tests.Objects;

namespace Spoleto.Common.Tests
{
Expand Down Expand Up @@ -86,5 +87,29 @@ public void EnumWithIntEnum()
Assert.That(json.Contains("\"200\""), Is.False);
});
}
[Test]
public void EnumWithType()
{
// Arrange
var systemType = typeof(int);
var myType = typeof(TestClass);
var obj = new TestTypeClass
{
MyType = myType,
SystemType = systemType
};

// Act
var json = JsonHelper.ToJson(obj);
var fromJson = JsonHelper.FromJson<TestTypeClass>(json);

// Assert
Assert.Multiple(() =>
{
Assert.That(fromJson.MyType, Is.EqualTo(obj.MyType));
Assert.That(fromJson.SystemType, Is.EqualTo(obj.SystemType));
});
}
}
}
9 changes: 9 additions & 0 deletions src/Spoleto.Common.Tests/Objects/TestTypeClass.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace Spoleto.Common.Tests.Objects
{
public class TestTypeClass
{
public Type MyType { get; set; }

public Type SystemType { get; set; }
}
}
6 changes: 2 additions & 4 deletions src/Spoleto.Common/Helpers/JsonHelper.cs
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.Encodings.Web;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Text.Unicode;
using System.Threading.Tasks;
using System.Web;
using Spoleto.Common.JsonConverters;

namespace Spoleto.Common.Helpers
{
Expand All @@ -29,6 +26,7 @@ static JsonHelper()
};

_defaultSerializerOptions.Converters.Add(new JsonStringEnumConverter());
_defaultSerializerOptions.Converters.Add(new JsonTypeConverter());
}

/// <summary>
Expand Down
100 changes: 100 additions & 0 deletions src/Spoleto.Common/Helpers/SerializedTypeHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Text.RegularExpressions;

namespace Spoleto.Common.Helpers
{
/// <summary>
/// The helper for serialization of types.
/// </summary>
public static class SerializedTypeHelper
{
/// <summary>
/// Custom binder type.
/// </summary>
public static Func<string, Type> BindToType { get; set; } = DefaultBindToType;

/// <summary>
/// Gets the type name does not have Version, Culture, Public token.
/// </summary>
public static bool RemoveAssemblyVersion = true;

private static readonly Regex SubtractFullNameRegex = new Regex(@", Version=\d+.\d+.\d+.\d+, Culture=\w+, PublicKeyToken=\w+", RegexOptions.Compiled);

// mscorlib or System.Private.CoreLib
private static readonly bool IsMscorlib = typeof(int).AssemblyQualifiedName.Contains("mscorlib");

private static readonly Dictionary<string, Type> TypeCache = [];

Check failure on line 28 in src/Spoleto.Common/Helpers/SerializedTypeHelper.cs

View workflow job for this annotation

GitHub Actions / Build-Tests-Publish

Invalid expression term '['

Check failure on line 28 in src/Spoleto.Common/Helpers/SerializedTypeHelper.cs

View workflow job for this annotation

GitHub Actions / Build-Tests-Publish

Syntax error; value expected

Check failure on line 28 in src/Spoleto.Common/Helpers/SerializedTypeHelper.cs

View workflow job for this annotation

GitHub Actions / Build-Tests-Publish

Invalid expression term '['

Check failure on line 28 in src/Spoleto.Common/Helpers/SerializedTypeHelper.cs

View workflow job for this annotation

GitHub Actions / Build-Tests-Publish

Syntax error; value expected

/// <summary>
/// Deserializes the type.
/// </summary>
/// <param name="typeName"></param>
/// <returns></returns>
public static Type DeserializeType(string typeName)
{
lock (((ICollection)TypeCache).SyncRoot)
{
if (!TypeCache.TryGetValue(typeName, out var type))
{
type = BindToType(typeName);
if (type == null)
{
if (IsMscorlib && typeName.Contains("System.Private.CoreLib"))
{
typeName = typeName.Replace("System.Private.CoreLib", "mscorlib");
type = Type.GetType(typeName);
}
else if (!IsMscorlib && typeName.Contains("mscorlib"))
{
typeName = typeName.Replace("mscorlib", "System.Private.CoreLib");
type = Type.GetType(typeName);
}
else
{
type = Type.GetType(typeName);
}
}

if (type == null)
{
throw new TypeLoadException($"Cannot load type by the name <{typeName}>.");
}

TypeCache[typeName] = type;
}

return type;
}
}

/// <summary>
/// Builds the type name for serialization.
/// </summary>
/// <param name="type"></param>
/// <returns></returns>
public static string BuildTypeName(Type type)
{
if (RemoveAssemblyVersion)
{
string full = type.AssemblyQualifiedName;

var shortened = SubtractFullNameRegex.Replace(full, String.Empty);
if (Type.GetType(shortened) == null)
{
// if type cannot be found with shortened name - use full name
shortened = full;
}

return shortened;
}
else
{
return type.AssemblyQualifiedName;
}
}

private static Type DefaultBindToType(string typeName) => Type.GetType(typeName);
}
}
53 changes: 53 additions & 0 deletions src/Spoleto.Common/JsonConverters/JsonTypeConverter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
using System;
using System.Text.Json;
using System.Text.Json.Serialization;
using Spoleto.Common.Helpers;

namespace Spoleto.Common.JsonConverters
{
/// <summary>
/// The custom Json converter for the <see cref="Type"/>.
/// </summary>
public class JsonTypeConverter : JsonConverter<Type>
{
/// <summary>
/// The default constructor to initialize a Json converter for the <see cref="Type"/>.
/// </summary>
public JsonTypeConverter()
{
}

public override Type Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
if (reader.TokenType == JsonTokenType.Null)
return null;

var typeName = reader.GetString();

if (typeName == null)
return null;

var resolvedType = SerializedTypeHelper.DeserializeType(typeName);

if (resolvedType == null)
{
throw new JsonException($"Unable to resolve type {typeName}");
}

return resolvedType;
}

public override void Write(Utf8JsonWriter writer, Type value, JsonSerializerOptions options)
{
if (value == null)
{
writer.WriteNullValue();
return;
}

// Write the assembly-qualified name
var typeName = SerializedTypeHelper.BuildTypeName(value);
writer.WriteStringValue(typeName);
}
}
}
68 changes: 68 additions & 0 deletions src/Spoleto.Common/Objects/WebType.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
using System;
using System.Runtime.Serialization;
using Spoleto.Common.Helpers;

namespace Spoleto.Common.Objects
{
/// <summary>
/// String representation of Type.
/// </summary>
[DataContract]
[Serializable]
public class WebType
{
/// <summary>
/// Default constructor.
/// </summary>
public WebType()
{
}

/// <summary>
/// Constructor with parameter.
/// </summary>
/// <param name="type"></param>
public WebType(Type type)
{
if (type != null)
{
TypeName = SerializedTypeHelper.BuildTypeName(type);
}
}

/// <summary>
/// Full type name with assembly name.
/// </summary>
[DataMember]
public string TypeName { get; set; }

/// <summary>
/// Gets the real type.
/// </summary>
public Type GetRealType() => TypeName != null ? SerializedTypeHelper.DeserializeType(TypeName) : null;

/// <summary>
/// User-defined conversion from Type to WebType.
/// </summary>
/// <param name="type"></param>
public static explicit operator WebType(Type type)
{
return new WebType(type);
}

/// <summary>
/// User-defined conversion from WebType to Type.
/// </summary>
/// <param name="webType"></param>
public static explicit operator Type(WebType webType)
{
return webType?.GetRealType();
}

/// <summary>
/// Returns the string representation.
/// </summary>
/// <returns></returns>
public override String ToString() => TypeName;
}
}

0 comments on commit d1f0874

Please sign in to comment.