I'm trying to overcome a problem with command handling, there are easier ways using flags and enums, but if I can make a magical class that stops coupling I want it.
Basically I have a simple class:
public class ServiceProperty<T>
{
public T Value { get; set; }
}
And all I want is to map the data simply. The reason behind this class is that we want the "update" command to be flexible (e.g. the client can update one attribute without being forced to send everything, risking reverting a change to another field that may have occurred during the process due to timing).
The problem is that null is acceptable for some fields, so we can't rely on nullable, we need a class that has the value (which can be null) and if the property is not included at all we then know they don't wish to update it.
the problem is the resulting JSON required (in this example we are updating the string "description"):
{
"description": { "value": "lol" }
}
I wish to change the required JSON to the following:
{
"description": "lol"
}
and use converters to go between. I've built the converter:
public class ServicePropertyConverter<T> : JsonConverter where T : class
{
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var str = reader.Value.ToString();
return new ServiceProperty<T> { Value = string.IsNullOrEmpty(str) ? null : (T)JsonConvert.DeserializeObject(reader.Value.ToString(), typeof(T)) };
}
... (other methods here)...
}
So that should work fine, however swagger still asks for the first JSON example.
I've tried the following:
services.AddMvc(o =>
{
...(Unimportant code here)...
}).AddJsonOptions(x =>
{
x.SerializerSettings.ContractResolver = new ServicePropertyContractResolver();
x.SerializerSettings.Converters.Add(new ServicePropertyConverter<string>());
});
The string implementation may seem silly, but it's just a first step to get the description example working, then I'll work on everything else, but it didn't work. As you can see I also created a custom 'ContractResolver':
protected override JsonConverter ResolveContractConverter(Type objectType)
{
if (objectType == null || !objectType.IsAssignableFrom(typeof(ServiceProperty<>)))
{
return base.ResolveContractConverter(objectType);
}
return new ServicePropertyConverter<string>();
}
But this didn't make any difference either. At this point I'm a little desperate, I don't want to waste any more time, so if there isn't a simple solution I'll go back to the boilerplate solution and give up on this more elegant approach.
Any help is greatly appreciated - I am new to ASP.NET, so I'm learning as I go.
What you want to do is to create a single converter
ServicePropertyConverter
that works for allServiceProperty<T>
for allT
. This is fairly simple to do with the following modifications to your type:Notice that:
There is an abstract base type that returns the value as an
object
. The presence of this base type makesWriteJson()
simpler.The generic type now has both parameterized and parameterless constructors. The presence of the parameterized constructor makes
ReadJson()
simpler.Then, create the following converter:
And, serialize and deserialize with the following settings:
Note the use of
DefaultValueHandling.IgnoreAndPopulate
. Using this setting ensures that a property with a null value for aServiceProperty<T>
is completely skipped when serializing, but populated during deserialization when present. (IfNullValueHandling.Ignore
were used instead,ServicePropertyConverter.ReadJson()
would not get not called when a null value is encountered, breaking your design.)If you cannot set
DefaultValueHandling
in settings, you could set it via a custom contract resolver:And with settings:
You may want to cache the contract resolver for best performance.
Sample fiddle.