00af4ad338
* Use a concurrent dictionary to cache types since the converter is effectively a singleton * Don't modify the collection in the getter --------- Co-authored-by: Oğuzhan Koral <45078678+oguzhankoral@users.noreply.github.com>
133 lines
4.7 KiB
C#
133 lines
4.7 KiB
C#
using System.Collections.Concurrent;
|
|
using System.Diagnostics;
|
|
using System.Reflection;
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
using Speckle.Newtonsoft.Json;
|
|
using Speckle.Newtonsoft.Json.Linq;
|
|
using Speckle.Newtonsoft.Json.Serialization;
|
|
using Speckle.Sdk.Serialisation;
|
|
|
|
namespace Speckle.Connectors.DUI.Utils;
|
|
|
|
/// <summary>
|
|
/// This converter ensures we can do polymorphic deserialization to concrete types. This converter is intended
|
|
/// for use only with UI bound types, not Speckle Bases.
|
|
/// </summary>
|
|
//this is effectively a singleton because BrowserBridge and TopLevelExceptionHandler use it
|
|
public class DiscriminatedObjectConverter(IServiceProvider serviceProvider) : JsonConverter<DiscriminatedObject>
|
|
{
|
|
// POC: remove, replace with DI
|
|
private static readonly ConcurrentDictionary<string, Type?> s_typeCache = new();
|
|
private static readonly Speckle.Newtonsoft.Json.JsonSerializer s_localSerializer =
|
|
new()
|
|
{
|
|
DefaultValueHandling = DefaultValueHandling.Ignore,
|
|
ContractResolver = new CamelCasePropertyNamesContractResolver(),
|
|
NullValueHandling = NullValueHandling.Ignore
|
|
};
|
|
|
|
public override void WriteJson(
|
|
JsonWriter writer,
|
|
DiscriminatedObject? value,
|
|
Speckle.Newtonsoft.Json.JsonSerializer serializer
|
|
)
|
|
{
|
|
if (value is null)
|
|
{
|
|
return;
|
|
}
|
|
var jo = JObject.FromObject(value, s_localSerializer);
|
|
jo.WriteTo(writer);
|
|
}
|
|
|
|
public override DiscriminatedObject? ReadJson(
|
|
JsonReader reader,
|
|
Type objectType,
|
|
DiscriminatedObject? existingValue,
|
|
bool hasExistingValue,
|
|
Speckle.Newtonsoft.Json.JsonSerializer serializer
|
|
)
|
|
{
|
|
JObject jsonObject = JObject.Load(reader);
|
|
|
|
var typeName =
|
|
jsonObject.Value<string>("typeDiscriminator")
|
|
?? throw new SpeckleDeserializeException(
|
|
"DUI3 Discriminator converter deserialization failed: did not find a typeDiscriminator field."
|
|
);
|
|
var type =
|
|
GetTypeByName(typeName)
|
|
?? throw new SpeckleDeserializeException(
|
|
"DUI3 Discriminator converter deserialization failed, type not found: " + typeName
|
|
);
|
|
var obj = ActivatorUtilities.CreateInstance(serviceProvider, type);
|
|
serializer.Populate(jsonObject.CreateReader(), obj);
|
|
|
|
// Store the JSON property names in the object for later comparison
|
|
if (obj is PropertyValidator pv)
|
|
{
|
|
// Capture property names from JSON
|
|
var jsonPropertyNames = jsonObject.Properties().Select(p => p.Name).ToList();
|
|
|
|
pv.JsonPropertyNames = jsonPropertyNames;
|
|
}
|
|
|
|
// POC: cast? throw if null?
|
|
return obj as DiscriminatedObject;
|
|
}
|
|
|
|
private static Type? GetTypeByName(string typeName) =>
|
|
s_typeCache.GetOrAdd(
|
|
typeName,
|
|
name =>
|
|
{
|
|
//POC: why does this exist like this?
|
|
// The assemblies within the CurrentDomain are not necessarily loaded
|
|
// probably we can leverage DI here so we already know the types, possibly DI plus an attribute
|
|
// then we can cache everything on startup
|
|
foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies().Reverse())
|
|
{
|
|
try
|
|
{
|
|
// POC: contains is weak
|
|
// working by accident, ModelCard is contained within SenderModelCard :O
|
|
// comparisons :D
|
|
var type = assembly.DefinedTypes.FirstOrDefault(t => !string.IsNullOrEmpty(t?.Name) && t?.Name == name);
|
|
if (type != null)
|
|
{
|
|
return type;
|
|
}
|
|
}
|
|
// POC: this Exception pattern is too broad and should be resticted but fixes the above issues
|
|
// the call above is causing load of all assemblies (which is also possibly not good)
|
|
// AND it explodes for me loading an exception, so at the last this should
|
|
// catch System.Reflection.ReflectionTypeLoadException (and anthing else DefinedTypes might throw)
|
|
// LATER COMMENT: Since discriminated object is only used in DUI3 models, we could restrict to only "this" assembly?
|
|
catch (ReflectionTypeLoadException ex)
|
|
{
|
|
// POC: logging
|
|
Debug.WriteLine("***" + ex.Message);
|
|
}
|
|
}
|
|
|
|
// should this throw instead? :/
|
|
return null;
|
|
}
|
|
);
|
|
}
|
|
|
|
public class AbstractConverter<TReal, TAbstract> : JsonConverter
|
|
{
|
|
public override bool CanConvert(Type objectType) => objectType == typeof(TAbstract);
|
|
|
|
public override object? ReadJson(
|
|
JsonReader reader,
|
|
Type objectType,
|
|
object? existingValue,
|
|
Speckle.Newtonsoft.Json.JsonSerializer serializer
|
|
) => serializer.Deserialize<TReal>(reader);
|
|
|
|
public override void WriteJson(JsonWriter writer, object? value, Speckle.Newtonsoft.Json.JsonSerializer serializer) =>
|
|
serializer.Serialize(writer, value);
|
|
}
|