public static object?Read(ref Utf8JsonReader reader,
                                  Type typeToConvert,
                                  JsonSerializerOptions nestedOptions,
                                  Func <Type, JsonSerializerOptions, IEnumerable <PropertyInfo> >?getPropertiesToRead = null)
        {
            getPropertiesToRead ??= GetDefaultPropertiesToRead;
            switch (reader.TokenType)
            {
            case JsonTokenType.StartObject:
            {
                var obj        = Activator.CreateInstance(typeToConvert);
                var properties = getPropertiesToRead(typeToConvert, nestedOptions)
                                 .ToDictionary(keySelector: p => p.Name.ToLower(), StringComparer.CurrentCultureIgnoreCase);
                var extensionProperties = getPropertiesToRead(typeToConvert, nestedOptions).Where(p => p.HasAttribute <JsonExtensionDataAttribute>())
                                          .Take(2)
                                          .ToList();
                PropertyInfo?extensionProperty = null;
                if (extensionProperties.Count == 2)
                {
                    throw new InvalidOperationException("Multiple properties decorated with 'JsonExtensionDataAttribute' found");
                }
                else if (extensionProperties.Count == 1)
                {
                    extensionProperty = extensionProperties[0];
                    if (!typeof(IDictionary <string, object>).IsAssignableFrom(extensionProperty.PropertyType))
                    {
                        throw new InvalidOperationException($"Property '{extensionProperty.Name}' decorated with 'JsonExtensionDataAttribute' is not assignable to `IDictionary<string, object>`");
                    }
                }

                IDictionary <string, object?>?extensionDictionary = null;
                if (extensionProperty != null)
                {
                    extensionDictionary = new Dictionary <string, object?>();
                    extensionProperty.SetValue(obj, extensionDictionary);
                }
                while (true)
                {
                    reader.Read();
                    if (reader.TokenType == JsonTokenType.EndObject)
                    {
                        break;
                    }
                    Contract.Assert(reader.TokenType == JsonTokenType.PropertyName);

                    string propertyName = reader.GetString() ?? throw new ContractException();

                    if (properties.TryGetValue(propertyName, out PropertyInfo? property))
                    {
                        var    copy = reader;
                        object?value;
                        try
                        {
                            value = JsonSerializer.Deserialize(ref reader, property.PropertyType, nestedOptions);
                        }
                        catch (JsonException)
                        {
                            var token = copy.GetTokenAsJson();
                            throw new JsonException($"Cannot deserialize type '{property.PropertyType}' from: \n\n" + token);
                        }
                        property.SetValue(obj, value);
                    }
                    else if (extensionDictionary != null)
                    {
                        var value = JsonSerializer.Deserialize <object?>(ref reader, nestedOptions);
                        extensionDictionary[propertyName] = value;
                    }
                    else
                    {
                        Global.AddDebugObject("Ignoring property " + propertyName);
                    }
                }
                return(obj);
            }

            case JsonTokenType.StartArray:
                throw new NotImplementedException();

            case JsonTokenType.False:
            case JsonTokenType.True:
                return(reader.GetBoolean());

            case JsonTokenType.Number:
                return(reader.GetSingle());

            case JsonTokenType.None:
                return(null);

            case JsonTokenType.String:
                return(reader.GetString());

            case JsonTokenType.Comment:
                reader.Read();
                return(Read(ref reader, typeToConvert, nestedOptions));

            default:
                throw new Exception("Unknown tokentype");
            }
        }