For example we have two classes
class FooA
{
[SomeSpecialAttribute]
public int SomeValueA { get; set; }
public int SomeValueB { get; set; }
public int SomeValueC { get; set; }
}
class FooB
{
public FooA FooA { get; set; }
}
I use Json.NET, max depth is 1. While serializing FooA it should output all properties as usual, but while serializing FooB it should output only one FooA's property which has special attribute. So only while resolving nested reference properties (Depth > 0) we should get a single field.
Output should be: { "FooA": { "SomeValueA": "0" } }
Any ideas?
The basic difficulty here is that Json.NET is a contract-based serializer which creates a contract for each type to be serialized, then serializes according to the contract. No matter where a type appears in the object graph, the same contract applies. But you want to selectively include properties for a given type depending on its depth in the object graph, which conflicts with the basic "one type one contract" design and thus requires some work.
One way to accomplish what you want would be to create a JsonConverter that performs a default serialization for each object, then prunes undesired properties, along the lines of Generic method of modifying JSON before being returned to client. Note that this has problems with recursive structures such as trees, because the converter must disable itself for child nodes to avoid infinite recursion.
Another possibility would be to create a custom IContractResolver that returns a different contract for each type depending on the serialization depth. This must needs make use of serialization callbacks to track when object serialization begins and ends, since serialization depth is not made known to the contract resolver:
[System.AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = true, Inherited = true)]
public class JsonIncludeAtDepthAttribute : System.Attribute
{
public JsonIncludeAtDepthAttribute()
{
}
}
public class DepthPruningContractResolver : IContractResolver
{
readonly int depth;
public DepthPruningContractResolver()
: this(0)
{
}
public DepthPruningContractResolver(int depth)
{
if (depth < 0)
throw new ArgumentOutOfRangeException("depth");
this.depth = depth;
}
[ThreadStatic]
static DepthTracker currentTracker;
static DepthTracker CurrentTracker { get { return currentTracker; } set { currentTracker = value; } }
class DepthTracker : IDisposable
{
int isDisposed;
DepthTracker oldTracker;
public DepthTracker()
{
isDisposed = 0;
oldTracker = CurrentTracker;
currentTracker = this;
}
#region IDisposable Members
public void Dispose()
{
if (0 == Interlocked.Exchange(ref isDisposed, 1))
{
CurrentTracker = oldTracker;
oldTracker = null;
}
}
#endregion
public int Depth { get; set; }
}
abstract class DepthTrackingContractResolver : DefaultContractResolver
{
static DepthTrackingContractResolver() { } // Mark type with beforefieldinit.
static SerializationCallback OnSerializing = (o, context) =>
{
if (CurrentTracker != null)
CurrentTracker.Depth++;
};
static SerializationCallback OnSerialized = (o, context) =>
{
if (CurrentTracker != null)
CurrentTracker.Depth--;
};
protected override JsonObjectContract CreateObjectContract(Type objectType)
{
var contract = base.CreateObjectContract(objectType);
contract.OnSerializingCallbacks.Add(OnSerializing);
contract.OnSerializedCallbacks.Add(OnSerialized);
return contract;
}
}
sealed class RootContractResolver : DepthTrackingContractResolver
{
// As of 7.0.1, Json.NET suggests using a static instance for "stateless" contract resolvers, for performance reasons.
// http://www.newtonsoft.com/json/help/html/ContractResolver.htm
// http://www.newtonsoft.com/json/help/html/M_Newtonsoft_Json_Serialization_DefaultContractResolver__ctor_1.htm
// "Use the parameterless constructor and cache instances of the contract resolver within your application for optimal performance."
static RootContractResolver instance;
static RootContractResolver() { instance = new RootContractResolver(); }
public static RootContractResolver Instance { get { return instance; } }
}
sealed class NestedContractResolver : DepthTrackingContractResolver
{
static NestedContractResolver instance;
static NestedContractResolver() { instance = new NestedContractResolver(); }
public static NestedContractResolver Instance { get { return instance; } }
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
var property = base.CreateProperty(member, memberSerialization);
if (property.AttributeProvider.GetAttributes(typeof(JsonIncludeAtDepthAttribute), true).Count == 0)
{
property.Ignored = true;
}
return property;
}
}
public static IDisposable CreateTracker()
{
return new DepthTracker();
}
#region IContractResolver Members
public JsonContract ResolveContract(Type type)
{
if (CurrentTracker != null && CurrentTracker.Depth > depth)
return NestedContractResolver.Instance.ResolveContract(type);
else
return RootContractResolver.Instance.ResolveContract(type);
}
#endregion
}
Then mark your classes as follows:
class FooA
{
[JsonIncludeAtDepthAttribute]
public int SomeValueA { get; set; }
public int SomeValueB { get; set; }
public int SomeValueC { get; set; }
}
class FooB
{
public FooA FooA { get; set; }
}
And serialize as follows:
var settings = new JsonSerializerSettings { ContractResolver = new DepthPruningContractResolver(depth), Formatting = Formatting.Indented };
using (DepthPruningContractResolver.CreateTracker())
{
var jsonB = JsonConvert.SerializeObject(foob, settings);
Console.WriteLine(jsonB);
var jsonA = JsonConvert.SerializeObject(foob.FooA, settings);
Console.WriteLine(jsonA);
}
The slightly awkward CreateTracker() is needed to ensure that, in the event an exception is thrown partway through serialization, the current object depth gets reset and does not affect future calls to JsonConvert.SerializeObject().
This solution assumes you don't want to change all serialization of the FooA class. If this is the case, you should create your own JsonConverter.
public class FooConverter : JsonConverter
{
public FooConveter(params Type[] parameterTypes)
{
}
public override bool CanConvert(Type objectType)
{
return objectType.IsAssignableFrom(typeof(FooA));
}
public override object ReadJson(JsonReader reader, Type objectType)
{
//Put your code to deserialize FooA here.
//You probably don't need it based on the scope of your question.
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
//Code to serialize FooA.
if (value == null)
{
writer.WriteNull();
return;
}
//Only serialize SomeValueA
var foo = value as FooA;
writer.WriteStartObject();
writer.WritePropertyName("FooA");
writer.Serialize(writer, foo.SomeValueA);
writer.WriteEndObject();
}
}
And use your converter in your code as
class FooB
{
[FooConverter]
public FooA FooA { get; set; }
}
Otherwise, you can use the JsonIgnore attribute to ignore the fields in FooA that you don't want serialized. Keep in mind, the tradeoff there is that whenever you convert FooA to Json, it will always ignore fields marked with that attribute.
Related
So let's say I have a:
List<IInterface> list;
that has been serialized with TypeNameHandling.Auto, so it has "dynamic" type information. I can deserialize it fine as Newtonsoft.Json can recognize the type from the $type and Json can use the correct constructor. So far so good.
Now say I want to override the creation converter with a mehtod:
CustomCreationConverter<IInterface>
that overrides the creation of the object:
public override IInterface Create(Type objectType)
At this point objectType will always be IInterface and not a derived implementation, so I have no way to create the correct object. The meta-information of $type is now lost.
Is there an elegant way to fix this?
Here would be an attempt that does not work:
public class CustomConverter : CustomCreationConverter<Example.IInterface> {
public override Example.IInterface Create(Type objectType) {
return Example.MakeObject(objectType); // this won't work, objectType will always be IInterface
}
}
public class Example {
public interface IInterface { };
public class A : IInterface { public int content; };
public class B : IInterface { public float data; };
public static IInterface MakeObject(Type t) {
if (t == typeof(IInterface)) {
throw new Exception();
}
return t == typeof(A) ? new A() : new B();
}
public static void Serialize() {
var settings = new JsonSerializerSettings() {
TypeNameHandling = TypeNameHandling.Auto
};
JsonSerializer serializer = JsonSerializer.Create(settings);
// serializer.Converters.Add(new CustomConverter()); // ?? can't have both, either CustomConverter or $type
List<IInterface> list = new() { MakeObject(typeof(A)), MakeObject(typeof(B)) };
using (StreamWriter sw = new("example.json")) {
serializer.Serialize(sw, list);
}
// Now read back example.json into a List<IInterface> using MakeObject
// Using CustomConverter won't work
using (JsonTextReader rd = new JsonTextReader(new StreamReader("example.json"))) {
List<IInterface> list2 = serializer.Deserialize<List<IInterface>>(rd);
}
}
}
Once you provide a custom converter such as CustomCreationConverter<T> for a type, the converter is responsible for all the deserialization logic including logic for type selection logic that would normally be implemented by TypeNameHandling. If you only want to inject a custom factory creation method and leave all the rest of the deserialization logic unchanged, you could create your own custom contract resolver and inject the factory method as JsonContract.DefaultCreator.
To implement this, first define the following factory interface and contract resolver:
public interface IObjectFactory<out T>
{
bool CanCreate(Type type);
T Create(Type type);
}
public class ObjectFactoryContractResolver : DefaultContractResolver
{
readonly IObjectFactory<object> factory;
public ObjectFactoryContractResolver(IObjectFactory<object> factory) => this.factory = factory ?? throw new ArgumentNullException(nameof(factory));
protected override JsonContract CreateContract(Type objectType)
{
var contract = base.CreateContract(objectType);
if (factory.CanCreate(objectType))
{
contract.DefaultCreator = () => factory.Create(objectType);
contract.DefaultCreatorNonPublic = false;
}
return contract;
}
}
Next, refactor your IInterface class hierarchy to make use of an IObjectFactory as an object creation factory:
public class InterfaceFactory : IObjectFactory<IInterface>
{
public InterfaceFactory(string runtimeId) => this.RuntimeId = runtimeId; // Some value to inject into the constructor
string RuntimeId { get; }
public bool CanCreate(Type type) => !type.IsAbstract && typeof(IInterface).IsAssignableFrom(type);
public IInterface Create(Type type) => type switch
{
var t when t == typeof(A) => new A(RuntimeId),
var t when t == typeof(B) => new B(RuntimeId),
_ => throw new NotImplementedException(type.ToString()),
};
}
public interface IInterface
{
public string RuntimeId { get; }
}
public class A : IInterface
{
[JsonIgnore] public string RuntimeId { get; }
internal A(string id) => this.RuntimeId = id;
public int content { get; set; }
}
public class B : IInterface
{
[JsonIgnore] public string RuntimeId { get; }
internal B(string id) => this.RuntimeId = id;
public float data { get; set; }
}
(Here RuntimeId is some value that needs to be injected during object creation.)
Now you will be able to construct your list as follows:
var valueToInject = "some value to inject";
var factory = new InterfaceFactory(valueToInject);
List<IInterface> list = new() { factory.Create(typeof(A)), factory.Create(typeof(B)) };
And serialize and deserialize as follows:
var resolver = new ObjectFactoryContractResolver(factory)
{
// Set any necessary properties e.g.
NamingStrategy = new CamelCaseNamingStrategy(),
};
var settings = new JsonSerializerSettings
{
ContractResolver = resolver,
TypeNameHandling = TypeNameHandling.Auto,
};
var json = JsonConvert.SerializeObject(list, Formatting.Indented, settings);
var list2 = JsonConvert.DeserializeObject<List<IInterface>>(json, settings);
Notes:
Newtosoft recommends that you cache and reuse your contract resolvers for best performance.
Newtonsoft also recommends that
TypeNameHandling should be used with caution when your application deserializes JSON from an external source. Incoming types should be validated with a custom SerializationBinder when deserializing with a value other than None.
For why, see e.g. TypeNameHandling caution in Newtonsoft Json or External json vulnerable because of Json.Net TypeNameHandling auto?.
Demo fiddle here.
I am creating a project that will manage app configurations. It will be very generic, reusable across different apps (with different config models on each) and very flexible - including the ability to create/save/store/read/merge partial configurations from multiple sources.
Without getting too much into details, here's an example of what I need to do.
I have a class like below:
public class TestConfigModel
{
public int SomeIntValue { get; set; }
public string SomeStringValue { get; set; }
public TestConfigSubsection Subsection { get; set; }
}
public class TestConfigSubsection
{
public System.DayOfWeek SomeSubsectionEnumValue { get; set; }
public Guid SomeSubsectionGuidValue { get; set; }
}
I need to dynamically generate a version of this model that has all properties nullable (unless they already take a null):
public class TestConfigModelNullable
{
public int? SomeIntValue { get; set; }
public string SomeStringValue { get; set; } // already takes a null
public TestConfigSubsection Subsection { get; set; } // already takes a null
}
public class TestConfigSubsectionNullable
{
public System.DayOfWeek? SomeSubsectionEnumValue { get; set; }
public Guid? SomeSubsectionGuidValue { get; set; }
}
Example use:
I have a default (complete) config like so:
var aConfigInstance = new TestConfigModel()
{
SomeIntValue = 3,
SomeStringValue = "hey",
Subsection = new TestConfigSubsection()
{
SomeSubsectionEnumValue = DayOfWeek.Thursday,
SomeSubsectionGuidValue = Guid.Parse("{2C475019-5AAC-43C6-AC87-21947A40E3B7}")
}
};
Now, I need to be able to create, serialize, store and later deserialize and operate on a partial configuration model, like below:
var aPartialConfigInstance = new TestConfigModelNullable()
{
SomeIntValue = 4,
Subsection = new TestConfigSubsection()
{
SomeSubsectionEnumValue = DayOfWeek.Monday
}
};
... with all missing properties null. If I try to do the same with the original class, all the other non-nullable fields will receive default values and that would be bad (how do I tell if int value of 0 is intended or not? Maybe it makes sense for the consumer app).
However, I'm new to reflection in general and not sure how to approach this. Your help would be much appreciated :)
Recall that we don't know the model ahead of time.
I happened to implement several similar mechanisms in several different flavors. Implementing an "automagical" mechanism implies quite a bit of heavy lifting.
Here I wouldn't suggest generating separate nullable versions of the models. Instead, I would opt for making all model properties Optional<T>, which is like Nullable<T> but works for reference types as well. In this way, partial models will be represented with the same types as "base" models.
Such an approach will save the complexity of code generation (T4, Roslyn, CodeDom, or Reflection.Emit -- all these imply a lot of effort, including plugging them into the build process).
In addition, in either approach, a "merging" logic must be implemented which applies a partial model over a "base" one. In code generation approach, the merge logic can be generated as part of the nullable models. In Optional<T> approach, it can be either hard-coded or implemented in generic way with runtime Reflection (not Reflection.Emit). The hard-coded way appears to be the easiest, but for large number of models and properties, runtime Reflection may be a better fit.
How it will look
The models would look like this:
public class TestConfigModel
{
public Optional<int> SomeIntValue { get; set; }
public Optional<string> SomeStringValue { get; set; }
public Optional<TestConfigSubsection> Subsection { get; set; }
}
With the implicit conversion operators of Optional<T>, you'll be able to initialize section values as normally:
var config = new TestConfigModel {
SomeIntValue = 123,
SomeStringValue = "ABC",
Subsection = new TestConfigSubsection {
SomeSubsectionEnumValue = DayOfWeek.Thursday
}
};
Generic merging logic can be implemented by introducing an Apply method to Optional<T>:
Optional<T> Apply(Optional<T> partial, Func<T, T, Optional<T>> merge = null)
Every model will have to implement its own ApplyXxxx() method that will be passed in the merge parameter, like this:
public class TestConfigModel
{
// ...properties
public Optional<TestConfigModel> ApplyModel(TestConfigModel partial)
{
SomeIntValue = SomeIntValue.Apply(partial.SomeIntValue);
SomeStringValue = SomeStringValue.Apply(partial.SomeStringValue);
Subsection = Subsection.Apply(
partial.Subsection,
merge: (left, right) => left.ApplySubsection(right));
return this;
}
}
public class TestConfigSubsection
{
// ...properties
public Optional<TestConfigSubsection> ApplySubsection(TestConfigSubsection partial)
{
SomeSubsectionEnumValue = SomeSubsectionEnumValue.Apply(partial.SomeSubsectionEnumValue);
SomeSubsectionGuidValue = SomeSubsectionGuidValue.Apply(partial.SomeSubsectionGuidValue);
return this;
}
}
Optional<T>
Built-in implementation of Optional<T> is planned for C# 8, but it can be implemented easily (mostly similar to Nullable<T>).
public interface IOptional
{
bool HasValue { get; }
object Value { get; }
}
public struct Optional<T> : IOptional
{
private readonly bool _hasValue;
private readonly T _value;
public Optional(T value)
{
_value = value;
_hasValue = true;
}
public bool HasValue => _hasValue;
object IOptional.Value => Value;
public T Value
{
get
{
if (!_hasValue)
{
throw new InvalidOperationException("has no value");
}
return _value;
}
}
public T GetValueOrDefault() => _value;
public T GetValueOrDefault(T defaultValue)
{
if (!_hasValue)
{
return defaultValue;
}
return _value;
}
public bool IsNullValue => _hasValue && ReferenceEquals(_value, null);
public override bool Equals(object other)
{
if (other is Optional<T> otherOptional)
{
if (_hasValue != otherOptional.HasValue)
{
return false;
}
if (_hasValue)
{
return CompareValue(otherOptional.Value);
}
return true;
}
return false;
}
bool CompareValue(object otherValue)
{
if (_value == null)
{
return (otherValue == null);
}
return _value.Equals(otherValue);
}
public override int GetHashCode()
{
if (_hasValue || ReferenceEquals(_value, null))
{
return 0;
}
return _value.GetHashCode();
}
public override string ToString()
{
if (!_hasValue || ReferenceEquals(_value, null))
{
return "";
}
return _value.ToString();
}
public Optional<T> Apply(Optional<T> partial, Func<T, T, Optional<T>> merge = null)
{
if (!_hasValue && partial.HasValue)
{
return partial;
}
if (_hasValue && partial.HasValue)
{
if (ReferenceEquals(_value, null))
{
return partial.Value;
}
if (!ReferenceEquals(partial.Value, null))
{
if (merge != null)
{
return merge(_value, partial.Value);
}
throw new InvalidOperationException("both values exist and merge not provided");
}
}
return this;
}
public static implicit operator Optional<T>(T value)
{
return new Optional<T>(value);
}
public static explicit operator T(Optional<T> value)
{
return value.Value;
}
}
Serialization
The last thing left is to teach the serializers to handle Optional<T>. For instance, Newtonsoft.Json would require a custom JsonConverter. Below isn't a complete implementation, but it demonstrates the approach:
public class OptionalConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType.IsGenericType && objectType.GetGenericTypeDefinition() == typeof(Optional<>);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
// TODO: implement properly
// roughly the approach is like this:
var hasValue = reader.ReadAsBoolean().GetValueOrDefault();
var innerValue = hasValue
? serializer.Deserialize(reader, objectType.GetGenericArguments([0])
: null;
return Activator.CreateInstance(
objectType,
innerValue != null ? new[] {innerValue} : new object[0]);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
if (value is IOptional optional)
{
// TODO: implement writing
}
}
}
// Just for convenience
public Type CreateNullableTypeFrom<T>()
{
return CreateNullableTypeFrom(typeof(T));
}
public Type CreateNullableTypeFrom(Type typeToConvert)
{
// Get the AssemblyName where the type is defined
AssemblyName assembly = typeToConvert.Assembly.GetName();
AssemblyBuilder dynamicAssembly = AssemblyBuilder.DefineDynamicAssembly(assembly, AssemblyBuilderAccess.Run);
ModuleBuilder dynamicModule = dynamicAssembly.DefineDynamicModule(assembly.Name);
TypeBuilder typeBuilder = dynamicModule.DefineType(typeToConvert.Name + "Nullable");
// Loop through the properties
foreach(PropertyInfo property in typeToConvert.GetProperties())
{
// If property is value type, it can't be null
if(property.PropertyType.IsValueType)
{
// Create a nullable type for the property
typeBuilder.DefineProperty(property.Name, property.Attributes, typeof(Nullable<>).MakeGenericType(property.PropertyType), Type.EmptyTypes);
}
// The property can be null
else
{
// Create a similar property
typeBuilder.DefineProperty(property.Name, property.Attributes, property.PropertyType, Type.EmptyTypes);
}
}
// Finally, create the type
Type convertedType = typeBuilder.CreateType();
Console.WriteLine(convertedType.Name);
// Note: to access the properties of the converted type through reflection,
// use GetRuntimeProperties method, not GetProperties, since GetProperties
// will return an empty array because the type was created an runtime
return convertedType;
}
Im trying to write serializer which will serialize/deserialize to exact same class structure. So, even if default value of property is not provided - it should throw error. I thought what I accomplished it by setting member handling and contract resolver, but It wont work. Exceptions are not thrown where it should by all means (I explicitly specified it in contract resolver).
And here is my serializer:
public sealed class JsonSerializer : ISerializer
{
private readonly JsonSerializerSettings _settings = new JsonSerializerSettings
{
ContractResolver = new RequireObjectPropertiesContractResolver(),//Everything required. Null are allowed.
MissingMemberHandling = MissingMemberHandling.Error, //If something missing in target class - it should throw error.
Formatting = Formatting.Indented
};
public object Deserialize(string input, Type type)
{
return JsonConvert.DeserializeObject(input, type, _settings);
}
public string Serialize(object input)
{
return JsonConvert.SerializeObject(input, _settings);
}
private class RequireObjectPropertiesContractResolver : DefaultContractResolver
{
protected override JsonObjectContract CreateObjectContract(Type objectType)
{
var contract = base.CreateObjectContract(objectType);
contract.ItemRequired = Required.AllowNull;
return contract;
}
}
}
And here is test case which I can't get working:
[DataContract]
private class TestInput
{
[DataMember]
public string Value0 { get; set; }
[DataMember]
public string Value1 { get; set; }
[DataMember]
public Inner Inner { get; set; }
}
[DataContract]
private class Inner
{
[DataMember]
public string NewValue0 { get; set; }
}
[TestMethod]
public void TestSerialization()
{
using (var kernel = new StandardKernel(new MyModule()))
{
var serializer = kernel.Get<ISerializer>();
//It should throw error because Inner not provided as null (not thrown)
string json = "{\"Value0\":\"test0\", \"Value1\":\"test1\"}";
Extensions.Throws<Exception> (()=>serializer.Deserialize(json, typeof(TestInput)));
//It should throw error because nothing were provided as null (not thrown)
json = "{}";
Extensions.Throws<Exception>(() => serializer.Deserialize(json, typeof(TestInput)));
//this one is correct one (no problems here)
json = "{\"Value0\":\"test0\", \"Value1\":\"test1\", \"Inner\":null}";
Extensions.NotThrows(()=>serializer.Deserialize(json, typeof(TestInput)));
}
}
These two tests just won't pass.
After some time I managed to make it work. DataContract applied to my classes were in the way so I just reassigned values they are setting:
private class RequireObjectPropertiesContractResolver : DefaultContractResolver
{
protected override JsonObjectContract CreateObjectContract(Type objectType)
{
var contract = base.CreateObjectContract(objectType);
contract.ItemRequired = Required.AllowNull;
return contract;
}
protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
{
var properties = base.CreateProperties(type, memberSerialization);
foreach (var p in properties)
{
p.Required = Required.AllowNull;
}
return properties;
}
}
Just setted Required.AllowNull on all properties, instead of contract itself.
I am trying to get SignalR to work with custom JsonSerializerSettings for its payload, specifically I'm trying to set TypeNameHandling = TypeNameHandling.Auto.
The problem seems to be, that SignalR uses the settings in hubConnection.JsonSerializer and GlobalHost.DependencyResolver.Resolve<JsonSerializer>() for its internal data structures as well which then causes all kinds of havoc (internal server crashes when I set TypeNameHandling.All as the most crass example, but with TypeNameHandling.Auto I also get problems, particularly when IProgress<> callbacks are involved).
Is there any workaround or am I just doing it wrong?
Sample code to demonstrate:
Server:
class Program
{
static void Main(string[] args)
{
using (WebApp.Start("http://localhost:8080"))
{
Console.ReadLine();
}
}
}
public class Startup
{
public void Configuration(IAppBuilder app)
{
var hubConfig = new HubConfiguration()
{
EnableDetailedErrors = true
};
GlobalHost.DependencyResolver.Register(typeof(JsonSerializer), ConverterSettings.GetSerializer);
app.MapSignalR(hubConfig);
}
}
public interface IFoo
{
string Val { get; set; }
}
public class Foo : IFoo
{
public string Val { get; set; }
}
public class MyHub : Hub
{
public IFoo Send()
{
return new Foo { Val = "Hello World" };
}
}
Client:
class Program
{
static void Main(string[] args)
{
Task.Run(async () => await Start()).Wait();
}
public static async Task Start()
{
var hubConnection = new HubConnection("http://localhost:8080");
hubConnection.JsonSerializer = ConverterSettings.GetSerializer();
var proxy = hubConnection.CreateHubProxy("MyHub");
await hubConnection.Start();
var result = await proxy.Invoke<IFoo>("Send");
Console.WriteLine(result.GetType());
}
Shared:
public static class ConverterSettings
{
public static JsonSerializer GetSerializer()
{
return JsonSerializer.Create(new JsonSerializerSettings()
{
TypeNameHandling = TypeNameHandling.All
});
}
}
This can be done by taking advantage of the fact that your types and the SignalR types are in different assemblies. The idea is to create a JsonConverter that applies to all types from your assemblies. When a type from one of your assemblies is first encountered in the object graph (possibly as the root object), the converter would temporarily set jsonSerializer.TypeNameHandling = TypeNameHandling.Auto, then proceed with the standard serialization for the type, disabling itself for the duration to prevent infinite recursion:
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
}
Then in startup you would add this converter to the default JsonSerializer, passing in the assemblies for which you want "$type" applied.
Update
If for whatever reason it's inconvenient to pass the list of assemblies in at startup, you could enable the converter by objectType.Namespace. All types living in your specified namespaces would automatically get serialized with TypeNameHandling.Auto.
Alternatively, you could introduce an Attribute which targets an assembly, class or interface and enables TypeNameHandling.Auto when combined with the appropriate converter:
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()
{
}
}
Note - tested with various test cases but not SignalR itself since I don't currently have it installed.
TypeNameHandling Caution
When using TypeNameHandling, do take note of this caution from the Newtonsoft docs:
TypeNameHandling should be used with caution when your application deserializes JSON from an external source. Incoming types should be validated with a custom SerializationBinder when deserializing with a value other than None.
For a discussion of why this may be necessary, see TypeNameHandling caution in Newtonsoft Json.
I know that this is a rather old thread and that there is an accepted answer.
However, I had the problem that I could not make the Server read the received json correctly, that is it did only read the base classes
However, the solution to the problem was quite simple:
I added this line before the parameter classes:
[JsonConverter(typeof(PolymorphicAssemblyRootConverter), typeof(ABase))]
public class ABase
{
}
public class ADerived : ABase
{
public AInner[] DifferentObjects { get; set;}
}
public class AInner
{
}
public class AInnerDerived : AInner
{
}
...
public class PolymorphicAssemblyRootConverter: JsonConverter
{
public PolymorphicAssemblyRootConverter(Type classType) :
this(new Assembly[]{classType.Assembly})
{
}
// Here comes the rest of PolymorphicAssemblyRootConverter
}
No need to set JsonSerializer on the proxy connection of the client and add it to the GlobalHost.DependencyResolver.
It took me a long time to figure it out, I am using SignalR 2.2.1 on both client and server.
It's easier that your thought. I came across the same issue, trying to serialize derived classes however no properties from derived types are sent.
As Microsoft says here: https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json-how-to?pivots=dotnet-5-0#serialize-properties-of-derived-classes
If you specify your model of type "Object" instead of the strongly typed "Base type" it will be serialized as such and then properties will be sent.
If you have a big graph of objects you need to to it all the way down. It violates the strongly typed (type safety) but it allows the technology to send the data back with no changes to your code, just to your model.
as an example:
public class NotificationItem
{
public string CreatedAt { get; set; }
}
public class NotificationEventLive : NotificationItem
{
public string Activity { get; set; }
public string ActivityType { get; set;}
public DateTime Date { get; set;}
}
And if your main model that uses this Type is something like:
public class UserModel
{
public string Name { get; set; }
public IEnumerable<object> Notifications { get; set; } // note the "object"
..
}
if you try
var model = new UserModel() { ... }
JsonSerializer.Serialize(model);
you will sent all your properties from your derived types.
The solution is not perfect because you lose the strongly typed model, but if this is a ViewModel being passed to javascript which is in case of SignalR usage it works just fine.
I am programming against a third party API which returns JSON data, but the format can be a little strange. Certain properties can either be an object (which contains an Id property), or a string (which is the Id of the object). For example, both of the following are valid:
{
ChildObject: 'childobjectkey1'
}
and
{
ChildObject: {
Id: 'childobjectkey1',
// (other properties)
}
}
I'm trying to deserialize this using JSON.net into a strongly typed class, but haven't had much luck so far. My best idea was to serialise it to two properties, one a string and the other an object, and to use a custom JsonConverter for each to allow for the variable behaviour:
public abstract class BaseEntity
{
public string Id { get; set; }
}
public class ChildObject : BaseEntity { }
public class MyObject
{
[JsonProperty("ChildObject")]
[JsonConverter(typeof(MyCustomIdConverter))]
public string ChildObjectId { get; set; }
[JsonProperty("ChildObject")]
[JsonConverter(typeof(MyCustomObjectConverter))]
public ChildObject ChildObject { get; set; }
}
However, setting the JsonProperty attribute on two properties with the same PropertyName causes the exception:
Newtonsoft.Json.JsonSerializationException: A member with the name
'ChildObject' already exists on '.....'.
Use the JsonPropertyAttribute to specify another name.
I'm fairly sure the JsonConverter approach will work if I can get over this hurdle - I suspect the error is there because the JsonProperty attribute is used for Serialization as well as Deserialization. In this instance I have no interest in Serializing this class - it will only ever be used as the target for Deserialization.
I have no control over the remote end (it's a third party API), but I would like to be able to achieve this serialisation. I don't mind if it's using the approach I've started on, or one I've not thought of yet.
This question is also related, but there were no answers.
Try this (extend it with some thorough validation if you'll be using it in your code):
public class MyObject
{
public ChildObject MyChildObject;
public string MyChildObjectId;
[JsonProperty("ChildObject")]
public object ChildObject
{
get
{
return MyChildObject;
}
set
{
if (value is JObject)
{
MyChildObject = ((JToken)value).ToObject<ChildObject>();
MyChildObjectId = MyChildObject.Id;
}
else
{
MyChildObjectId = value.ToString();
MyChildObject = null;
}
}
}
}
Rather than creating two separate converters for each of the fields, it would be wise to create a single converter for the "main" property and link the other one to it. ChildObjectId is derived from the ChildObject.
public class MyObject
{
[JsonIgnore]
public string ChildObjectId
{
get { return ChildObject.Id; }
// I would advise against having a setter here
// you should only allow changes through the object only
set { ChildObject.Id = value; }
}
[JsonConverter(typeof(MyObjectChildObjectConverter))]
public ChildObject ChildObject { get; set; }
}
Now to convert the ChildObject can be a bit of a challenge. There are two possible representations of the object: a string or an object. Determine what representation you have and perform the conversion.
public class MyObjectChildObjectConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(ChildObject);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
serializer.Serialize(writer, value);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var obj = serializer.Deserialize<JToken>(reader);
switch (obj.Type)
{
case JTokenType.Object:
return ReadAsObject(obj as JObject);
case JTokenType.String:
return ReadAsString((string)(JValue)obj);
default:
throw new JsonSerializationException("Unexpected token type");
}
}
private object ReadAsObject(JObject obj)
{
return obj.ToObject<ChildObject>();
}
private object ReadAsString(string str)
{
// do a lookup for the actual object or whatever here
return new ChildObject
{
Id = str,
};
}
}
Here is what I would do in this situation.
Only have a single property in your parent class for the child object, and make it of type ChildObject
Create a custom JsonConverter which can inspect the JSON and either:
deserialize the full instance of the child object if the data is present, or
create a new instance of the child object and set its ID, leaving all other properties blank. (Or you could do as Jeff Mercado suggested and have the converter load the object from a database based on the ID, if that applies to your situation.)
Optionally, put a property on the child object indicating whether it is fully populated. The converter can set this property during deserialization.
After deserialization, if there was a ChildObject property in the JSON (with either an ID or a full object value), you are guaranteed to have a ChildObject instance and you can get its ID from it; otherwise, if there was no ChildObject property in the JSON, the ChildObject property in the parent class will be null.
Below is a full working example to demonstrate. In this example, I modified the parent class to include three separate instances of the ChildObject to show the different possibilities in the JSON (string ID only, full object and neither present). They all use the same converter. I also added a Name property and an IsFullyPopulated property to the ChildObject class.
Here are the DTO classes:
public abstract class BaseEntity
{
public string Id { get; set; }
}
public class ChildObject : BaseEntity
{
public string Name { get; set; }
public bool IsFullyPopulated { get; set; }
}
public class MyObject
{
[JsonProperty("ChildObject1")]
[JsonConverter(typeof(MyCustomObjectConverter))]
public ChildObject ChildObject1 { get; set; }
[JsonProperty("ChildObject2")]
[JsonConverter(typeof(MyCustomObjectConverter))]
public ChildObject ChildObject2 { get; set; }
[JsonProperty("ChildObject3")]
[JsonConverter(typeof(MyCustomObjectConverter))]
public ChildObject ChildObject3 { get; set; }
}
Here is the converter:
class MyCustomObjectConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return (objectType == typeof(ChildObject));
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JToken token = JToken.Load(reader);
ChildObject child = null;
if (token.Type == JTokenType.String)
{
child = new ChildObject();
child.Id = token.ToString();
child.IsFullyPopulated = false;
}
else if (token.Type == JTokenType.Object)
{
child = token.ToObject<ChildObject>();
child.IsFullyPopulated = true;
}
else if (token.Type != JTokenType.Null)
{
throw new JsonSerializationException("Unexpected token: " + token.Type);
}
return child;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
serializer.Serialize(writer, value);
}
}
Here is the test program to demonstrate the operation of the converter:
class Program
{
static void Main(string[] args)
{
string json = #"
{
""ChildObject1"":
{
""Id"": ""key1"",
""Name"": ""Foo Bar Baz""
},
""ChildObject2"": ""key2""
}";
MyObject obj = JsonConvert.DeserializeObject<MyObject>(json);
DumpChildObject("ChildObject1", obj.ChildObject1);
DumpChildObject("ChildObject2", obj.ChildObject2);
DumpChildObject("ChildObject3", obj.ChildObject3);
}
static void DumpChildObject(string prop, ChildObject obj)
{
Console.WriteLine(prop);
if (obj != null)
{
Console.WriteLine(" Id: " + obj.Id);
Console.WriteLine(" Name: " + obj.Name);
Console.WriteLine(" IsFullyPopulated: " + obj.IsFullyPopulated);
}
else
{
Console.WriteLine(" (null)");
}
Console.WriteLine();
}
}
And here is the output of the above:
ChildObject1
Id: key1
Name: Foo Bar Baz
IsFullyPopulated: True
ChildObject2
Id: key2
Name:
IsFullyPopulated: False
ChildObject3
(null)