Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
124 changes: 124 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
# Changelog

All notable changes to this project are documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

This changelog was reconstructed from the project's commit history and published
NuGet releases; entries for older versions are a best-effort summary.

## [4.2.0] - 2026-06-16

### Added
- Option to pass a custom `ISerializationSurrogateProvider` to the serializers via the new
`DataSerializer.SerializationSurrogateProvider` property, allowing callers to customize
serialization and add support for otherwise unsupported types ([#178]).

### Notes
- The surrogate provider is honored by the XML serializer only. The JSON serializer
(`DataContractJsonSerializer`) does not apply surrogate providers
([dotnet/runtime#100553](https://github.com/dotnet/runtime/issues/100553)).
- The property is available on the `net6.0`–`net10.0` and `netstandard2.1` targets;
it is not exposed on `net48`/`net481`/`netstandard2.0`, which lack
`ISerializationSurrogateProvider`.

## [4.1.0] - 2026-06-16

### Added
- Support for .NET 10 (`net10.0`).

### Changed
- Replaced AppVeyor with GitHub Actions for CI; publishing now uses NuGet trusted
publishing (OIDC) instead of a stored API key.
- Added Dependabot configuration for NuGet updates.
- Documentation updates (README supported platforms, project guidance).

## [4.0.0] - 2025-01-19

### Added
- Support for .NET 9 (`net9.0`).

## [3.1.0] - 2023-10-21

### Added
- Support for .NET 8 (`net8.0`) and .NET Framework 4.8.1 (`net481`).
- `DateOnly` and `TimeOnly` added to the known types.

### Fixed
- Serialization of unbound `ToString` expressions ([#169]).

### Removed
- Dropped .NET 5 target (out of support).

## [3.0.0] - 2023-06-10

### Added
- Support for .NET 7 (`net7.0`).
- `netstandard2.0` and `netstandard2.1` targets.
- README is now included in the NuGet package.

### Changed
- Suppressed `SYSLIB0011` warnings during the transition away from `BinaryFormatter`.

### Fixed
- Do not explode interface types as nullable types ([#163]).

### Removed
- Removed `BinaryFormatSerializer` / `BinaryFormatter`-based serialization due to
security concerns.

## [2.0.0] - 2020-12-12

### Added
- Support for .NET 5 (`net5.0`).
- Symbol package (`.snupkg`) support.

### Changed
- License changed to MIT; removed legacy copyright/license headers.
- Added support for `DefaultExpression` ([#146]).
- `DateTimeOffset` added to the known types ([#107]).

### Removed
- Removed deprecated target frameworks and example projects.

## [1.8.1] - 2019-04-25

### Fixed
- Follow-up fix for finding the real constant value ([#113]).
- Support for `IndexExpression` serialization ([#119]).

## [1.8.0] - 2019-04-25

### Fixed
- Try harder to find the real constant value ([#113]).

## [1.7.3] - 2018-10-26

### Fixed
- Fix for member access on constant values ([#105]).
- Removed obsolete Silverlight/Windows Phone compiler switches.

## [1.7.2] - 2018-10-26

### Added
- `DateTimeOffset` added as a known type ([#107]).

## [1.7.1] - 2018-06-02

### Fixed
- Updated `Microsoft.Data.OData` reference (security alert).

## [1.7.0] - 2018-03-20

- Baseline release.

[Unreleased]: https://github.com/esskar/Serialize.Linq/compare/main...HEAD
[#178]: https://github.com/esskar/Serialize.Linq/issues/178
[#169]: https://github.com/esskar/Serialize.Linq/issues/169
[#163]: https://github.com/esskar/Serialize.Linq/pull/163
[#146]: https://github.com/esskar/Serialize.Linq/issues/146
[#119]: https://github.com/esskar/Serialize.Linq/issues/119
[#113]: https://github.com/esskar/Serialize.Linq/issues/113
[#107]: https://github.com/esskar/Serialize.Linq/issues/107
[#105]: https://github.com/esskar/Serialize.Linq/issues/105
76 changes: 76 additions & 0 deletions src/Serialize.Linq.Tests/Issues/Issue178.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
using System;
using System.Linq.Expressions;
using System.Runtime.Serialization;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Serialize.Linq.Serializers;

namespace Serialize.Linq.Tests.Issues
{
/// <summary>
/// https://github.com/esskar/Serialize.Linq/issues/178
/// Adds the ability to pass an <see cref="ISerializationSurrogateProvider"/> to the underlying
/// data contract serializer so callers can customize serialization / add support for unknown types.
///
/// Note: the JSON serializer does not honor the surrogate provider on all runtimes
/// (see https://github.com/dotnet/runtime/issues/100553), so these tests exercise the XML serializer.
/// </summary>
[TestClass]
public class Issue178
{
/// <summary>
/// A surrogate provider that does not substitute anything but records that it was queried,
/// proving the provider is actually wired into the data contract serializer.
/// </summary>
private sealed class RecordingSurrogateProvider : ISerializationSurrogateProvider
{
public int GetSurrogateTypeCalls { get; private set; }

public Type GetSurrogateType(Type type)
{
GetSurrogateTypeCalls++;
return type;
}

public object GetObjectToSerialize(object obj, Type targetType) => obj;

public object GetDeserializedObject(object obj, Type targetType) => obj;
}

[TestMethod]
public void SurrogateProviderIsInvokedDuringXmlRoundTrip()
{
var provider = new RecordingSurrogateProvider();
var xmlSerializer = new XmlSerializer
{
SerializationSurrogateProvider = provider
};
var serializer = new ExpressionSerializer(xmlSerializer);

Expression<Func<int, bool>> expression = x => x > 5;

var text = serializer.SerializeText(expression);
var actual = (Expression<Func<int, bool>>)serializer.DeserializeText(text);

Assert.IsTrue(provider.GetSurrogateTypeCalls > 0, "surrogate provider was not invoked.");

var func = actual.Compile();
Assert.IsTrue(func(6));
Assert.IsFalse(func(4));
}

[TestMethod]
public void NoSurrogateProviderStillRoundTrips()
{
var serializer = new ExpressionSerializer(new XmlSerializer());

Expression<Func<int, bool>> expression = x => x > 5;

var text = serializer.SerializeText(expression);
var actual = (Expression<Func<int, bool>>)serializer.DeserializeText(text);

var func = actual.Compile();
Assert.IsTrue(func(6));
Assert.IsFalse(func(4));
}
}
}
6 changes: 3 additions & 3 deletions src/Serialize.Linq/Serialize.Linq.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,9 @@

<PropertyGroup>
<TargetFrameworks>net48;net481;net6.0;net7.0;net8.0;net9.0;net10.0;netstandard2.0;netstandard2.1</TargetFrameworks>
<Version>4.1.0</Version>
<AssemblyVersion>4.1.0.0</AssemblyVersion>
<FileVersion>4.1.0.0</FileVersion>
<Version>4.2.0</Version>
<AssemblyVersion>4.2.0.0</AssemblyVersion>
<FileVersion>4.2.0.0</FileVersion>
<PackageLicenseFile>LICENSE</PackageLicenseFile>
</PropertyGroup>

Expand Down
35 changes: 32 additions & 3 deletions src/Serialize.Linq/Serializers/DataSerializer.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System;
using System;
using System.IO;
using Serialize.Linq.Nodes;
using System.Runtime.Serialization;
Expand All @@ -8,12 +8,29 @@ namespace Serialize.Linq.Serializers
{
public abstract class DataSerializer : SerializerBase, ISerializer
{
#if NETSTANDARD2_1 || NET6_0_OR_GREATER
/// <summary>
/// Gets or sets a custom <see cref="ISerializationSurrogateProvider"/> that is attached to the
/// underlying data contract serializer. A surrogate provider lets callers substitute types during
/// serialization and deserialization, for example to add support for types that the data contract
/// serializer cannot handle on its own.
/// </summary>
/// <remarks>
/// This property is not available on the .NET Framework / netstandard2.0 targets, which do not
/// expose <see cref="ISerializationSurrogateProvider"/>.
/// Note that the JSON serializer does not honor the surrogate provider on all runtimes
/// (see https://github.com/dotnet/runtime/issues/100553); prefer the XML serializer when a
/// surrogate provider is required.
/// </remarks>
public ISerializationSurrogateProvider SerializationSurrogateProvider { get; set; }
#endif

public virtual void Serialize<T>(Stream stream, T obj) where T : Node
{
if (stream == null)
throw new ArgumentNullException(nameof(stream));

var serializer = CreateXmlObjectSerializer(typeof(T));
var serializer = CreateSerializer(typeof(T));
serializer.WriteObject(stream, obj);
}

Expand All @@ -22,10 +39,22 @@ public virtual T Deserialize<T>(Stream stream) where T : Node
if (stream == null)
throw new ArgumentNullException(nameof(stream));

var serializer = CreateXmlObjectSerializer(typeof(T));
var serializer = CreateSerializer(typeof(T));
return (T)serializer.ReadObject(stream);
}

private XmlObjectSerializer CreateSerializer(Type type)
{
var serializer = CreateXmlObjectSerializer(type);
#if NETSTANDARD2_1 || NET6_0_OR_GREATER
// SetSerializationSurrogateProvider is only available on DataContractSerializer (XML).
// DataContractJsonSerializer does not honor a surrogate provider (dotnet/runtime#100553).
if (SerializationSurrogateProvider != null && serializer is DataContractSerializer dataContractSerializer)
dataContractSerializer.SetSerializationSurrogateProvider(SerializationSurrogateProvider);
#endif
return serializer;
}

protected abstract XmlObjectSerializer CreateXmlObjectSerializer(Type type);
}
}
Loading