Last active
May 17, 2022 15:54
-
-
Save gmanny/dda5fe1d6ebc9dfa6843741c568a12b9 to your computer and use it in GitHub Desktop.
JsonConverter for LanguageExt.Option which serializes empty Options as nulls.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; } | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | |
} | |
} |
Very much appreciated!
@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
awsome! been looking for something like this.