// ========================================================================== // JsonInheritanceConverter.cs // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex Group // All rights reserved. // ========================================================================== using System; using System.Linq; using System.Reflection; using System.Runtime.Serialization; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using NJsonSchema.Annotations; #pragma warning disable SA1306 // Field names must begin with lower-case letter namespace Squidex.Areas.Api.Controllers { public sealed class JsonInheritanceConverter : JsonConverter { private readonly string discriminator; [ThreadStatic] private static bool IsReading; [ThreadStatic] private static bool IsWriting; public override bool CanWrite { get { if (!IsWriting) { return true; } return IsWriting = false; } } public override bool CanRead { get { if (!IsReading) { return true; } return IsReading = false; } } public JsonInheritanceConverter(string discriminator) { this.discriminator = discriminator; } public override bool CanConvert(Type objectType) { return true; } public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { IsWriting = true; try { var jsonObject = JObject.FromObject(value, serializer); jsonObject.AddFirst(new JProperty(discriminator, GetSchemaName(value.GetType()))); writer.WriteToken(jsonObject.CreateReader()); } finally { IsWriting = false; } } public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { IsReading = true; try { var jsonObject = serializer.Deserialize(reader); var subName = jsonObject[discriminator]?.Value(); if (subName == null) { return null; } var subType = GetObjectSubtype(objectType, subName); if (subType == null) { return null; } return serializer.Deserialize(jsonObject.CreateReader(), subType); } finally { IsReading = false; } } private static Type GetObjectSubtype(Type objectType, string discriminatorValue) { var knownTypeAttribute = objectType.GetTypeInfo().GetCustomAttributes() .FirstOrDefault(a => IsKnownType(a, discriminatorValue)); return knownTypeAttribute?.Type; } private static bool IsKnownType(KnownTypeAttribute attribute, string discriminator) { var type = attribute.Type; return type != null && GetSchemaName(type) == discriminator; } private static string GetSchemaName(Type type) { var schenaName = type.GetTypeInfo().GetCustomAttribute()?.Name; return schenaName ?? type.Name; } } }