vkhorikov/CSharpFunctionalExtensions

Json Serlization of Maybe

Closed this issue · 7 comments

How do I Json serialize a class with Maybe property?

Repro: Update the current unit test. CSharpFunctionalExtensions\CSharpFunctionalExtensions.Tests\MaybeTests

    [Fact]
    public void Deserialize_sets_no_value_on_maybe_none()
    {
        Maybe<MyClass> maybe = Maybe<MyClass>.None;
        JsonSerializer.Serialize(maybe);
      
    }

It throws.
System.InvalidOperationException : Maybe has no value.

Thanks.

Despite the provided issue relates to another Maybe, I completely agree with the added justification.

There are some types that you would not expect to be serialized and deserialized. Action, StringBuilder, SqlConnection, HomeController, CancellationToken, Task<T>, ReadOnlySpan<T> are just a few of these types. Moreover, these types in 99% of cases should also not be used as field and property types, they should act as local variables and as method parameters and return types.

The same applies to results/maybes/valueobjects. They should be used within your system, but they are not expected to cross system boundaries

So the solution here is simple

Maybe<MyClass> maybeClass = Maybe<MyClass>.None;
_ = JsonSerializer.Serialize(maybeClass.GetValueOrDefault());

// GetValueOrDefault returns default(T).
// It is null for reference types (which is totally fine),
// but for value types you need something a bit more sophisticated.

// Let's just convert Maybe<TValueType> to Nullable<TValueType>
// which is integrated to .NET type system and is well-known for serializers.

Maybe<MyStruct> maybeStruct = Maybe<MyStruct>.None;
_ = JsonSerializer.Serialize(maybeStruct.Match(value => value, () => default(MyStruct?))); // with inferred type
_ = JsonSerializer.Serialize(maybeStruct.Match<MyStruct?, MyStruct>(value => value, () => null)); // with explicetely provided type

Agree with @hankovich and with the commenter on the linked post. The general guideline here is this: use DTOs for intra-application communication. Maybe and other types with "logic" are for inter-application use only.

I was using Maybe to express optional properties of the domain. For now, I went with C# null feature. example
EMail? AdditionalEmail;

I don't know if the course covered a prescribed way of exposing optional properties of the domain.

Off-topic: There is inefficient coverage on the usage of the Enum value object type. Hopefully, your new course will use it. It serialised to {id ='foo', name='bar'}. It is unclear to me why the enum has two properties. When I serialize enum Color, I only expect to see 'Red', or 'Green', etc. The id is an implementation detail irrelevant to the domain.

In the domain, it's better to have Maybe<Email> AdditionalEmail; instead of EMail? AdditionalEmail;. If you want to serialize the class somewhere, then you'd need to map it to a DTO, which you'd serialize instead of the domain class.

Regarding the enum class, there should be 2 versions, 1 with Id and Name and the other one with just an Id. I haven't looked into the current implementation in-depth yet, it probably needs some refactoring.

The gist: Maybe class does not support serialization so we have to use a DTO class.