<PackageReference Include="Newtonsoft.Json" Version="9.0.1" />

DiscriminatedUnionConverter

Converts a F# discriminated union type to and from JSON.
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Serialization; using Newtonsoft.Json.Utilities; using System; using System.Collections; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Reflection; namespace Newtonsoft.Json.Converters { public class DiscriminatedUnionConverter : JsonConverter { internal class Union { public List<UnionCase> Cases; public FSharpFunction TagReader { get; set; } } internal class UnionCase { public int Tag; public string Name; public PropertyInfo[] Fields; public FSharpFunction FieldReader; public FSharpFunction Constructor; } private const string CasePropertyName = "Case"; private const string FieldsPropertyName = "Fields"; private static readonly ThreadSafeStore<Type, Union> UnionCache = new ThreadSafeStore<Type, Union>(CreateUnion); private static readonly ThreadSafeStore<Type, Type> UnionTypeLookupCache = new ThreadSafeStore<Type, Type>(CreateUnionTypeLookup); private static Type CreateUnionTypeLookup(Type t) { object arg = ((object[])FSharpUtils.GetUnionCases(null, t, null)).First(); return (Type)FSharpUtils.GetUnionCaseInfoDeclaringType(arg); } private static Union CreateUnion(Type t) { Union union = new Union(); union.TagReader = (FSharpFunction)FSharpUtils.PreComputeUnionTagReader(null, t, null); union.Cases = new List<UnionCase>(); object[] array = (object[])FSharpUtils.GetUnionCases(null, t, null); foreach (object obj in array) { UnionCase unionCase = new UnionCase(); unionCase.Tag = (int)FSharpUtils.GetUnionCaseInfoTag(obj); unionCase.Name = (string)FSharpUtils.GetUnionCaseInfoName(obj); unionCase.Fields = (PropertyInfo[])FSharpUtils.GetUnionCaseInfoFields(obj); unionCase.FieldReader = (FSharpFunction)FSharpUtils.PreComputeUnionReader(null, obj, null); unionCase.Constructor = (FSharpFunction)FSharpUtils.PreComputeUnionConstructor(null, obj, null); union.Cases.Add(unionCase); } return union; } public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { DefaultContractResolver defaultContractResolver = serializer.ContractResolver as DefaultContractResolver; Type key = UnionTypeLookupCache.Get(value.GetType()); Union union = UnionCache.Get(key); int tag = (int)union.TagReader.Invoke(value); UnionCase unionCase = union.Cases.Single((UnionCase c) => c.Tag == tag); writer.WriteStartObject(); writer.WritePropertyName((defaultContractResolver != null) ? defaultContractResolver.GetResolvedPropertyName("Case") : "Case"); writer.WriteValue(unionCase.Name); if (unionCase.Fields != null && unionCase.Fields.Length != 0) { object[] obj = (object[])unionCase.FieldReader.Invoke(value); writer.WritePropertyName((defaultContractResolver != null) ? defaultContractResolver.GetResolvedPropertyName("Fields") : "Fields"); writer.WriteStartArray(); object[] array = obj; foreach (object value2 in array) { serializer.Serialize(writer, value2); } writer.WriteEndArray(); } writer.WriteEndObject(); } public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { if (reader.TokenType == JsonToken.Null) return null; UnionCase unionCase = null; string caseName = null; JArray jArray = null; reader.ReadAndAssert(); while (reader.TokenType == JsonToken.PropertyName) { string text = reader.Value.ToString(); if (string.Equals(text, "Case", StringComparison.OrdinalIgnoreCase)) { reader.ReadAndAssert(); Union union = UnionCache.Get(objectType); caseName = reader.Value.ToString(); unionCase = union.Cases.SingleOrDefault((UnionCase c) => c.Name == caseName); if (unionCase == null) throw JsonSerializationException.Create(reader, "No union type found with the name '{0}'.".FormatWith(CultureInfo.InvariantCulture, caseName)); } else { if (!string.Equals(text, "Fields", StringComparison.OrdinalIgnoreCase)) throw JsonSerializationException.Create(reader, "Unexpected property '{0}' found when reading union.".FormatWith(CultureInfo.InvariantCulture, text)); reader.ReadAndAssert(); if (reader.TokenType != JsonToken.StartArray) throw JsonSerializationException.Create(reader, "Union fields must been an array."); jArray = (JArray)JToken.ReadFrom(reader); } reader.ReadAndAssert(); } if (unionCase == null) throw JsonSerializationException.Create(reader, "No '{0}' property with union name found.".FormatWith(CultureInfo.InvariantCulture, "Case")); object[] array = new object[unionCase.Fields.Length]; if (unionCase.Fields.Length != 0 && jArray == null) throw JsonSerializationException.Create(reader, "No '{0}' property with union fields found.".FormatWith(CultureInfo.InvariantCulture, "Fields")); if (jArray != null) { if (unionCase.Fields.Length != jArray.Count) throw JsonSerializationException.Create(reader, "The number of field values does not match the number of properties defined by union '{0}'.".FormatWith(CultureInfo.InvariantCulture, caseName)); for (int i = 0; i < jArray.Count; i++) { JToken jToken = jArray[i]; PropertyInfo propertyInfo = unionCase.Fields[i]; array[i] = jToken.ToObject(propertyInfo.PropertyType, serializer); } } object[] args = new object[1] { array }; return unionCase.Constructor.Invoke(args); } public override bool CanConvert(Type objectType) { if (typeof(IEnumerable).IsAssignableFrom(objectType)) return false; Attribute[] array = CustomAttributeExtensions.GetCustomAttributes(objectType.GetTypeInfo(), true).ToArray(); bool flag = false; object[] array2 = array; for (int i = 0; i < array2.Length; i++) { Type type = array2[i].GetType(); if (type.FullName == "Microsoft.FSharp.Core.CompilationMappingAttribute") { FSharpUtils.EnsureInitialized(type.Assembly()); flag = true; break; } } if (!flag) return false; return (bool)FSharpUtils.IsUnion(null, objectType, null); } } }