Farse 0.6.0
dotnet add package Farse --version 0.6.0
NuGet\Install-Package Farse -Version 0.6.0
<PackageReference Include="Farse" Version="0.6.0" />
<PackageVersion Include="Farse" Version="0.6.0" />
<PackageReference Include="Farse" />
paket add Farse --version 0.6.0
#r "nuget: Farse, 0.6.0"
#:package Farse@0.6.0
#addin nuget:?package=Farse&version=0.6.0
#tool nuget:?package=Farse&version=0.6.0
Farse
Simple parsing library for F# using System.Text.Json.
Inspired by Thoth.Json and its composability.
Farse uses a slightly different syntax, includes a computation expression, and a few custom operators that simplify parsing. It also tries to keep a low overhead while still producing acceptable error messages.
Installation
dotnet add package Farse
Benchmarks
There are some initial benchmarks here.
BenchmarkDotNet v0.15.3, macOS 26.0 (25A354) [Darwin 25.0.0]
Apple M1 Pro, 1 CPU, 8 logical and 8 physical cores
.NET SDK 9.0.305
[Host] : .NET 9.0.9 (9.0.9, 9.0.925.41916), Arm64 RyuJIT armv8.0-a DEBUG
DefaultJob : .NET 9.0.9 (9.0.9, 9.0.925.41916), Arm64 RyuJIT armv8.0-a
| Method | Mean | Ratio | Gen0 | Gen1 | Allocated | Alloc Ratio |
|----------------------- |---------:|------:|--------:|--------:|----------:|------------:|
| System.Text.Json | 109.1 us | 0.83 | 4.1504 | - | 25.85 KB | 0.60 |
| System.Text.Json* | 112.1 us | 0.86 | 13.0615 | 1.7090 | 80.19 KB | 1.86 |
| Farse | 130.9 us | 1.00 | 6.8359 | - | 43.02 KB | 1.00 |
| Thoth.System.Text.Json | 247.5 us | 1.89 | 55.1758 | 18.0664 | 338.76 KB | 7.87 |
| Newtonsoft.Json* | 248.1 us | 1.89 | 58.5938 | 5.8594 | 365.01 KB | 8.48 |
| Newtonsoft.Json | 274.0 us | 2.09 | 75.6836 | 22.9492 | 464.07 KB | 10.79 |
| Thoth.Json.Net | 365.5 us | 2.79 | 94.7266 | 44.9219 | 581.86 KB | 13.52 |
* Serialization
Example
The complete example can be found here.
Given the JSON string.
{
"id": "c8eae96a-025d-4bc9-88f8-f204e95f2883",
"name": "Alice",
"age": null,
"email": "alice@domain.com",
"profiles": [
"01458283-b6e3-4ae7-ae54-a68eb587cdc0",
"927eb20f-cd62-470c-aafc-c3ce6b9248b0",
"bf00d1e2-ee53-4969-9507-86bed7e96432"
],
"subscription": {
"plan": "Pro",
"isCanceled": false,
"renewsAt": "2026-12-25T10:30:00Z"
}
}
And the two (optional) operators.
// Parses a required property.
let (&=) = Parse.req
// Parses an optional property.
let (?=) = Parse.opt
We can create a simple parser.
open Farse
open Farse.Operators
module User =
open Parse
let parser =
parser {
let! id = "id" &= guid |> Parser.map UserId
and! name = "name" &= string
and! age = "age" ?= valid byte Age.fromByte
and! email = "email" &= valid string Email.fromString
and! profiles = "profiles" &= set profileId // Custom parser example.
// Inlined parser example.
and! subscription = "subscription" &= parser {
let! plan = "plan" &= valid string Plan.fromString
and! isCanceled = "isCanceled" &= bool
and! renewsAt = "renewsAt" ?= instant // Custom parser example.
return {
Plan = plan
IsCanceled = isCanceled
RenewsAt = renewsAt
}
}
// "Path" example, which can be very useful
// when we just want to parse a (few) nested value(s).
and! _isCanceled = "subscription.isCanceled" &= bool
return {
Id = id
Name = name
Age = age
Email = email
Profiles = profiles
Subscription = subscription
}
}
Note: In this example, our parsers are defined under the same module name as included parsers.
With the following types.
type UserId = UserId of Guid
module UserId =
let asString (UserId x) =
string x
type Age = Age of byte
module Age =
let fromByte = function
| age when age >= 12uy -> Ok <| Age age
| age -> Error $"Invalid age '%u{age}'."
let asByte (Age x) = x
type Email = Email of string
module Email =
let fromString =
// Some validation.
Email >> Ok
let asString (Email x) = x
type ProfileId = ProfileId of Guid
module ProfileId =
let asString (ProfileId x) =
string x
type Plan =
| Pro
| Standard
| Free
module Plan =
let fromString = function
| "Pro" -> Ok Pro
| "Standard" -> Ok Standard
| "Free" -> Ok Free
| str -> Error $"Invalid plan '%s{str}'."
let asString = function
| Pro -> "Pro"
| Standard -> "Standard"
| Free -> "Free"
type Subscription = {
Plan: Plan
IsCanceled: bool
RenewsAt: Instant option
}
type User = {
Id: UserId
Name: string
Age: Age option
Email: Email
Profiles: ProfileId Set
Subscription: Subscription
}
Then we can just run the parser.
let user =
User.parser
|> Parser.parse json
|> Result.defaultWith failwith
printf "%s" user.Name
Custom parsers
Parse.custom can be used to build parsers for third-party types, or to just avoid unnecessary operations.
open Farse
module Parse =
let profileId =
Parse.custom (fun element ->
match element.TryGetGuid() with
| true, guid -> Ok <| ProfileId guid
| _ -> Error None // No details.
) ExpectedKind.String
let instant =
Parse.custom (fun element ->
let string = element.GetString()
match InstantPattern.General.Parse(string) with
| result when result.Success -> Ok result.Value
| result -> Error <| Some result.Exception.Message // Added as details.
) ExpectedKind.String
Note: Custom parsers produce detailed error messages when validation fails, see errors.
Creating JSON
We can create JSON strings with Json.
This creates an object, but you can create any type and convert it with Json.asString.
open Farse
module User =
let asJson user =
JObj [
"id", JStr <| UserId.asString user.Id
"name", JStr user.Name
"age",
user.Age
|> Option.map (Age.asByte >> JNum)
|> JNil
"email", JStr <| Email.asString user.Email
"profiles",
user.Profiles
|> Seq.map (ProfileId.asString >> JStr)
|> JArr
"subscription",
JObj [
"plan", JStr <| Plan.asString user.Subscription.Plan
"isCanceled", JBit user.Subscription.IsCanceled
"renewsAt",
user.Subscription.RenewsAt
|> Option.map (_.ToString() >> JStr)
|> JNil
]
]
Note: Use JNum<'a> to be explicit.
Errors
More examples can be found here.
Object
Error: Could not parse property 'renewsAt'.
Message: Tried parsing '202612-25T10:30:00Z' to Instant. Details: The value string [...]
Object:
{
"plan": "Pro",
"isCanceled": false,
"renewsAt": "202612-25T10:30:00Z"
}
Array
Error: Could not parse property 'profiles[1]'.
Message: Tried parsing '927eb20f-cd62-470c-aafc-c3ce6b9248' to ProfileId.
Array:
[
"01458283-b6e3-4ae7-ae54-a68eb587cdc0",
"927eb20f-cd62-470c-aafc-c3ce6b9248",
"bf00d1e2-ee53-4969-9507-86bed7e9643c"
]
Note: Farse does not throw exceptions unless something unexpected occurs.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net8.0
- FSharp.Core (>= 8.0.100)
- System.Text.Json (>= 9.0.0)
-
net9.0
- FSharp.Core (>= 8.0.100)
- System.Text.Json (>= 9.0.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 0.6.0 | 187 | 11/9/2025 |
| 0.5.2 | 161 | 10/5/2025 |
| 0.5.1 | 165 | 9/25/2025 |
| 0.5.0 | 220 | 9/21/2025 |
| 0.4.0 | 146 | 9/13/2025 |
| 0.3.0 | 212 | 8/27/2025 |
| 0.2.7 | 78 | 8/23/2025 |
| 0.2.6 | 147 | 8/19/2025 |
| 0.2.5 | 124 | 8/17/2025 |
| 0.2.4 | 171 | 8/13/2025 |
| 0.2.3 | 161 | 8/8/2025 |
| 0.2.2 | 256 | 8/5/2025 |
| 0.2.1 | 206 | 8/4/2025 |
| 0.2.0 | 149 | 7/31/2025 |
| 0.1.9-alpha | 158 | 7/27/2025 |
| 0.1.8-alpha | 103 | 7/18/2025 |
| 0.1.7-alpha | 154 | 7/9/2025 |
| 0.1.6-alpha | 113 | 7/4/2025 |
| 0.1.5-alpha | 120 | 6/20/2025 |
| 0.1.4-alpha | 167 | 6/18/2025 |
| 0.1.3-alpha | 137 | 5/30/2025 |
| 0.1.2-alpha | 193 | 5/16/2025 |
| 0.1.1-alpha | 185 | 5/16/2025 |
| 0.1.0-alpha | 192 | 5/16/2025 |