/// <summary>
        /// Reads the JSON representation of the object.
        /// </summary>
        /// <param name="reader">The <see cref="JsonReader"/> to read from.</param>
        /// <param name="objectType">Type of the object.</param>
        /// <param name="existingValue">The existing value of object being read.</param>
        /// <param name="serializer">The calling serializer.</param>
        /// <returns>The object value.</returns>
        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {
            var(jToken, range) = SourceScope.ReadTokenRange(reader, sourceContext);

            using (new SourceScope(sourceContext, range))
            {
                string refDialogName = null;

                if (resourceExplorer.IsRef(jToken))
                {
                    refDialogName = jToken.Value <string>();

                    // We can't do this asynchronously as the Json.NET interface is synchronous
                    var reference = resourceExplorer.ResolveRefInternalAsync(jToken, sourceContext).GetAwaiter().GetResult();
                    jToken = reference.token;
                    if (!rangeReferences.ContainsKey(jToken))
                    {
                        rangeReferences.Add(jToken, reference.range);
                    }
                }

                var kind = (string)jToken["$kind"];

                if (kind == null)
                {
                    // see if there is jObject resolver
                    var unKnownResult = ResolveUnknownObject(jToken);
                    if (unKnownResult != null)
                    {
                        return(unKnownResult);
                    }

                    throw new ArgumentNullException($"$kind was not found: {JsonConvert.SerializeObject(jToken)}");
                }

                // if reference resolution made a source context available for the JToken, then add it to the context stack
                var found = rangeReferences.TryGetValue(jToken, out var rangeResolved);
                using (var newScope = found ? new SourceScope(sourceContext, rangeResolved) : null)
                {
                    var passTwo = false;

                    foreach (var observer in this.observers)
                    {
                        if (observer is CycleDetectionObserver cycDetectObserver && cycDetectObserver.CycleDetectionPass == CycleDetectionPasses.PassTwo)
                        {
                            passTwo = true;
                        }

                        if (observer.OnBeforeLoadToken(sourceContext, rangeResolved ?? range, jToken, out T interceptResult))
                        {
                            return(interceptResult);
                        }
                    }

                    var tokenToBuild = TryAssignId(jToken, sourceContext);

                    T result;
                    if (passTwo && refDialogName != null && cachedRefDialogs.ContainsKey(refDialogName))
                    {
                        result = cachedRefDialogs[refDialogName];
                    }
                    else
                    {
                        result = this.resourceExplorer.BuildType <T>(kind, tokenToBuild, serializer);
                        if (passTwo && refDialogName != null)
                        {
                            cachedRefDialogs[refDialogName] = result;
                            this.resourceExplorer.UpdateResourceTokenCache(refDialogName, tokenToBuild, range);
                        }
                    }

                    // Associate the most specific source context information with this item
                    if (sourceContext.CallStack.Count > 0)
                    {
                        range = sourceContext.CallStack.Peek().DeepClone();
                        if (!DebugSupport.SourceMap.TryGetValue(result, out var _))
                        {
                            DebugSupport.SourceMap.Add(result, range);
                        }
                    }

                    foreach (var observer in this.observers)
                    {
                        if (observer.OnAfterLoadToken(sourceContext, range, jToken, result, out T interceptedResult))
                        {
                            return(interceptedResult);
                        }
                    }

                    return(result);
                }
            }
        }