External types
Sometimes you need to serialize or deserialize a type you don’t control or can’t modify. In these cases, Serde uses a “proxy type” to stand-in for the external type. To create a proxy type, simply create a new class and add the attribute [GenerateSerde(ForType = typeof(ExternalType)]. Serde will automatically use the public properties and fields on the external type if the proxy type is empty. Here’s a simple example that assumes there’s an external Response record that you can’t modify.
using System;
using Serde;
using Serde.Json;
namespace ExternalTypesSample;
// Pretend that Response is an external type that we can't modify directly
public record Response(string ResponseType, string Body);
// Proxy for the Response type.
// We use the [GenerateSerde] attribute with the `ForType` parameter to control
// generation for the proxy type. Since the ResponseProxy type is empty, Serde
// will assume that we want to automatically use all the public properties and
// fields of the Response type, with no further customization.
[GenerateSerde(ForType = typeof(Response))]
partial class ResponseProxy;
public class Sample
{
public static void Run()
{
var resp = new Response(ResponseType: "success", Body: "hello, world");
Console.WriteLine($"Original version: {resp}");
// Serialize the Response to a JSON string
// In addition to the Response type, we also have to pass in the proxy type
var json = JsonSerializer.Serialize<Response, ResponseProxy>(resp);
Console.WriteLine($"Serialized version: {json}");
// Deserialize the JSON string back to a Response object
var deResp = JsonSerializer.Deserialize<Response, ResponseProxy>(json);
Utils.AssertEq(resp, deResp);
Console.WriteLine($"Deserialized version: {deResp}");
}
}
Types that need conversion
An empty proxy works when the external type has a parameterless constructor (or a
matching primary constructor) so Serde can construct it during deserialization.
Some external types don’t fit that mold — for example, a BCL type like
System.Version ships without any Serde support, can’t be annotated with
[GenerateSerde], and has no parameterless constructor. For those, write a
non-empty proxy that mirrors the data you want on the wire, and provide explicit
conversion operators in both directions:
ExternalType -> Proxy, used when serializing.Proxy -> ExternalType, used when deserializing.
Serde generates the serialization against the proxy’s own fields and uses the operators to convert to and from the external type at the call site.
using System;
using Serde;
using Serde.Json;
namespace VersionProxySample;
// System.Version is an external BCL type: we can't add [GenerateSerde] to it, and it has no
// parameterless constructor, so an *empty* proxy won't work. Instead we write a *non-empty*
// proxy that mirrors the data we want on the wire and provide explicit conversion operators
// in both directions. Serde generates serialization against the proxy's own fields and uses
// the operators to convert to and from Version at the call site:
// Version -> VersionProxy (used when serializing)
// VersionProxy -> Version (used when deserializing)
[GenerateSerde(ForType = typeof(Version))]
partial struct VersionProxy
{
public int Major;
public int Minor;
public int Build;
public int Revision;
public static explicit operator Version(VersionProxy p)
=> new Version(p.Major, p.Minor, p.Build, p.Revision);
public static explicit operator VersionProxy(Version v)
=> new VersionProxy { Major = v.Major, Minor = v.Minor, Build = v.Build, Revision = v.Revision };
}
public static class Sample
{
public static void Run()
{
var version = new Version(1, 2, 3, 4);
Console.WriteLine($"Original version: {version}");
// Serialize the Version, going through the Version -> VersionProxy conversion.
// produces: {"major":1,"minor":2,"build":3,"revision":4}
var json = JsonSerializer.Serialize<Version, VersionProxy>(version);
Console.WriteLine($"Serialized version: {json}");
// Deserialize, going through the VersionProxy -> Version conversion.
var deVersion = JsonSerializer.Deserialize<Version, VersionProxy>(json);
Utils.AssertEq(version, deVersion);
Console.WriteLine($"Deserialized version: {deVersion}");
}
}
Serializing a Version then produces JSON from the proxy’s fields:
{"major":1,"minor":2,"build":3,"revision":4}
Note that the proxy only needs the conversion operators for the directions you
actually generate. A serialize-only proxy ([GenerateSerialize(ForType = ...)])
needs only ExternalType -> Proxy, and a deserialize-only proxy
([GenerateDeserialize(ForType = ...)]) needs only Proxy -> ExternalType.