Questo può essere fatto sfruttando il fatto che i tuoi tipi e d i tipi SignalR sono in diversi assemblies. L'idea è quella di creare un che si applica a tutti i tipi dagli assembly. Quando si incontra per la prima volta un oggetto di uno degli assiemi nel grafico dell'oggetto (probabilmente come oggetto radice), il convertitore imposta temporaneamente jsonSerializer.TypeNameHandling = TypeNameHandling.Auto
, quindi procede con la serializzazione standard per il tipo, disabilitando se stesso per la durata per impedire la ricorsione infinita:
public class PolymorphicAssemblyRootConverter : JsonConverter
{
[ThreadStatic]
static bool disabled;
// Disables the converter in a thread-safe manner.
bool Disabled { get { return disabled; } set { disabled = value; } }
public override bool CanWrite { get { return !Disabled; } }
public override bool CanRead { get { return !Disabled; } }
readonly HashSet<Assembly> assemblies;
public PolymorphicAssemblyRootConverter(IEnumerable<Assembly> assemblies)
{
if (assemblies == null)
throw new ArgumentNullException();
this.assemblies = new HashSet<Assembly>(assemblies);
}
public override bool CanConvert(Type objectType)
{
return assemblies.Contains(objectType.Assembly);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
using (new PushValue<bool>(true,() => Disabled, val => Disabled = val)) // Prevent infinite recursion of converters
using (new PushValue<TypeNameHandling>(TypeNameHandling.Auto,() => serializer.TypeNameHandling, val => serializer.TypeNameHandling = val))
{
return serializer.Deserialize(reader, objectType);
}
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
using (new PushValue<bool>(true,() => Disabled, val => Disabled = val)) // Prevent infinite recursion of converters
using (new PushValue<TypeNameHandling>(TypeNameHandling.Auto,() => serializer.TypeNameHandling, val => serializer.TypeNameHandling = val))
{
// Force the $type to be written unconditionally by passing typeof(object) as the type being serialized.
serializer.Serialize(writer, value, typeof(object));
}
}
}
public struct PushValue<T> : IDisposable
{
Action<T> setValue;
T oldValue;
public PushValue(T value, Func<T> getValue, Action<T> setValue)
{
if (getValue == null || setValue == null)
throw new ArgumentNullException();
this.setValue = setValue;
this.oldValue = getValue();
setValue(value);
}
#region IDisposable Members
// By using a disposable struct we avoid the overhead of allocating and freeing an instance of a finalizable class.
public void Dispose()
{
if (setValue != null)
setValue(oldValue);
}
#endregion
}
Poi in avvio si dovrebbe aggiungere questo convertitore al default JsonSerializer
, passando nelle assemblee per cui si desidera "$type"
applicato.
Aggiornamento
Se per qualsiasi motivo è scomodo per passare alla lista di assemblee in fase di avvio, è possibile attivare il convertitore da objectType.Namespace
. Tutti i tipi che vivono negli spazi dei nomi specificati verranno automaticamente serializzati con TypeNameHandling.Auto
.
In alternativa, si potrebbe introdurre un Attribute
che targets un'assemblea, classe o interfaccia e permette TypeNameHandling.Auto
quando combinato con il convertitore appropriato:
public class EnableJsonTypeNameHandlingConverter : JsonConverter
{
[ThreadStatic]
static bool disabled;
// Disables the converter in a thread-safe manner.
bool Disabled { get { return disabled; } set { disabled = value; } }
public override bool CanWrite { get { return !Disabled; } }
public override bool CanRead { get { return !Disabled; } }
public override bool CanConvert(Type objectType)
{
if (Disabled)
return false;
if (objectType.Assembly.GetCustomAttributes<EnableJsonTypeNameHandlingAttribute>().Any())
return true;
if (objectType.GetCustomAttributes<EnableJsonTypeNameHandlingAttribute>(true).Any())
return true;
foreach (var type in objectType.GetInterfaces())
if (type.GetCustomAttributes<EnableJsonTypeNameHandlingAttribute>(true).Any())
return true;
return false;
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
using (new PushValue<bool>(true,() => Disabled, val => Disabled = val)) // Prevent infinite recursion of converters
using (new PushValue<TypeNameHandling>(TypeNameHandling.Auto,() => serializer.TypeNameHandling, val => serializer.TypeNameHandling = val))
{
return serializer.Deserialize(reader, objectType);
}
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
using (new PushValue<bool>(true,() => Disabled, val => Disabled = val)) // Prevent infinite recursion of converters
using (new PushValue<TypeNameHandling>(TypeNameHandling.Auto,() => serializer.TypeNameHandling, val => serializer.TypeNameHandling = val))
{
// Force the $type to be written unconditionally by passing typeof(object) as the type being serialized.
serializer.Serialize(writer, value, typeof(object));
}
}
}
[System.AttributeUsage(System.AttributeTargets.Assembly | System.AttributeTargets.Class | System.AttributeTargets.Interface)]
public class EnableJsonTypeNameHandlingAttribute : System.Attribute
{
public EnableJsonTypeNameHandlingAttribute()
{
}
}
Nota - testato con i vari casi di test, ma non per sé SignalR da quando don Al momento è installato.
C'è un motivo particolare per cui non si desidera utilizzare il serializzatore Json predefinito da SignalR? –
@Matei_Radu Perché usa 'TypenameHandling.None' e ho bisogno di' Auto'. – Voo
Non ho SignalR per testare con; è il problema che hai bisogno della proprietà '$ type' sull'oggetto * root * json, o su qualche oggetto nidificato? E se il primo, c'è un modo per rilassare quel requisito, magari restituendo un oggetto wrapper come root? – dbc