SkillAgentSearch skills...

FsCodec

F# Event-Union Contract Encoding with versioning tolerant converters supporting System.Text.Json and Newtonsoft.Json

Install / Use

/learn @jet/FsCodec

README

FsCodec Build Status release NuGet license

Defines a minimal interface for serialization and deserialization of events for event-sourcing systems on .NET. Provides implementation packages for writing simple yet versionable Event Contract definitions in F# using ubiquitous serializers.

Typically used in applications leveraging Equinox and/or Propulsion, but also applicable to defining DTOs for other purposes such as Web APIs.

Components

The components within this repository are delivered as multi-targeted Nuget packages supporting netstandard2.1 (F# 4.5+) profiles.

  • Codec NuGet FsCodec Defines interfaces with trivial implementation helpers.
    • No dependencies.
    • FsCodec.IEventCodec: defines a base interface for serializers.
    • FsCodec.Codec: enables plugging in custom serialization (a trivial implementation of the interface that simply delegates to a pair of encode and decode functions you supply)
    • FsCodec.StreamName: strongly-typed wrapper for a Stream Name, together with factory functions and active patterns for parsing same
    • FsCodec.StreamId: strongly-typed wrapper for a Stream Id, together with factory functions and active patterns for parsing same
  • Box Codec NuGet FsCodec.Box: See FsCodec.Box.Codec; IEventCodec<obj> implementation that provides a null encode/decode step in order to enable decoupling of serialization/deserialization concerns from the encoding aspect, typically used together with Equinox.MemoryStore
    • depends on FsCodec, TypeShape >= 10
  • Newtonsoft.Json Codec NuGet FsCodec.NewtonsoftJson: As described in a scheme for the serializing Events modelled as an F# Discriminated Union, enabled tagging of F# Discriminated Union cases in a versionable manner with low-dependencies using TypeShape's UnionContractEncoder
    • Uses the ubiquitous Newtonsoft.Json library to serialize the event bodies.
    • Provides relevant Converters for common non-primitive types prevalent in F#
    • depends on FsCodec.Box, Newtonsoft.Json >= 13.0.3, Microsoft.IO.RecyclableMemoryStream >= 3.0.0, System.Buffers >= 4.5.1
  • System.Text.Json Codec NuGet FsCodec.SystemTextJson: See #38: drop in replacement that allows one to retarget from Newtonsoft.Json to the .NET Core >= v 3.0 default serializer: System.Text.Json, solely by changing the referenced namespace.
    • depends on FsCodec.Box, System.Text.Json >= 6.0.1,

Features: FsCodec

The purpose of the FsCodec package is to provide a minimal interface on which libraries such as Equinox and Propulsion can depend on in order that they can avoid forcing a specific serialization mechanism.

  • FsCodec.IEventData represents a single event and/or related metadata in raw form (i.e. still as a UTF8 string etc, not yet bound to a specific Event Type)
  • FsCodec.ITimelineEvent represents a single stored event and/or related metadata in raw form (i.e. still as a UTF8 string etc, not yet bound to a specific Event Type). Inherits IEventData, adding Index and IsUnfold in order to represent the position on the timeline that the event logically occupies.
  • FsCodec.IEventCodec presents Encode: 'Context option * 'Event -> IEventData and Decode: ITimelineEvent -> 'Event voption methods that can be used in low level application code to generate IEventDatas or decode ITimelineEvents based on a contract defined by 'Union
  • FsCodec.Codec.Create implements IEventCodec in terms of supplied encode: 'Event -> string * byte[] and decode: string * byte[] -> 'Event voption functions (other overloads are available for advanced cases)
  • FsCodec.Core.EventData.Create is a low level helper to create an IEventData directly for purposes such as tests etc.
  • FsCodec.Core.TimelineEvent.Create is a low level helper to create an ITimelineEvent directly for purposes such as tests etc.

Features: FsCodec.(Newtonsoft|SystemText)Json

Common API

The concrete implementations implement common type/member/function signatures and behavior that offer consistent behavior using either Newtonsoft.Json or System.Text.Json, emphasizing the following qualities:

  • avoid non-straightforward encodings:
    • tuples don't magically become arrays
    • union bodies don't become arrays of mixed types like they do OOTB in JSON.NET (they become JSON Objects with named fields via UnionEncoder, or string values via TypeSafeEnumConverter)
  • don't surprise .NET developers used to JSON.NET or System.Text.Json
  • having an opinionated core set of behaviors, but don't conflict with the standard extensibility mechanisms afforded by the underlying serializer (one should be able to search up and apply answers from StackOverflow to questions regarding corner cases)
  • maintain a minimal but well formed set of built in converters that are implemented per supported serializer - e.g., choices like not supporting F# list types (although System.Text.Json v >= 6 does now provide such support)

Codec

FsCodec.NewtonsoftJson/SystemTextJson.Codec provides an implementation of IEventCodec as described in a scheme for the serializing Events modelled as an F# Discriminated Union. This yields a clean yet versionable way of managing the roundtripping events based on a contract inferred from an F# Discriminated Union Type using Newtonsoft.Json >= 13.0.3 / System.Text.Json to serialize the bodies.

Converters: Newtonsoft.Json.Converters / System.Text.Json.Serialization.JsonConverters

Explicit vs Implicit

While it's alluded to in the recommendations, it's worth calling out that the converters in FsCodec (aside from obvious exceptions like the Option and Record ones) are intended to be used by tagging the type with a JsonConverterAttribute rather than by inclusion in the global converters list of the underlying serializer.

The key effect of this is that any non-trivial mapping will manifest as the application of the relevant attribute on the type or property in question. This also aligns well with the notion of cordoning off a module Events as described in Equinox's module Aggregate documentation: types that participate in an Event union are defined and namespaced together (including any snapshot serialization contracts).

This set might be all you need ...

While this may not seem like a sufficiently large set of converters for a large app, it should be mentioned that the serializer-neutral escape hatch represented by JsonIsomorphism has resulted in this set alone proving sufficient for two major subsystems of a large e-commerce software suite. See recommendations for further expansion on this (TL;DR it does mean ruling out using some type constructs directly in event and/or binding contracts and using Anti Corruption Layer and/or event versioning techniques.

... but don't forget FSharp.SystemTextJson

The role and intention of the converters in the box in FsCodec.SystemTextJson and/or FsCodec.NewtonsoftJson has always been to be minimal but provide escape hatches; short lived shims absolutely fit within this remit. For example, with regard to System.Text.Json, over time the shimming provided has been adjusted in alignment with the STJ implementation:

  • System.Text.Json v4 did not even support F# recor
View on GitHub
GitHub Stars82
CategoryCustomer
Updated16h ago
Forks18

Languages

F#

Security Score

100/100

Audited on Mar 31, 2026

No findings