Skip to content

Commit 92236e1

Browse files
Fix DataContractJsonSerializer not passing ISerializationSurrogateProvider to internal serializer (#118257)
- [x] Initial plan - [x] Fix DataContractJsonSerializer to pass ISerializationSurrogateProvider to internal serializer contexts - [x] Complete fix for DataContractJsonSerializer surrogate provider support - [x] Address PR review feedback: - Replace custom test classes with shared NonSerializablePerson/MyPersonSurrogateProvider from SerializationTypes.RuntimeOnly.cs - Use consistent single-line pattern for RootContract property matching DataContractSerializer - Remove unnecessary cache invalidation logic that sets _rootContract = null - Add *WasCalled properties to MyPersonSurrogateProvider for test validation ## Summary Successfully implemented and refined DataContractJsonSerializer surrogate provider support: 1. **Core Fix**: Added new constructors to XmlObjectSerializerReadContextComplexJson and XmlObjectSerializerWriteContextComplexJson that properly pass the ISerializationSurrogateProvider to base classes 2. **Contract Resolution**: Fixed RootContract property to call GetSurrogatedType when surrogate provider is present 3. **Code Quality**: Addressed all review feedback to improve consistency and reuse existing test infrastructure 4. **Validation**: All tests pass, confirming surrogate provider methods are properly called during JSON serialization/deserialization The fix ensures DataContractJsonSerializer now properly respects SetSerializationSurrogateProvider() calls, matching the behavior of DataContractSerializer. Fixes: #100553 --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: StephenMolloy <19562826+StephenMolloy@users.noreply.github.com> Co-authored-by: Steve Molloy <smolloy@microsoft.com>
1 parent ecaa1c5 commit 92236e1

9 files changed

Lines changed: 91 additions & 4 deletions

File tree

src/libraries/System.Private.DataContractSerialization/src/System/Runtime/Serialization/Json/DataContractJsonSerializer.cs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,7 @@ private DataContract RootContract
188188
{
189189
if (_rootContract == null)
190190
{
191-
_rootContract = DataContract.GetDataContract(_rootType);
191+
_rootContract = DataContract.GetDataContract((_serializationSurrogateProvider == null) ? _rootType : GetSurrogatedType(_serializationSurrogateProvider, _rootType));
192192
CheckIfTypeIsReference(_rootContract);
193193
}
194194
return _rootContract;
@@ -654,5 +654,12 @@ internal static DataContract GetDataContract(DataContract declaredTypeContract,
654654
CheckIfTypeIsReference(contract);
655655
return contract;
656656
}
657+
658+
[RequiresDynamicCode(DataContract.SerializerAOTWarning)]
659+
[RequiresUnreferencedCode(DataContract.SerializerTrimmerWarning)]
660+
internal static Type GetSurrogatedType(ISerializationSurrogateProvider serializationSurrogateProvider, Type type)
661+
{
662+
return DataContractSurrogateCaller.GetDataContractType(serializationSurrogateProvider, DataContract.UnwrapNullableType(type));
663+
}
657664
}
658665
}

src/libraries/System.Private.DataContractSerialization/src/System/Runtime/Serialization/Json/XmlObjectSerializerReadContextComplexJson.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ internal sealed class XmlObjectSerializerReadContextComplexJson : XmlObjectSeria
2020

2121
internal XmlObjectSerializerReadContextComplexJson(DataContractJsonSerializer serializer, DataContract rootTypeDataContract)
2222
#pragma warning disable SYSLIB0050 // StreamingContext ctor is obsolete
23-
: base(serializer, serializer.MaxItemsInObjectGraph, new StreamingContext(StreamingContextStates.All), serializer.IgnoreExtensionDataObject)
23+
: base(serializer, serializer.MaxItemsInObjectGraph, new StreamingContext(StreamingContextStates.All), serializer.IgnoreExtensionDataObject, serializer.SerializationSurrogateProvider)
2424
#pragma warning restore SYSLIB0050
2525
{
2626
this.rootTypeDataContract = rootTypeDataContract;

src/libraries/System.Private.DataContractSerialization/src/System/Runtime/Serialization/Json/XmlObjectSerializerWriteContextComplexJson.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ internal static XmlObjectSerializerWriteContextComplexJson CreateContext(DataCon
2626

2727
internal XmlObjectSerializerWriteContextComplexJson(DataContractJsonSerializer serializer, DataContract rootTypeDataContract)
2828
#pragma warning disable SYSLIB0050 // StreamingContext ctor is obsolete
29-
: base(serializer, serializer.MaxItemsInObjectGraph, new StreamingContext(StreamingContextStates.All), serializer.IgnoreExtensionDataObject)
29+
: base(serializer, serializer.MaxItemsInObjectGraph, new StreamingContext(StreamingContextStates.All), serializer.IgnoreExtensionDataObject, serializer.SerializationSurrogateProvider)
3030
#pragma warning restore SYSLIB0050
3131
{
3232
_emitXsiType = serializer.EmitTypeInformation;

src/libraries/System.Private.DataContractSerialization/src/System/Runtime/Serialization/XmlObjectSerializerReadContextComplex.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,12 @@ internal XmlObjectSerializerReadContextComplex(XmlObjectSerializer serializer, i
2626
{
2727
}
2828

29+
internal XmlObjectSerializerReadContextComplex(XmlObjectSerializer serializer, int maxItemsInObjectGraph, StreamingContext streamingContext, bool ignoreExtensionDataObject, ISerializationSurrogateProvider? serializationSurrogateProvider)
30+
: base(serializer, maxItemsInObjectGraph, streamingContext, ignoreExtensionDataObject)
31+
{
32+
_serializationSurrogateProvider = serializationSurrogateProvider;
33+
}
34+
2935
[RequiresDynamicCode(DataContract.SerializerAOTWarning)]
3036
[RequiresUnreferencedCode(DataContract.SerializerTrimmerWarning)]
3137
internal override object? InternalDeserialize(XmlReaderDelegator xmlReader, int declaredTypeID, RuntimeTypeHandle declaredTypeHandle, string name, string ns)

src/libraries/System.Private.DataContractSerialization/src/System/Runtime/Serialization/XmlObjectSerializerWriteContextComplex.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,12 @@ internal XmlObjectSerializerWriteContextComplex(XmlObjectSerializer serializer,
3333
{
3434
}
3535

36+
internal XmlObjectSerializerWriteContextComplex(XmlObjectSerializer serializer, int maxItemsInObjectGraph, StreamingContext streamingContext, bool ignoreExtensionDataObject, ISerializationSurrogateProvider? serializationSurrogateProvider)
37+
: base(serializer, maxItemsInObjectGraph, streamingContext, ignoreExtensionDataObject)
38+
{
39+
_serializationSurrogateProvider = serializationSurrogateProvider;
40+
}
41+
3642
internal override bool WriteClrTypeInfo(XmlWriterDelegator xmlWriter, DataContract dataContract)
3743
{
3844
return false;

src/libraries/System.Runtime.Serialization.Json/tests/DataContractJsonSerializer.cs

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3123,4 +3123,69 @@ private static string GetAmString(DateTimeFormat dateTimeFormat)
31233123
{
31243124
return ((CultureInfo)dateTimeFormat.FormatProvider).DateTimeFormat.AMDesignator;
31253125
}
3126+
3127+
// Test for the fix to ensure DataContractJsonSerializer passes ISerializationSurrogateProvider to internal XML serializer
3128+
// Same tests used for the regular DataContractSerializer to ensure the surrogate provider is working correctly
3129+
[Fact]
3130+
public static void DCJS_MyPersonSurrogate()
3131+
{
3132+
DataContractJsonSerializer dcjs = new DataContractJsonSerializer(typeof(Family));
3133+
dcjs.SetSerializationSurrogateProvider(new MyPersonSurrogateProvider());
3134+
MemoryStream ms = new MemoryStream();
3135+
Family myFamily = new Family
3136+
{
3137+
Members = new NonSerializablePerson[]
3138+
{
3139+
new NonSerializablePerson("John", 34),
3140+
new NonSerializablePerson("Jane", 32),
3141+
new NonSerializablePerson("Bob", 5),
3142+
}
3143+
};
3144+
dcjs.WriteObject(ms, myFamily);
3145+
ms.Position = 0;
3146+
var newFamily = (Family)dcjs.ReadObject(ms);
3147+
Assert.Equal(myFamily.Members.Length, newFamily.Members.Length);
3148+
for (int i = 0; i < myFamily.Members.Length; ++i)
3149+
{
3150+
Assert.Equal(myFamily.Members[i].Name, newFamily.Members[i].Name);
3151+
Assert.Equal(myFamily.Members[i].Age, newFamily.Members[i].Age);
3152+
}
3153+
}
3154+
3155+
[Fact]
3156+
public static void DCJS_FileStreamSurrogate()
3157+
{
3158+
using (var testFile = TempFile.Create())
3159+
{
3160+
const string TestFileData = "Some data for data contract surrogate test";
3161+
3162+
// Create the serializer and specify the surrogate
3163+
var dcjs = new DataContractJsonSerializer(typeof(MyFileStream));
3164+
dcjs.SetSerializationSurrogateProvider(MyFileStreamSurrogateProvider.Singleton);
3165+
3166+
// Create and initialize the stream
3167+
byte[] serializedStream;
3168+
3169+
// Serialize the stream
3170+
using (var stream1 = new MyFileStream(testFile.Path))
3171+
{
3172+
stream1.WriteLine(TestFileData);
3173+
using (var memoryStream = new MemoryStream())
3174+
{
3175+
dcjs.WriteObject(memoryStream, stream1);
3176+
serializedStream = memoryStream.ToArray();
3177+
}
3178+
}
3179+
3180+
// Deserialize the stream
3181+
using (var stream = new MemoryStream(serializedStream))
3182+
{
3183+
using (var stream2 = (MyFileStream)dcjs.ReadObject(stream))
3184+
{
3185+
string fileData = stream2.ReadLine();
3186+
Assert.Equal(TestFileData, fileData);
3187+
}
3188+
}
3189+
}
3190+
}
31263191
}

src/libraries/System.Runtime.Serialization.Json/tests/ReflectionOnly/System.Runtime.Serialization.Json.ReflectionOnly.Tests.csproj

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,11 @@
77
<EnableAotAnalyzer>false</EnableAotAnalyzer>
88
</PropertyGroup>
99
<ItemGroup>
10-
<Compile Include="$(CommonTestPath)System\Runtime\Serialization\Utils.cs" />
1110
<Compile Include="$(TestSourceFolder)..\..\..\System.Runtime.Serialization.Xml\tests\SerializationTypes.cs" />
1211
<Compile Include="$(TestSourceFolder)..\..\..\System.Runtime.Serialization.Xml\tests\SerializationTypes.RuntimeOnly.cs" />
1312
<Compile Include="$(TestSourceFolder)..\DataContractJsonSerializer.cs" />
13+
<Compile Include="$(CommonTestPath)System\IO\TempFile.cs" Link="Common\System\IO\TempFile.cs" />
14+
<Compile Include="$(CommonTestPath)System\Runtime\Serialization\Utils.cs" />
1415

1516
<TrimmerRootDescriptor Include="$(ILLinkDescriptorsPath)ILLink.Descriptors.Serialization.xml" />
1617
</ItemGroup>

src/libraries/System.Runtime.Serialization.Json/tests/System.Runtime.Serialization.Json.Tests.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
<Compile Include="$(TestSourceFolder)DataContractJsonSerializer.cs" />
1010
<Compile Include="$(TestSourceFolder)..\..\System.Runtime.Serialization.Xml\tests\SerializationTypes.cs" />
1111
<Compile Include="$(TestSourceFolder)..\..\System.Runtime.Serialization.Xml\tests\SerializationTypes.RuntimeOnly.cs" />
12+
<Compile Include="$(CommonTestPath)System\IO\TempFile.cs" Link="Common\System\IO\TempFile.cs" />
1213
<Compile Include="$(CommonTestPath)System\Runtime\Serialization\Utils.cs" />
1314

1415
<TrimmerRootDescriptor Include="$(ILLinkDescriptorsPath)ILLink.Descriptors.Serialization.xml" />

src/libraries/System.Runtime.Serialization.Xml/tests/DataContractSerializer.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4216,6 +4216,7 @@ public static void DCS_MyPersonSurrogate()
42164216
for (int i = 0; i < myFamily.Members.Length; ++i)
42174217
{
42184218
Assert.Equal(myFamily.Members[i].Name, newFamily.Members[i].Name);
4219+
Assert.Equal(myFamily.Members[i].Age, newFamily.Members[i].Age);
42194220
}
42204221
}
42214222

0 commit comments

Comments
 (0)