This library is under development!
How about you didn't have to worry about random nulls in your JSON contracts?
Let's say you've created an API for selling concert tickets. You're using Json.NET for serialization/deserialization and one of your contracts looks like this:
public class BuyTicketRequest
{
public string EventId { get; set; }
public string FullName { get; set; }
public string DiscountCode { get; set; }
}
Not all fields are required by your contract, though. Let's say that FullName
and DiscountCode
are not necessary to buy a ticket. This is hardly visible โ in the end, every string
can be null.
There are a few ways to deal with it, including naming conventions and fancy validators. Personally, I prefer to take advantage of type systems, and more precisely of option/maybe types.
In this example, let's make our contract more explicit by using SimpleMaybe โ a library implementing option/maybe type:
PM> Install-Package SimpleMaybe -IncludePrerelease
Our refined contract looks like this:
using SimpleMaybe;
public class BuyTicketRequest
{
public string EventId { get; set; }
public Maybe<string> FullName { get; set; }
public Maybe<string> DiscountCode { get; set; }
}
It's definitely more clear, but it won't work by default with Json.NET.
Also, I would like for two things to happen during deserialization:
- for properties of
Maybe<T>
type, nulls should map toMaybe.None<T>()
and values toMaybe.Some(value)
, - every other property should never deserialize into null.
We can achieve this by installing SimpleMaybe.JsonNetSerialization
- a library integrating Json.NET with SimpleMaybe:
PM> Install-Package SimpleMaybe.JsonNetSerialization -IncludePrerelease
Json.NET configuration should look like this:
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using SimpleMaybe.JsonNetSerialization;
// ...
var settings = new JsonSerializerSettings
{
ContractResolver = new RequireMaybePropertiesContractResolver(
new DefaultContractResolver()
),
Converters = new JsonConverter[]
{
new MaybeJsonConverter()
}
};
Let's test how these maybes map!
Given following JSON:
{
"EventId": "modal-nodes-05-1977",
"FullName": "Han Solo",
"DiscountCode": null
}
Deserialization should have following effect:
var request = JsonConvert.DeserializeObject<BuyTicketRequest>(json, settings);
request.EventId.Should().Be("modal-nodes-05-1977");
request.FullName.Should().Be(Maybe.Some("Han Solo"));
request.DiscountCode.Should().Be(Maybe.None<string>());
Great! But what if we try to smuggle a null through some non-maybe property?
{
"EventId": null,
"FullName": "Han Solo",
"DiscountCode": null
}
If we try to do that, we'll get the following exception:
Required property 'EventId' expects a value but got null. (...)
One more thing - the same principles will apply to serialization. It means, that the only way to get null in your JSON, will be by using Maybe.None<T>()
.
Happy coding with fewer nulls!
Make sure to read the library's current limitations!
SimpleMaybe was created to fight nulls and make handling optional values more explicit. And since JSON contracts are the major source of unexpected nulls, why not create a tool to handle them more explicitly?
By using SimpleMaybe along with Json.NET:
- you make it very explicit which properties are required, and which are optional,
- you make sure that required properties are actually set and not null.
Also, you don't have to check nulls everywhere in your code.
This library consists of two classes: MaybeJsonConverter
and RequireMaybePropertiesContractResolver
. They were designed to work together, but can be used separately if necessary.
MaybeJsonConverter
enables Json.NET to serialize and deserialize Maybe<T>
objects.
When deserializing:
- null is deserialized into
Maybe.None<T>
, - non-null value is deserialized into
Maybe.Some<T>
.
When serializing:
Maybe.None<T>
is serialized into null,Maybe.Some<T>
is serialized into contained value.
RequireMaybePropertiesContractResolver
will make sure, that only properties of Maybe<T>
type can be null or absent; if any other property is null, serialization/deserialization will throw an exception.
Of course, using it will only make sense in combination with MaybeJsonConverter
.
Use it by assigning RequireMaybePropertiesContractResolver
instance to JsonSerializerSettings.ContractResolver
:
var serializerSettings = new JsonSerializerSettings
{
ContractResolver = new RequireMaybePropertiesContractResolver(
new DefaultContractResolver()
)
};
Since RequireMaybePropertiesContractResolver
is a decorator for IContractResolver
, you can compose it with any other contract resolver:
new RequireMaybePropertiesContractResolver(
new DefaultContractResolver()
);
new RequireMaybePropertiesContractResolver(
new CamelCasePropertyNamesContractResolver()
);
new RequireMaybePropertiesContractResolver(
new YourFancyContractResolver()
);
At the moment, RequireMaybePropertiesContractResolver
will only work with objects' properties. It means, that other containers such as arrays or dictionaries will still be able to contain nulls! Be sure to check them by hand if necessary.
The reason for this is that I have no idea how to implement it in Json.NET. If you know how, let me know!