Web API - Detecting null assignment - c#

I'm trying to come up with a pattern that can detect when a property has been set to null. Something similar to the Nullable<T> class, but a little more advanced. Let's call it MoreThanNullable<T>. Basically I need to do something different depending on the following 3 scenarios:
The property was never set
The property was set to null
The property is set to an instance of T.
I created my own class to do this with an "instantiated" property and it all works within a test scenario. Some sample code:
public struct MoreThanNullable<T>
{
private bool hasValue;
internal T value;
public bool Instantiated { get; set; }
public MoreThanNullable(T value)
{
this.value = value;
this.hasValue = true;
Instantiated = true;
}
// ... etc etc...
And the test that passes as I expect it to:
[TestFixture]
public class MoreThanNullableTests
{
public class AccountViewModel
{
public Guid Id { get; set; }
public string Name { get; set; }
public MoreThanNullable<string> Test1 { get; set; }
public MoreThanNullable<string> Test2 { get; set; }
public MoreThanNullable<string> Test3 { get; set; }
}
[Test]
public void Tests()
{
var myClass = new AccountViewModel();
Assert.AreEqual(false, myClass.Test1.Instantiated);
myClass.Test1 = null;
Assert.AreEqual(true, myClass.Test1.Instantiated);
}
}
Next, using this same view model I wire it up to a POST on my REST service and I pass in the following JSON:
{
Name:"test",
Test1:null,
Test2:"test"
}
... and it all falls to pieces. Neither Test1 nor Test3 get instantiated! Obviously, I'm not expecting Test3 to get instantiated, but Test1 remaining uninstantiated was unexpected. Effectively I can't tell the difference between a property I did and didn't receive via REST. (Note: In our application there is a distinct difference between not set and set to null)
Am I doing someting wrong? Or is this correct behaviour for Web API?
** UPDATE **
What I probably didn't make clear is that I have effectively replicated the Nullable<T> class to achieve this, including overloading the implicit operator like this:
public static implicit operator MoreThanNullable<T>(T value)
{
return new MoreThanNullable<T>(value);
}
I always seem to leave out important stuff in my questions...

Why does it not work? In your JSON
{
Name:"test",
Test1:null,
Test2:"test"
}
Test1 end Test2 are primitive types but you created your own type and those are the properties it should map over to so its no longer primitive but a complex object. So really your JSON is expected to also contain objects. It would work if you changed it to
{
Name:"test",
Test1:{Instantiated:false, value: null},
Test2:{Instantiated:false, value: "test"},
}
as now they are objects and deserialized as such. Also your struct has a default empty constructor, if it were a class with a private default constructor it would not work again.
Now, how do you get a primitive type like a string to deserialize over to your custom type? You might be able to do this with a custom web api action filter and parse the incoming json and map it to your object in the OnActionExecuting.
EDIT 2
Assuming you are using JSON.NET from newtonsoft you can plug in a custom JSON converter. This would be a global change across the web api application, no need for custom filters.
public sealed class MoreThanNullableConverter : Newtonsoft.Json.JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType.IsGenericType && objectType.GetGenericTypeDefinition() == typeof (MoreThanNullable<>);
}
public override bool CanWrite { get { return false; } }
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
// return a new MoreThanNullable instance with value
return Activator.CreateInstance(objectType, reader.Value);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
Change to webapiconfig.cs
var formatters = GlobalConfiguration.Configuration.Formatters;
var jsonFormatter = formatters.JsonFormatter;
var settings = jsonFormatter.SerializerSettings;
jsonFormatter.SerializerSettings.Converters.Add(new MoreThanNullableConverter());

i think you have fallen in a common problem with deserialization on services in .net
I've always seen this problem with WCF, but i think it's also present in json deserialization
https://msdn.microsoft.com/en-us/library/system.runtime.serialization.ondeserializedattribute(v=vs.110).aspx
.NET during deserialization doesn't execute constructor, so if you need to execute some code after deserialization you have to add some code as these
[OnDeserialized()]
internal void OnDeserializedMethod(StreamingContext context)
{
// ... logic here after deserialization
}
In that logic you should complete private field with missing information.
hasValue field must setted depending if value is null or not.
Instantiated should have a private field with setter private. It should setted on true on public setter of value.
If hasValue is true, instantiated is obviously true, how can be otherwise?
So the problem is, how can we have hasvalue false but instatiated true?
This is the only case when you cannot determine the value with other values, so we need somthing else. Making the setter of instantiated public is the simplest way but for ensure object consistence you should check it after deserialization to ensure previous object rules...

Related

Why does Asp WebApi HttpGet lose derived class properties? What's up with polymorphism? [duplicate]

The JSON response from my ASP.NET Core 3.1 API controller is missing properties. This happens when a property uses a derived type; any properties defined in the derived type but not in the base/interface will not be serialized to JSON. It seems there is some lack of support for polymorphism in the response, as if serialization is based on a property's defined type instead of its runtime type. How can I change this behavior to ensure that all public properties are included in the JSON response?
Example:
My .NET Core Web API Controller returns this object that has a property with an interface type.
// controller returns this object
public class Result
{
public IResultProperty ResultProperty { get; set; } // property uses an interface type
}
public interface IResultProperty
{ }
Here is a derived type that defines a new public property named Value.
public class StringResultProperty : IResultProperty
{
public string Value { get; set; }
}
If I return the derived type from my controller like this:
return new MainResult {
ResultProperty = new StringResultProperty { Value = "Hi there!" }
};
then the actual response includes an empty object (the Value property is missing):
I want the response to be:
{
"ResultProperty": { "Value": "Hi there!" }
}
While the other answers are good and solves the problem, if all you want is the general behavior to be like pre netcore3, you can use the Microsoft.AspNetCore.Mvc.NewtonsoftJson NuGet package and in Startup.cs do:
services.AddControllers().AddNewtonsoftJson()
More info here. This way, you don't need to create any extra json-converters.
I ended up creating a custom JsonConverter (System.Text.Json.Serialization namespace) which forces JsonSerializer to serialize to the object's runtime type. See the Solution section below. It's lengthy but it works well and does not require me to sacrifice object oriented principles in my API's design. (If you need something quicker and can use Newtonsoft then check out the top voted answer instead.)
Some background: Microsoft has a System.Text.Json serialization guide with a section titled Serialize properties of derived classes with good information relevant to my question. In particular it explains why properties of derived types are not serialized:
This behavior is intended to help prevent accidental exposure of data
in a derived runtime-created type.
If that is not a concern for you then the behavior can be overridden in the call to JsonSerializer.Serialize by either explicitly specifying the derived type or by specifying object, for example:
// by specifying the derived type
jsonString = JsonSerializer.Serialize(objToSerialize, objToSerialize.GetType(), serializeOptions);
// or specifying 'object' works too
jsonString = JsonSerializer.Serialize<object>(objToSerialize, serializeOptions);
To accomplish this with ASP.NET Core you need to hook into the serialization process. I did this with a custom JsonConverter that calls JsonSerializer.Serialize one of the ways shown above. I also implemented support for deserialization which, while not explicitly asked for in the original question, is almost always needed anyway. (Oddly, supporting only serialization and not deserialization proved to be tricky anyway.)
Solution
I created a base class, DerivedTypeJsonConverter, which contains all of the serialization & deserialization logic. For each of your base types, you would create a corresponding converter class for it that derives from DerivedTypeJsonConverter. This is explained in the numbered directions below.
This solution follows the "type name handling" convention from Json.NET which introduces support for polymorphism to JSON. It works by including an additional $type property in the derived type's JSON (ex: "$type":"StringResultProperty") that tells the converter what the object's true type is. (One difference: in Json.NET, $type's value is a fully qualified type + assembly name, whereas my $type is a custom string which helps future-proof against namespace/assembly/class name changes.) API callers are expected to include $type properties in their JSON requests for derived types. The serialization logic solves my original problem by ensuring that all of the object's public properties are serialized, and for consistency the $type property is also serialized.
Directions:
1) Copy the DerivedTypeJsonConverter class below into your project.
using System;
using System.Collections.Generic;
using System.Dynamic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
public abstract class DerivedTypeJsonConverter<TBase> : JsonConverter<TBase>
{
protected abstract string TypeToName(Type type);
protected abstract Type NameToType(string typeName);
private const string TypePropertyName = "$type";
public override bool CanConvert(Type objectType)
{
return typeof(TBase) == objectType;
}
public override TBase Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
// get the $type value by parsing the JSON string into a JsonDocument
JsonDocument jsonDocument = JsonDocument.ParseValue(ref reader);
jsonDocument.RootElement.TryGetProperty(TypePropertyName, out JsonElement typeNameElement);
string typeName = (typeNameElement.ValueKind == JsonValueKind.String) ? typeNameElement.GetString() : null;
if (string.IsNullOrWhiteSpace(typeName)) throw new InvalidOperationException($"Missing or invalid value for {TypePropertyName} (base type {typeof(TBase).FullName}).");
// get the JSON text that was read by the JsonDocument
string json;
using (var stream = new MemoryStream())
using (var writer = new Utf8JsonWriter(stream, new JsonWriterOptions { Encoder = options.Encoder })) {
jsonDocument.WriteTo(writer);
writer.Flush();
json = Encoding.UTF8.GetString(stream.ToArray());
}
// deserialize the JSON to the type specified by $type
try {
return (TBase)JsonSerializer.Deserialize(json, NameToType(typeName), options);
}
catch (Exception ex) {
throw new InvalidOperationException("Invalid JSON in request.", ex);
}
}
public override void Write(Utf8JsonWriter writer, TBase value, JsonSerializerOptions options)
{
// create an ExpandoObject from the value to serialize so we can dynamically add a $type property to it
ExpandoObject expando = ToExpandoObject(value);
expando.TryAdd(TypePropertyName, TypeToName(value.GetType()));
// serialize the expando
JsonSerializer.Serialize(writer, expando, options);
}
private static ExpandoObject ToExpandoObject(object obj)
{
var expando = new ExpandoObject();
if (obj != null) {
// copy all public properties
foreach (PropertyInfo property in obj.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance).Where(p => p.CanRead)) {
expando.TryAdd(property.Name, property.GetValue(obj));
}
}
return expando;
}
}
2) For each of your base types, create a class that derives from DerivedTypeJsonConverter. Implement the 2 abstract methods which are for mapping $type strings to actual types. Here is an example for my IResultProperty interface that you can follow.
public class ResultPropertyJsonConverter : DerivedTypeJsonConverter<IResultProperty>
{
protected override Type NameToType(string typeName)
{
return typeName switch
{
// map string values to types
nameof(StringResultProperty) => typeof(StringResultProperty)
// TODO: Create a case for each derived type
};
}
protected override string TypeToName(Type type)
{
// map types to string values
if (type == typeof(StringResultProperty)) return nameof(StringResultProperty);
// TODO: Create a condition for each derived type
}
}
3) Register the converters in Startup.cs.
services.AddControllers()
.AddJsonOptions(options => {
options.JsonSerializerOptions.Converters.Add(new ResultPropertyJsonConverter());
// TODO: Add each converter
});
4) In requests to the API, objects of derived types will need to include a $type property. Example JSON: { "Value":"Hi!", "$type":"StringResultProperty" }
Full gist here
The documentation shows how to serialize as the derived class when calling the serializer directly. The same technique can also be used in a custom converter that we then can tag our classes with.
First, create a custom converter
public class AsRuntimeTypeConverter<T> : JsonConverter<T>
{
public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
return JsonSerializer.Deserialize<T>(ref reader, options);
}
public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
{
JsonSerializer.Serialize(writer, value, value?.GetType() ?? typeof(object), options);
}
}
Then mark the relevant classes to be used with the new converter
[JsonConverter(typeof(AsRuntimeTypeConverter<MyBaseClass>))]
public class MyBaseClass
{
...
Alternately, the converter can be registered in startup.cs instead
services
.AddControllers(options =>
.AddJsonOptions(options =>
{
options.JsonSerializerOptions.Converters.Add(new AsRuntimeTypeConverter<MyBaseClass>());
}));
I had a similar issue, where I was returning an enumerable of type TAnimal (but the object instances were of derived types such as Dog, Cat, etc.):
[HttpGet]
public IEnumerable<TAnimal> GetAnimals()
{
IEnumerable<TAnimal> list = GetListOfAnimals();
return list;
}
This only included properties defined in TAnimal.
However, in ASP .NET Core 3.1 at least, I found that I could just cast the object instances to object, and the JSON serializer then included all the properties from the derived classes:
[HttpGet]
public IEnumerable<object> GetAnimals()
{
IEnumerable<TAnimal> list = GetListOfAnimals();
return list.Select(a => (object)a);
}
(Note that the signature of the GetAnimals method must also changed, but that doesn't usually matter much in a web API context). If you need to provide type information for Swagger or whatever, you can annotate the method:
[HttpGet]
[Produces(MediaTypeNames.Application.Json, Type = typeof(TAnimal[]))]
public IEnumerable<object> GetAnimals()
{
...
}
Casting to object is a simple solution if you only have a 1-layer-deep object hierarchy to worry about.
This is the expected result. You're upcasting when you do that, so what will be serialized is the upcasted object, not the actual derived type. If you need stuff from the derived type, then that has to be the type of the property. You may want to use generics for this reason. In other words:
public class Result<TResultProperty>
where TResultProperty : IResultProperty
{
public TResultProperty ResultProperty { get; set; } // property uses an interface type
}
Then:
return new Result<StringResultProperty> {
ResultProperty = new StringResultProperty { Value = "Hi there!" }
};
I solved it by writing this extension:
public static class JsonSerializationExtensions
{
public static string ToJson<T>(this IEnumerable<T> enumerable, bool includeDerivedTypesProperties = true)
where T : class
{
var jsonOptions = new JsonSerializerOptions()
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
};
if (includeDerivedTypeProperties)
{
var collection = enumerable.Select(e => e as object).ToList();
return JsonSerializer.Serialize<object>(collection, jsonOptions);
}
else
{
return JsonSerializer.Serialize(enumerable, jsonOptions);
}
}
}
I was also struggling with this in a .NET Core 3.1 API, where I wanted the result to include $type attribute.
As suggested, install the correct package and then 'AddNewtonsoftJson'.
I wanted the $type field to be added to show the derived type handling, to get that
services.AddControllers().AddNewtonsoftJson(options =>
{
options.SerializerSettings.TypeNameHandling = Newtonsoft.Json.TypeNameHandling.All;
});
Not knocking Newtonsoft, but I found an easier way to resolve this with the built handlers.
[OperationContract]
[WebInvoke(Method = "GET", UriTemplate = "/emps", BodyStyle = WebMessageBodyStyle.Wrapped, RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json)]
List<emp> GetEmps();
//[DataContract(Namespace = "foo")] <<< comment/removed this line
public class emp
{
public string userId { get; set; }
public string firstName { get; set; }
}
public class dept
{
public string deptId{ get; set; }
public string deptName{ get; set; }
}
In my case dept objects where working fine, but emp ones were not - they came across as empty.

context.GetInput<model> returns base class instead of specified derived class in $Type

Using Azure Durable Functions, I am trying to use the context.GetInput<model>() function which returns the specified model. The model being used has a parameter that is another model which is a derived class. The model that is outputted from context.GetInput<model>() returns the model with the base class instead of the derived class.
I have checked the $type specified in the context, which shows the derived class, but when checking the outputted model, the result is the base class.
for example:
public class Student{
public Book book {get;set;}
}
public class Textbook:Book {
public string classfor {get;set;}
}
public class Book {
public string title {get;set;}
}
[ActivityTrigger] DurableActivityContextBase context is a parameter to the function.
Then I would be calling :
var model = context.GetInput<Student>()
where the context includes
{
"$type": "Student",
"book" : {
"$type": "Textbook",
"classfor" : "Math",
"title" : "PreAlgebra"
}
}
Yet the result is
Model of student which contains a Book instead of Textbook, where the title is assigned "PreAlgebra"
I expect the output of Student model to have a Textbook with properties:
title = "PreAlgebra"
classfor = "Math"
but the actual Student output contains a Book with the property
title = "PreAlgebra"
I've encountered the same problem you did last week. Unfortunately right now Azure Functions (even 2.x) don't support polymorphism for durable functions. The durable context serializes your object to JSON, but there's no way to pass JSON serialization settings as described here on GitHub. There's also another issue about this specific problem.
In my case I have an abstract base class, but you can use the same approach for your derived types. You can create a custom JSON converter that will deal with picking the correct type during deserialization. So for example if you have this sort of inheritance:
[JsonConverter(typeof(DerivedTypeConverter))]
public abstract class Base
{
[JsonProperty("$type")]
public abstract string Type { get; }
}
public class Child : Base
{
public override string Type => nameof(Child);
}
public class Child2 : Base
{
public override string Type => nameof(Child2);
}
Then you can have your а JSON Converter:
public class BaseDerivedTypeConverter : DefaultContractResolver
{
// You need this to protect yourself against circular dependencies
protected override JsonConverter ResolveContractConverter(Type objectType)
{
return typeof(Base).IsAssignableFrom(objectType) && !objectType.IsAbstract
? null
: base.ResolveContractConverter(objectType);
}
}
public class DerivedTypeConverter : JsonConverter
{
private static readonly JsonSerializerSettings Settings =
new JsonSerializerSettings()
{
ContractResolver = new BaseDerivedTypeConverter()
};
public override bool CanConvert(Type objectType) => (objectType == typeof(Base));
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JObject jsonObject = JObject.Load(reader);
// Make checks if jsonObject["$type"].Value<string>() has a supported type
// You can have a static dictionary or a const array of supported types
// You can leverage the array or dictionary to get the type you want again
var type = Type.GetType("Full namespace to the type you want", false); // the false flag means that the method call won't throw an exception on error
if (type != null)
{
return JsonConvert.DeserializeObject(jsonObject.ToString(), type, Settings);
}
else
{
throw new ValidationException("No valid $type has been specified!");
}
}
public override bool CanWrite => false;
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) => throw new NotImplementedException();
}
In my usage when I call context.GetInput<Base>() I can get either Child or Child1 because Base is abstract.
In your case it can be Book or Student depending on what's the actual value. This also applies for other durable function operations like like
var foobar = await context.CallActivityAsync<Base>("FuncName", context.GetInput<int>());
The converter will deal with that and you'll get the object you want inside foobar.
Per my understanding, the class Textbook extends Book, so "Book" is parent class and "Textbook" is subclass. In your context, you want to turn the child class(Textbook) to the parent class(Book). After that, "book" will just have the attribute "title" which is their common attribute but doesn't have the specific attribute "classfor". You can refer to the code below:
Tracked the updates to pass in Json serialization to Azure Functions here showing that it will be in v2.1!

Include Class name as part of the serialization in JSON C#

I have a specific situation where I need to include the class name as property in JSON when classes are serialized. The tricky parts is I need to do this dynamically. I can't just create an anonymous class before calling serialization.
I have decorated my class with a custom attribute as below:
[OntologyArea("con", " http://someurl/area/DomicileAddress")]
public class DomicileAddress : IContactPoint
{
[JsonProperty(PropertyName = "con:hasAddressPoint")]
public IAddressPoint AddressPoint
{
get; set;
}
}
In the above example the OntologyArea attribute should be read and included as a Property. The propery name should be the first argument of OntologyArea + the class name (i.e con:DomicileAddress) and the value should be the concrete class of IAddressPoint.
The tricky part is that the concrete class of IAddressPoint might need to do the same as shown here:
[OntologyArea("geo", "http://someurl.net/geolocation")]
public class StreetAddress : IAddressPoint
{
[JsonProperty("geo:hasStartingStreetNumber")]
public string StartingStreetNumber
{
get; set;
}
}
an example of JSON:
"con:DomicileAddress" : {
"con:hasAddressPoint" : {
"geo:StreetAddress" : {
"geo:hasEndingStreetNumber" : ""
}
}
}
So if any object does have a OntologyArea attribute I need to add a parent level. If it does not contain this attribute normal serilization should continue.
Please let me know If I need to explain more.
Is this solution for specifying the concrete type is a must or is it just your proposed solution?
Because Json.NET has a built-in support for encoding the actual types for properties of interface types or base classes:
var json = JsonConvert.SerializeObject(myContract, new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.Auto });
If you really need a completely custom logic you have to implement a converter, which also can be passed to the JsonSerializerSettings. It must be derived from JsonConverter and you have to implement the WriteJson method to emit your desired json sting using low level tokens just like in case of an XmlWriter:
private class MyConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return typeof(IAddressPoint).IsAssignableFrom(objectType);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var address = (IAddressPoint)value;
writer.WriteStartObject(); // {
writer.WritePropertyName($"geo:{address.GetType().Name}"); // "geo:StreetAddress"
// ... etc.
writer.WriteEndObject(); // }
// or you can just emit raw string:
writer.WriteRaw(myJsonBody);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue,
{
// todo: and the deserialization part goes here
}
}
This pointed me in the correct direction. I have an attribute that is read in this class and appended to the writer object.

JSON.Net preserve reference to static object

I am using a variation of the type object pattern (basically smart enums). Since this problem can best be explained with code I will jump right into it.
class Program
{
static void Main(string[] args)
{
Test C = Test.B;
Console.WriteLine(C == Test.B); //Returns true
string Json = JsonConvert.SerializeObject(C);
C = JsonConvert.DeserializeObject<Test>(Json);
Console.WriteLine(C == Test.B); //Returns false
}
}
public class Test
{
public int A { get; set; }
public Test(int A)
{
this.A = A;
}
public static Test B = new Test(100);
}
In this example Test is the type object, and instances of it are assigned to it's static field, B. In real life scenarios there would be multiple of these static fields, each representing a different type. When I serialize and deserialize, the test object is serialized purely as data. I understand why this is happening, but I don't know what to do about it. I would like to somehow preserve instances of Test being references to a static member in that class.
What you are looking for is support for the IObjectReference interface:
Implement this interface on objects that are references to a different object, which cannot be resolved until the current object is completely restored. During the fixup stage, any object implementing IObjectReference is queried for its real object and that object is inserted into the graph.
Unfortunately, Json.NET does not support this interface. However, it turns out to be quite easy to extend Json.NET to support this interface in cases where the type in question also implements ISerializable. This is a quite reasonable restriction given that, in practice, these two interfaces are often used together, as is shown in the documentation example.
First, introduce the following custom contract resolver:
public class ISerializableRealObjectContractResolver : DefaultContractResolver
{
// 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 ISerializableRealObjectContractResolver instance;
static ISerializableRealObjectContractResolver() { instance = new ISerializableRealObjectContractResolver(); }
public static ISerializableRealObjectContractResolver Instance { get { return instance; } }
public ISerializableRealObjectContractResolver()
: base()
{
this.IgnoreSerializableInterface = false;
}
protected override JsonISerializableContract CreateISerializableContract(Type objectType)
{
var contract = base.CreateISerializableContract(objectType);
var constructor = contract.ISerializableCreator;
contract.ISerializableCreator = args =>
{
var obj = constructor(args);
if (obj is IObjectReference)
{
var context = (StreamingContext)args[1];
obj = ((IObjectReference)obj).GetRealObject(context);
}
return obj;
};
return contract;
}
}
Now, modify your psuedo-enum Test type to implement ISerializable and IObjectReference:
public class Test : ISerializable, IObjectReference
{
readonly int a;
public int A { get { return a; } }
public Test(int A)
{
this.a = A;
}
public static readonly Test B = new Test(100);
#region ISerializable Members
protected Test(SerializationInfo info, StreamingContext context)
{
a = info.GetInt32("A");
}
public void GetObjectData(SerializationInfo info, StreamingContext context)
{
info.AddValue("A", A);
}
#endregion
#region IObjectReference Members
public object GetRealObject(StreamingContext context)
{
// Check all static properties to see whether the key value "A" matches. If so, return the static instance.
if (this.A == B.A)
return B;
return this;
}
#endregion
}
I also made the type immutable since that is clearly the requirement here.
Now your unit test will pass when using this contract resolver:
Test C = Test.B;
Console.WriteLine(C == Test.B); //Returns true
string Json = JsonConvert.SerializeObject(C, new JsonSerializerSettings { ContractResolver = ISerializableRealObjectContractResolver.Instance });
Console.WriteLine(Json);
C = JsonConvert.DeserializeObject<Test>(Json, new JsonSerializerSettings { ContractResolver = ISerializableRealObjectContractResolver.Instance });
Console.WriteLine(C == Test.B); //Still returns true
if (!object.ReferenceEquals(C, Test.B))
{
throw new InvalidOperationException("!object.ReferenceEquals(C, Test.B)");
}
else
{
Console.WriteLine("Global singleton instance deserialized successfully.");
}
Note however that Json.NET only supports the ISerializable interface in full trust.
Not possible by default, since the JSON deserializer doesn't care about existing references or static objects in your class.
You could compare for equality using a custom Equals method, but I guess that isn't what you want.
Don't serialize MyObj.Test, suppress that with an Ignore attribute. Instead, expose a property MyObj.TestID that returns MyObj.Test.ID. When TestID is set on MyObj, load the Test from a static collection keyed by the ID and set MyObj.Test to that value.
First of all, Type Object patterns are supposed to be used when you don't want to go through the inheritance hierarchy every time you define a new derivative of a base class. Having a type object attached as static didn't make sens to be at the first place to be honest. As you mentioned it's a variation Im not going to jump on that.
Looks like you want to be able to keep the reference even after deserialization using json.net.
Now if you want to do that you might want to have a look here.
Taking snippets from the aforementioned link as it's better to have a sample here as this is a StackOverflow answer. It should sustain even the provided link is dead.
Your first option is to use default PreserveReferencesHandling. The associated sample is following where you can reference same objects in a list and point to it. I don't think it actually keeps the old reference but sure helps when you have same things in a list and you dont want to go with your own IEqualityComparer or IEquatable implementations:
string json = JsonConvert.SerializeObject(people, Formatting.Indented,
new JsonSerializerSettings { PreserveReferencesHandling = PreserveReferencesHandling.Objects });
//[
// {
// "$id": "1",
// "Name": "James",
// "BirthDate": "1983-03-08T00:00Z",
// "LastModified": "2012-03-21T05:40Z"
// },
// {
// "$ref": "1"
// }
//]
List<Person> deserializedPeople = JsonConvert.DeserializeObject<List<Person>>(json,
new JsonSerializerSettings { PreserveReferencesHandling = PreserveReferencesHandling.Objects });
Console.WriteLine(deserializedPeople.Count);
// 2
Person p1 = deserializedPeople[0];
Person p2 = deserializedPeople[1];
Console.WriteLine(p1.Name);
// James
Console.WriteLine(p2.Name);
// James
bool equal = Object.ReferenceEquals(p1, p2);
// true
You can use IsReference attribute to control which properties would be kept as references:
[JsonObject(IsReference = true)]
public class EmployeeReference
{
public string Name { get; set; }
public EmployeeReference Manager { get; set; }
}
Now if you want to keep the exact same reference in the code for yourself (I don't think this is really a good design anyway, you might just need a Equality comparison method and be done with it), you need a custom IReferenceResolver defined here.
Furthermore, if you want to have something like that, look no further than Json.net's source code here.
It's an IdReferenceResolver that you can possibly use to preserve your object reference as Guid and possibly use it your way.
And if you want to know how DefaultReferenceResolver works you can have a look at this stackoverflow thread.

Json.net deserialization null guid case

I'm deserializing an object using Json.NET that contains a private field of type Guid and a public property for that field. When the value for my Guid is null in my json I want to assign Guid.Empty to my field.
public class MyClass
{
private Guid property;
public Guid Property
{
get { return property; }
set
{
if (value == null)
{
property = Guid.Empty;
}
else
{
property = value;
}
}
}
}
But the deserializer wants to access the private field, cause I get this error when I try to deserialize:
Error converting value {null} to type 'System.Guid'. Path
'[0].property', line 6, position 26.
How can I make it ignore the private field and use the public property instead?
Json.NET refuses to set a null value for a Guid because it is a non-nullable value type. Try typing (Guid)null in the Immediate Window and you will see an error message indicating that this conversion cannot be made in .Net.
To work around this, you have a couple of options:
Create a Guid? nullable proxy property. It can be private if you desire as long as it has a [JsonProperty] attribute:
public class MyClass
{
[JsonIgnore]
public Guid Property { get; set; }
[JsonProperty("Property")]
Guid? NullableProperty { get { return Property == Guid.Empty ? null : (Guid?)Property; } set { Property = (value == null ? Guid.Empty : value.Value); } }
}
Create a JsonConverter that converts a null Json token to a default Guid value:
public class NullToDefaultConverter<T> : JsonConverter where T : struct
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(T);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var token = JToken.Load(reader);
if (token == null || token.Type == JTokenType.Null)
return default(T);
return token.ToObject(objectType); // Deserialize using default serializer
}
// Return false instead if you don't want default values to be written as null
public override bool CanWrite { get { return true; } }
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
if (EqualityComparer<T>.Default.Equals((T)value, default(T)))
writer.WriteNull();
else
writer.WriteValue(value);
}
}
Then apply it to your type as follows:
public class MyClass
{
[JsonConverter(typeof(NullToDefaultConverter<Guid>))]
public Guid Property { get; set; }
}
Alternatively, you can apply the converter to all values of type T by adding the converter to JsonSerializerSettings.Converters. And, to register such a converter globally, see e.g.How to set custom JsonSerializerSettings for Json.NET in MVC 4 Web API? for Web API, Setting JsonConvert.DefaultSettings asp net core 2.0 not working as expected for ASP.NET Core or Registering a custom JsonConverter globally in Json.Net for a console app.
If you do register the converter globally for a console app, you may need to disable it for recursive calls as shown in JSON.Net throws StackOverflowException when using [JsonConvert()].
If you only need to deserialize a null value for a Guid and not re-serialize it as such, you can apply [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] to the Guid property, and null values will ignored despite being invalid Guid values:
public class MyClass
{
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public Guid Property { get; set; }
}
Of course if you do this your Guid will be re-serialized as "00000000-0000-0000-0000-000000000000". To ameliorate that you could apply DefaultValueHandling = DefaultValueHandling.Ignore which will cause empty Guid values to be omitted during serialization:
[JsonProperty(NullValueHandling = NullValueHandling.Ignore, DefaultValueHandling = DefaultValueHandling.Ignore)]
public Guid Property { get; set; }
Note that if a parameterized constructor called during deserialization has a non-nullable Guid argument with the same name, a different approach may be required.

Categories