Skip to content

Commit f12b99f

Browse files
Copilotstephentoub
andcommitted
Add comprehensive tests for custom converters with invalid types
Added tests covering properties with invalid types (ref properties) with converters on both properties and types, including collections and dictionaries. Fixed JsonPropertyInfo to handle null _jsonTypeInfo when custom converters are used. Co-authored-by: stephentoub <[email protected]>
1 parent 859d08a commit f12b99f

File tree

4 files changed

+418
-5
lines changed

4 files changed

+418
-5
lines changed

src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonPropertyInfo.cs

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -606,14 +606,14 @@ private void DetermineNumberHandlingForProperty()
606606
{
607607
Debug.Assert(DeclaringTypeInfo != null, "We should have ensured parent is assigned in JsonTypeInfo");
608608
Debug.Assert(!IsConfigured, "Should not be called post-configuration.");
609-
Debug.Assert(_jsonTypeInfo != null, "Must have already been determined on configuration.");
610609

611610
bool numberHandlingIsApplicable = NumberHandingIsApplicable();
612611

613612
if (numberHandlingIsApplicable)
614613
{
615614
// Priority 1: Get handling from attribute on property/field, its parent class type or property type.
616-
JsonNumberHandling? handling = NumberHandling ?? DeclaringTypeInfo.NumberHandling ?? _jsonTypeInfo.NumberHandling;
615+
// _jsonTypeInfo may be null if using a custom converter that doesn't need type metadata.
616+
JsonNumberHandling? handling = NumberHandling ?? DeclaringTypeInfo.NumberHandling ?? _jsonTypeInfo?.NumberHandling;
617617

618618
// Priority 2: Get handling from JsonSerializerOptions instance.
619619
if (!handling.HasValue && Options.NumberHandling != JsonNumberHandling.Strict)
@@ -673,9 +673,12 @@ private void DetermineEffectiveObjectCreationHandlingForProperty()
673673
ThrowHelper.ThrowInvalidOperationException_ObjectCreationHandlingPropertyValueTypeMustHaveASetter(this);
674674
}
675675

676-
Debug.Assert(_jsonTypeInfo != null);
677-
Debug.Assert(_jsonTypeInfo.IsConfigurationStarted);
678-
if (JsonTypeInfo.SupportsPolymorphicDeserialization)
676+
// _jsonTypeInfo may be null if using a custom converter that doesn't need type metadata.
677+
if (_jsonTypeInfo is not null)
678+
{
679+
Debug.Assert(_jsonTypeInfo.IsConfigurationStarted);
680+
}
681+
if (JsonTypeInfo?.SupportsPolymorphicDeserialization == true)
679682
{
680683
ThrowHelper.ThrowInvalidOperationException_ObjectCreationHandlingPropertyCannotAllowPolymorphicDeserialization(this);
681684
}
Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System.Collections.Generic;
5+
using System.Text.Json.Serialization;
6+
using Xunit;
7+
8+
namespace System.Text.Json.SourceGeneration.Tests
9+
{
10+
public static class InvalidTypesWithConvertersTests
11+
{
12+
[Fact]
13+
public static void SourceGen_CustomConverter_OnProperty_WithInvalidElementType_Succeeds()
14+
{
15+
var collection = new SourceGenCollectionWithInvalidElementType
16+
{
17+
Items = new List<SourceGenTypeWithRefProperty>
18+
{
19+
new SourceGenTypeWithRefProperty { Value1 = 42 }
20+
}
21+
};
22+
23+
string json = JsonSerializer.Serialize(collection, InvalidTypesContext.Default.SourceGenCollectionWithInvalidElementType);
24+
Assert.Equal(@"{""Items"":[]}", json);
25+
26+
var deserialized = JsonSerializer.Deserialize(json, InvalidTypesContext.Default.SourceGenCollectionWithInvalidElementType);
27+
Assert.NotNull(deserialized);
28+
Assert.NotNull(deserialized.Items);
29+
Assert.Empty(deserialized.Items);
30+
}
31+
32+
[Fact]
33+
public static void SourceGen_CustomConverter_OnProperty_WithInvalidType_Succeeds()
34+
{
35+
var obj = new SourceGenClassWithInvalidTypeProperty
36+
{
37+
InvalidProperty = new SourceGenTypeWithRefProperty { Value1 = 100 }
38+
};
39+
40+
string json = JsonSerializer.Serialize(obj, InvalidTypesContext.Default.SourceGenClassWithInvalidTypeProperty);
41+
Assert.Equal(@"{""InvalidProperty"":""custom""}", json);
42+
43+
var deserialized = JsonSerializer.Deserialize(json, InvalidTypesContext.Default.SourceGenClassWithInvalidTypeProperty);
44+
Assert.NotNull(deserialized);
45+
Assert.NotNull(deserialized.InvalidProperty);
46+
Assert.Equal(999, deserialized.InvalidProperty.Value1);
47+
}
48+
49+
[Fact]
50+
public static void SourceGen_CustomConverter_OnType_WithRefProperty_Succeeds()
51+
{
52+
var obj = new SourceGenClassWithTypeConverterAttribute
53+
{
54+
Item = new SourceGenTypeWithRefPropertyAndTypeConverter { Value1 = 50 }
55+
};
56+
57+
string json = JsonSerializer.Serialize(obj, InvalidTypesContext.Default.SourceGenClassWithTypeConverterAttribute);
58+
Assert.Equal(@"{""Item"":""type-converter""}", json);
59+
60+
var deserialized = JsonSerializer.Deserialize(json, InvalidTypesContext.Default.SourceGenClassWithTypeConverterAttribute);
61+
Assert.NotNull(deserialized);
62+
Assert.NotNull(deserialized.Item);
63+
Assert.Equal(888, deserialized.Item.Value1);
64+
}
65+
66+
[Fact]
67+
public static void SourceGen_CustomConverter_OnDictionary_WithInvalidValueType_Succeeds()
68+
{
69+
var obj = new SourceGenClassWithDictionaryOfInvalidType
70+
{
71+
Items = new Dictionary<string, SourceGenTypeWithRefProperty>
72+
{
73+
["key1"] = new SourceGenTypeWithRefProperty { Value1 = 1 }
74+
}
75+
};
76+
77+
string json = JsonSerializer.Serialize(obj, InvalidTypesContext.Default.SourceGenClassWithDictionaryOfInvalidType);
78+
Assert.Equal(@"{""Items"":{}}", json);
79+
80+
var deserialized = JsonSerializer.Deserialize(json, InvalidTypesContext.Default.SourceGenClassWithDictionaryOfInvalidType);
81+
Assert.NotNull(deserialized);
82+
Assert.NotNull(deserialized.Items);
83+
Assert.Empty(deserialized.Items);
84+
}
85+
}
86+
87+
// Test classes for source generation
88+
89+
public class SourceGenCollectionWithInvalidElementType
90+
{
91+
[JsonConverter(typeof(SourceGenListOfInvalidTypeConverter))]
92+
public IList<SourceGenTypeWithRefProperty> Items { get; set; }
93+
}
94+
95+
public class SourceGenClassWithInvalidTypeProperty
96+
{
97+
[JsonConverter(typeof(SourceGenInvalidTypeConverter))]
98+
public SourceGenTypeWithRefProperty InvalidProperty { get; set; }
99+
}
100+
101+
public class SourceGenClassWithTypeConverterAttribute
102+
{
103+
public SourceGenTypeWithRefPropertyAndTypeConverter Item { get; set; }
104+
}
105+
106+
public class SourceGenClassWithDictionaryOfInvalidType
107+
{
108+
[JsonConverter(typeof(SourceGenDictionaryOfInvalidTypeConverter))]
109+
public IDictionary<string, SourceGenTypeWithRefProperty> Items { get; set; }
110+
}
111+
112+
// Type with ref property - invalid for serialization
113+
public class SourceGenTypeWithRefProperty
114+
{
115+
public int Value1 { get; set; }
116+
117+
private int _value2;
118+
public ref int Value2 => ref _value2;
119+
}
120+
121+
// Type with ref property and [JsonConverter] attribute on the type itself
122+
[JsonConverter(typeof(SourceGenTypeWithRefPropertyConverter))]
123+
public class SourceGenTypeWithRefPropertyAndTypeConverter
124+
{
125+
public int Value1 { get; set; }
126+
127+
private int _value2;
128+
public ref int Value2 => ref _value2;
129+
}
130+
131+
// Custom converter for IList<SourceGenTypeWithRefProperty>
132+
public class SourceGenListOfInvalidTypeConverter : JsonConverter<IList<SourceGenTypeWithRefProperty>>
133+
{
134+
public override IList<SourceGenTypeWithRefProperty> Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
135+
{
136+
reader.Skip();
137+
return new List<SourceGenTypeWithRefProperty>();
138+
}
139+
140+
public override void Write(Utf8JsonWriter writer, IList<SourceGenTypeWithRefProperty> value, JsonSerializerOptions options)
141+
{
142+
writer.WriteStartArray();
143+
writer.WriteEndArray();
144+
}
145+
}
146+
147+
// Custom converter for SourceGenTypeWithRefProperty
148+
public class SourceGenInvalidTypeConverter : JsonConverter<SourceGenTypeWithRefProperty>
149+
{
150+
public override SourceGenTypeWithRefProperty Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
151+
{
152+
reader.Skip();
153+
return new SourceGenTypeWithRefProperty { Value1 = 999 };
154+
}
155+
156+
public override void Write(Utf8JsonWriter writer, SourceGenTypeWithRefProperty value, JsonSerializerOptions options)
157+
{
158+
writer.WriteStringValue("custom");
159+
}
160+
}
161+
162+
// Custom converter for SourceGenTypeWithRefPropertyAndTypeConverter
163+
public class SourceGenTypeWithRefPropertyConverter : JsonConverter<SourceGenTypeWithRefPropertyAndTypeConverter>
164+
{
165+
public override SourceGenTypeWithRefPropertyAndTypeConverter Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
166+
{
167+
reader.Skip();
168+
return new SourceGenTypeWithRefPropertyAndTypeConverter { Value1 = 888 };
169+
}
170+
171+
public override void Write(Utf8JsonWriter writer, SourceGenTypeWithRefPropertyAndTypeConverter value, JsonSerializerOptions options)
172+
{
173+
writer.WriteStringValue("type-converter");
174+
}
175+
}
176+
177+
// Custom converter for IDictionary<string, SourceGenTypeWithRefProperty>
178+
public class SourceGenDictionaryOfInvalidTypeConverter : JsonConverter<IDictionary<string, SourceGenTypeWithRefProperty>>
179+
{
180+
public override IDictionary<string, SourceGenTypeWithRefProperty> Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
181+
{
182+
reader.Skip();
183+
return new Dictionary<string, SourceGenTypeWithRefProperty>();
184+
}
185+
186+
public override void Write(Utf8JsonWriter writer, IDictionary<string, SourceGenTypeWithRefProperty> value, JsonSerializerOptions options)
187+
{
188+
writer.WriteStartObject();
189+
writer.WriteEndObject();
190+
}
191+
}
192+
193+
// Source generation context
194+
[JsonSerializable(typeof(SourceGenCollectionWithInvalidElementType))]
195+
[JsonSerializable(typeof(SourceGenClassWithInvalidTypeProperty))]
196+
[JsonSerializable(typeof(SourceGenClassWithTypeConverterAttribute))]
197+
[JsonSerializable(typeof(SourceGenClassWithDictionaryOfInvalidType))]
198+
internal partial class InvalidTypesContext : JsonSerializerContext
199+
{
200+
}
201+
}

0 commit comments

Comments
 (0)