Skip to content

Instantly share code, notes, and snippets.

@gmanny
Last active May 17, 2022 15:54
Show Gist options
  • Save gmanny/dda5fe1d6ebc9dfa6843741c568a12b9 to your computer and use it in GitHub Desktop.
Save gmanny/dda5fe1d6ebc9dfa6843741c568a12b9 to your computer and use it in GitHub Desktop.
JsonConverter for LanguageExt.Option which serializes empty Options as nulls.
using System;
using System.Collections.Concurrent;
using System.Linq;
using System.Reflection;
using LanguageExt;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
public class OptionJsonConverter : JsonConverter
{
private static readonly ConcurrentDictionary<Type, ReflectionTypeData> cachedReflection =
new ConcurrentDictionary<Type, ReflectionTypeData>();
private ReflectionTypeData GetForOptionType(Type optionType)
{
return cachedReflection.GetOrAdd(optionType.GetGenericArguments().First(),
t => new ReflectionTypeData(optionType));
}
public override bool CanConvert(Type objectType)
{
return objectType.IsGenericType && objectType.GetGenericTypeDefinition().IsAssignableFrom(typeof(Option<>));
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
ReflectionTypeData typeData = GetForOptionType(value.GetType());
if ((bool)typeData.IsNoneProp.GetValue(value))
{
writer.WriteNull();
return;
}
serializer.Serialize(writer, typeData.IfNoneMethod.Invoke(value, new object[] { null }));
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue,
JsonSerializer serializer)
{
ReflectionTypeData typeData = GetForOptionType(objectType);
if (reader.TokenType == JsonToken.Null)
{
return typeData.NoneField.GetValue(null);
}
object result = serializer.Deserialize(reader, objectType.GetGenericArguments().First());
return typeData.SomeMethod.Invoke(null, new[] { result });
}
private class ReflectionTypeData
{
public ReflectionTypeData(Type optionType)
{
IsNoneProp = optionType.GetProperty(nameof(Option<object>.IsNone),
BindingFlags.Instance | BindingFlags.Public);
IfNoneMethod = optionType.GetMethods(BindingFlags.Instance | BindingFlags.Public)
.Filter(m => m.Name == nameof(Option<object>.IfNone)
&& m.GetParameters().Length == 1
&& !(m.GetParameters()[0].ParameterType.IsGenericType && m.GetParameters()[0]
.ParameterType.GetGenericTypeDefinition().IsAssignableFrom(typeof(Func<>))))
.First();
NoneField = optionType.GetField(nameof(Option<object>.None), BindingFlags.Static | BindingFlags.Public);
SomeMethod = optionType.GetMethod(nameof(Option<object>.Some),
BindingFlags.Static | BindingFlags.Public);
}
public PropertyInfo IsNoneProp { get; }
public MethodInfo IfNoneMethod { get; }
public FieldInfo NoneField { get; }
public MethodInfo SomeMethod { get; }
}
}
using System;
using System.Collections.Generic;
using System.IO;
using LanguageExt;
using Newtonsoft.Json;
public class TestSer
{
public Option<string> test1;
public Option<uint> test2;
public Option<List<double>> test3;
}
public class OptionJsonConverterTest
{
static void Main(string[] args)
{
JsonSerializer ser = new JsonSerializer();
ser.Converters.Add(new OptionJsonConverter());
var x = new TestSer
{
test1 = Prelude.None,
test2 = Prelude.None,
test3 = Prelude.None,
};
StringWriter sw = new StringWriter();
ser.Serialize(sw, x);
string ss = sw.ToString();
Console.WriteLine(ss);
TestSer y = ser.Deserialize<TestSer>(new JsonTextReader(new StringReader(ss)));
x = new TestSer
{
test1 = Prelude.Some("123"),
test2 = Prelude.Some(123123u),
test3 = Prelude.Some(new List<double> { 1, 2, 3 })
};
sw = new StringWriter();
ser.Serialize(sw, x);
ss = sw.ToString();
Console.WriteLine(ss);
y = ser.Deserialize<TestSer>(new JsonTextReader(new StringReader(ss)));
Console.ReadLine();
}
}
@danielbe1
Copy link

awsome! been looking for something like this.

@pcharbon70
Copy link

Very much appreciated!

@VincenzoCarlino
Copy link

@gavadinov Using WriteJson as implemented above give me some errors, for example serialization of empty IEnumerables will end up with this:

prop: {}

instead of:

prop: []

I solved that by rewriting function WriteJson:

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        if (value is IOptional optional)
        {
            optional.MatchUntyped<Unit>(
                optValue =>
                {
                    serializer.Serialize(writer, optValue);
                    return Unit.Default;
                },
                () =>
                {
                    writer.WriteNull();
                    return Unit.Default;
                }
            );
        }
    }

Do you have any tip/suggestion on this?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment