Repository: dotnet/runtime
File: src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/CustomConverterTests/CustomConverterTests.KeyConverters.cs (C#)

Code before:
        }

        [Fact]
        public static void ObjectConverterHandlingStrings_DictionaryWithStringKey_DoesNotCauseStackOverflow()
        {
            // Regression test: A custom JsonConverter<object> that claims to handle strings
            // via CanConvert was causing StackOverflowException when serializing dictionaries
            // with string keys, due to infinite recursion in the CastingConverter.
            var options = new JsonSerializerOptions { Converters = { new GenericObjectConverterHandlingStrings() } };
            var value = new Dictionary<string, int> { ["key"] = 123 };

            string json = JsonSerializer.Serialize(value, options);
            Assert.Equal(@"{""key"":123}", json);

            var deserialized = JsonSerializer.Deserialize<Dictionary<string, int>>(json, options);
            Assert.Equal(123, deserialized["key"]);
        }

        public class GenericObjectConverterHandlingStrings : JsonConverter<object>
        {
            public override bool CanConvert(Type typeToConvert) => typeToConvert == typeof(string);
            public override object Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) => reader.GetString()!;
            public override void Write(Utf8JsonWriter writer, object value, JsonSerializerOptions options) => writer.WriteStringValue(value?.ToString());
        }

        public class EmbeddedJsonKeyConverter<T> : JsonConverter<T>
        {
            public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions _)
                => throw new NotSupportedException();

            public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions _)
                => throw new NotSupportedException();

            public override void WriteAsPropertyName(Utf8JsonWriter writer, T value, JsonSerializerOptions _)
                => writer.WritePropertyName(JsonSerializer.Serialize(value));

            public override T ReadAsPropertyName(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions _)
                => JsonSerializer.Deserialize<T>(reader.GetString());
        }

        public class InvalidCustomKeyConverter : JsonConverter<string>
        {
            public enum InvalidOperationType
            {
                DoNothing, HandleEntireProperty, HandleEntireParentObject, ReturnNull
            }

            public InvalidOperationType OperationType { get; init; }

            public override string Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
                => throw new NotSupportedException();

Review comment (suggestion):
@copilot please make the converter have special behavior (e.g. writing out an empty string and returning a constant on deserialization) so that we can be confident about assertions exercising the right converter outputs.

Code after:
        }

        [Fact]
        public static void ObjectConverterHandlingStrings_DictionaryWithStringKey_DoesNotCauseStackOverflow()
        {
            // Regression test: A custom JsonConverter<object> that claims to handle strings
            // via CanConvert was causing StackOverflowException when serializing dictionaries
            // with string keys, due to infinite recursion in the CastingConverter.
            // The converter writes an empty string for property names and returns a constant on read,
            // which lets us verify the fallback converter is being used instead of the custom converter.
            var options = new JsonSerializerOptions { Converters = { new GenericObjectConverterHandlingStrings() } };
            var value = new Dictionary<string, int> { ["key"] = 123 };

            // The fallback StringConverter should be used for property names, preserving "key"
            string json = JsonSerializer.Serialize(value, options);
            Assert.Equal(@"{""key"":123}", json);

            // Deserialization should also use the fallback converter for property names
            var deserialized = JsonSerializer.Deserialize<Dictionary<string, int>>(json, options);
            Assert.Equal(123, deserialized["key"]);
        }

        public class GenericObjectConverterHandlingStrings : JsonConverter<object>
        {
            public override bool CanConvert(Type typeToConvert) => typeToConvert == typeof(string);
            public override object Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) => reader.GetString()!;
            public override void Write(Utf8JsonWriter writer, object value, JsonSerializerOptions options) => writer.WriteStringValue(value?.ToString());

            // These methods write/read distinctive values to verify the fallback converter is used instead
            public override void WriteAsPropertyName(Utf8JsonWriter writer, object value, JsonSerializerOptions options)
                => writer.WritePropertyName(string.Empty);
            public override object ReadAsPropertyName(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
                => "unexpected_key";
        }

        public class EmbeddedJsonKeyConverter<T> : JsonConverter<T>
        {
            public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions _)
                => throw new NotSupportedException();

            public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions _)
                => throw new NotSupportedException();

            public override void WriteAsPropertyName(Utf8JsonWriter writer, T value, JsonSerializerOptions _)
                => writer.WritePropertyName(JsonSerializer.Serialize(value));

            public override T ReadAsPropertyName(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions _)
                => JsonSerializer.Deserialize<T>(reader.GetString());
        }

        public class InvalidCustomKeyConverter : JsonConverter<string>