private JsonMapsCache GetInfoFromTypeCached(Type objectType)
        {
            JsonMapsCache data;

            lock (this.dicTypeData)
                if (!this.dicTypeData.TryGetValue(objectType, out data))
                {
                    JsonMapBase[]   listOfMappers = this.jsonMapsTree.GetMappers(objectType).ToArray();
                    JsonMapBase[][] subMappers    = this.jsonMapsTree.GetSubpathes(objectType).ToArray();
                    if (listOfMappers.Length == 0 && subMappers.Length == 0)
                    {
                        this.dicTypeData[objectType] = null;
                    }
                    else
                    {
                        this.dicTypeData[objectType] = data = new JsonMapsCache {
                            Mappers = listOfMappers, SubMappers = subMappers
                        }
                    };
                    return(data);
                }
                else
                {
                    return(data);
                }
        }
        public override bool CanConvert(Type objectType)
        {
            if (this.blockOnce == 1)
            {
                this.blockOnce = 0;
                return(false);
            }

            // the question here is: is this objectType associated with any class hierarchy in the `JsonMap` list?
            JsonMapsCache cachedData = this.GetInfoFromTypeCached(objectType);

            return(cachedData != null);
        }
        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {
            // the question here is: is this objectType associated with any class hierarchy in the `JsonMap` list?
            JsonMapsCache cachedData = this.GetInfoFromTypeCached(objectType);

            while (this.blockOnce == 1)
            {
                throw new Exception("Writing is blocked but should not.");
            }

            if (reader.TokenType == JsonToken.Null)
            {
                bool isNullable = Nullable.GetUnderlyingType(objectType) != null || !objectType.GetTypeInfo().IsValueType;
                if (isNullable)
                {
                    return(null);
                }
            }

            JObject jo = JObject.Load(reader);

            object?value = reader.Value;

            // Looking for discriminators:
            // - match the discriminators of the type given by `objectType`
            // - match the discriminators of subtypes of `objectType`

            // STEP 1: match the discriminators of the type given by `objectType`
            string      discriminatorFieldValue = null;
            bool        foundBaseIAndSubtypes   = false;
            JsonMapBase classMapToUse           = null;

            foreach (JsonMapBase jsonMapBase in cachedData.Mappers.Reverse())
            {
                if (foundBaseIAndSubtypes)
                {
                    throw new Exception($"IAndSubtypes mapper does not accept child mappers.");
                }

                if (jsonMapBase is IAndSubtypes)
                {
                    if (jsonMapBase.DiscriminatorFieldName != null)
                    {
                        JToken?discriminatorFieldValueToken = jo[jsonMapBase.DiscriminatorFieldName];
                        if (discriminatorFieldValueToken?.Type == JTokenType.String)
                        {
                            discriminatorFieldValue = discriminatorFieldValueToken.Value <string>();
                        }
                    }

                    foundBaseIAndSubtypes = true;
                    var jsonMapWithSubtypes = jsonMapBase as IAndSubtypes;
                    value = jsonMapWithSubtypes.CreateObject(discriminatorFieldValue);
                    serializer.Populate(jo.CreateReader(), value);
                    discriminatorFieldValue = null;
                }
                else
                {
                    var jsonSubclassMap = jsonMapBase as JsonSubclassMap;
                    if (jsonSubclassMap?.DiscriminatorFieldValue != null && discriminatorFieldValue != null)
                    {
                        if (jsonSubclassMap.DiscriminatorFieldValue != discriminatorFieldValue)
                        {
                            throw new Exception($"Discriminator does not match.");
                        }

                        discriminatorFieldValue = null;
                    }

                    if (jsonMapBase.DiscriminatorFieldName != null)
                    {
                        JToken?discriminatorFieldValueToken = jo[jsonMapBase.DiscriminatorFieldName];
                        if (discriminatorFieldValueToken?.Type == JTokenType.String)
                        {
                            discriminatorFieldValue = discriminatorFieldValueToken.Value <string>();
                        }
                    }

                    classMapToUse = jsonMapBase;
                }
            }


            // STEP 2: match the discriminators of subtypes of `objectType`
            JsonMapBase subclassMapToUse = null;

            foreach (JsonMapBase[] jsonSubmappers in cachedData.SubMappers)
            {
                string      subDiscriminatorFieldValue = discriminatorFieldValue;
                bool        foundSubIAndSubtypes       = foundBaseIAndSubtypes;
                JsonMapBase subclassMapToUse2          = null;
                foreach (JsonMapBase submapper in jsonSubmappers.Reverse())
                {
                    if (foundSubIAndSubtypes)
                    {
                        throw new Exception($"IAndSubtypes mapper does not accept child mappers.");
                    }

                    if (submapper is IAndSubtypes)
                    {
                        if (submapper.DiscriminatorFieldName != null)
                        {
                            JToken?discriminatorFieldValueToken = jo[submapper.DiscriminatorFieldName];
                            if (discriminatorFieldValueToken?.Type == JTokenType.String)
                            {
                                subDiscriminatorFieldValue = discriminatorFieldValueToken.Value <string>();
                            }

                            if (subDiscriminatorFieldValue == null)
                            {
                                throw new Exception($"Discriminator field not found.");
                            }
                        }

                        foundSubIAndSubtypes = true;
                        var jsonMapWithSubtypes = submapper as IAndSubtypes;
                        value = jsonMapWithSubtypes.CreateObject(subDiscriminatorFieldValue);
                        serializer.Populate(jo.CreateReader(), value);
                        subDiscriminatorFieldValue = null;
                    }
                    else
                    {
                        var jsonSubclassMap = submapper as JsonSubclassMap;
                        if (jsonSubclassMap != null)
                        {
                            if (jsonSubclassMap.DiscriminatorFieldValue != null)
                            {
                                if (jsonSubclassMap.DiscriminatorFieldValue != subDiscriminatorFieldValue)
                                {
                                    break;
                                }

                                if (subclassMapToUse != null)
                                {
                                    throw new Exception($"Ambiguous maps for the type `{submapper.SerializedType.Name}`");
                                }

                                subDiscriminatorFieldValue = null;
                                subclassMapToUse2          = jsonSubclassMap;
                            }
                        }
                        else
                        {
                            if (cachedData.Mappers.Length > 0)
                            {
                                throw new Exception($"Sub mappers must be subclasses of `{typeof(JsonSubclassMap).Name}`: `{cachedData.Mappers[0].GetType().Name}`");
                            }

                            if (subclassMapToUse != null)
                            {
                                throw new Exception($"Ambiguous maps for the type `{submapper.SerializedType.Name}`");
                            }

                            subclassMapToUse2 = submapper;
                        }

                        if (submapper.DiscriminatorFieldName != null)
                        {
                            JToken?discriminatorFieldValueToken = jo[submapper.DiscriminatorFieldName];
                            if (discriminatorFieldValueToken?.Type == JTokenType.String)
                            {
                                subDiscriminatorFieldValue = discriminatorFieldValueToken.Value <string>();
                            }

                            if (subDiscriminatorFieldValue == null)
                            {
                                break;
                            }
                        }
                    }
                }

                if (subclassMapToUse2 != null)
                {
                    subclassMapToUse = subclassMapToUse2;
                }

                if (subclassMapToUse2 != null && subDiscriminatorFieldValue != null)
                {
                    throw new Exception("Value of discriminator field not verified by any subclass map.");
                }
            }

            if (subclassMapToUse == null && discriminatorFieldValue != null)
            {
                throw new Exception("Value of discriminator field not verified by any subclass map.");
            }

            // STEP 3: Creating the new value with the correct JsonMapBase class if any
            // - by the way, there is no JsonMapBase when a IAndSubtypes mapper is found
            JsonMapBase classMapToUse2 = subclassMapToUse ?? classMapToUse;

            if (classMapToUse2 != null)
            {
                if (!classMapToUse2.CanCreateNew())
                {
                    throw new Exception($"Cannot create object of type `{classMapToUse2.SerializedType}`");
                }

                value = classMapToUse2.CreateNew();
                serializer.Populate(jo.CreateReader(), value);
            }

            return(value);
        }
        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            Type          objectType = value?.GetType();
            JsonMapsCache cachedData = this.GetInfoFromTypeCached(objectType);

            while (this.blockOnce == 1)
            {
                throw new Exception("Writing is blocked but should not.");
            }

            this.blockOnce = 1;
            JObject jo = JObject.FromObject(value);

            // finding discriminator field names
            string discriminatorFieldValue = null;

            foreach (JsonMapBase jsonMapBase in cachedData.Mappers)
            {
                if (jsonMapBase is IAndSubtypes)
                {
                    if (cachedData.Mappers.Length > 1)
                    {
                        throw new Exception("Json mapper of type `IAndSubtypes` cannot be combined with other mappers.");
                    }

                    var jsonMapWithSubtypes = jsonMapBase as IAndSubtypes;

                    discriminatorFieldValue = jsonMapWithSubtypes.DiscriminatorFieldValueGetter != null
                        ? jsonMapWithSubtypes.DiscriminatorFieldValueGetter(value.GetType())
                        : value.GetType().AssemblyQualifiedName;

                    jo.Add(jsonMapBase.DiscriminatorFieldName, discriminatorFieldValue);
                    discriminatorFieldValue = null;
                }
                else
                {
                    if (jsonMapBase.DiscriminatorFieldName != null)
                    {
                        if (discriminatorFieldValue == null)
                        {
                            throw new Exception("Value of discriminator field not defined by subclass map.");
                        }

                        jo.Add(jsonMapBase.DiscriminatorFieldName, discriminatorFieldValue);
                        discriminatorFieldValue = null;
                    }

                    var jsonSubclassMap = jsonMapBase as JsonSubclassMap;
                    if (jsonSubclassMap?.DiscriminatorFieldValue != null)
                    {
                        discriminatorFieldValue = jsonSubclassMap.DiscriminatorFieldValue;
                    }
                }
            }

            if (discriminatorFieldValue != null)
            {
                throw new Exception("Discriminating value not set to any field.");
            }

            jo.WriteTo(writer, serializer.Converters.ToArray());
        }