Farse 0.7.1
dotnet add package Farse --version 0.7.1
NuGet\Install-Package Farse -Version 0.7.1
<PackageReference Include="Farse" Version="0.7.1" />
<PackageVersion Include="Farse" Version="0.7.1" />
<PackageReference Include="Farse" />
paket add Farse --version 0.7.1
#r "nuget: Farse, 0.7.1"
#:package Farse@0.7.1
#addin nuget:?package=Farse&version=0.7.1
#tool nuget:?package=Farse&version=0.7.1
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 package add Farse
Benchmarks
The benchmarks can be found here.
BenchmarkDotNet v0.15.8, macOS Tahoe 26.2 (25C56) [Darwin 25.2.0]
Apple M1 Pro, 1 CPU, 8 logical and 8 physical cores
.NET SDK 10.0.100
[Host] : .NET 10.0.0 (10.0.0, 10.0.25.52411), Arm64 RyuJIT armv8.0-a DEBUG
DefaultJob : .NET 10.0.0 (10.0.0, 10.0.25.52411), Arm64 RyuJIT armv8.0-a
| Method | Mean | Ratio | Gen0 | Gen1 | Allocated | Alloc Ratio |
|----------------------- |---------:|------:|--------:|--------:|----------:|------------:|
| System.Text.Json | 108.0 us | 0.85 | 4.1504 | - | 25.85 KB | 0.60 |
| System.Text.Json* | 108.8 us | 0.86 | 12.9395 | 1.5869 | 79.97 KB | 1.86 |
| Farse | 127.1 us | 1.00 | 6.8359 | - | 43.02 KB | 1.00 |
| Newtonsoft.Json* | 193.8 us | 1.53 | 42.7246 | 5.8594 | 262.27 KB | 6.10 |
| Thoth.System.Text.Json | 215.0 us | 1.69 | 55.1758 | 18.3105 | 338.76 KB | 7.87 |
| Newtonsoft.Json | 223.3 us | 1.76 | 75.6836 | 23.9258 | 464.07 KB | 10.79 |
| Thoth.Json.Net | 308.1 us | 2.42 | 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 (&=) = Prop.req
// Parses an optional property.
let (?=) = Prop.opt
We can create this 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" ?= byte |> Parser.validate Age.fromByte
and! email = "email" &= string |> Parser.validate Email.fromString
and! profiles = "profiles" &= set profileId // Custom parser example.
// Inlined parser example.
and! subscription = "subscription" &= parser {
let! plan = "plan" &= string |> Parser.validate 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: Our custom 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 =
[<Literal>]
let private MinAge = 12uy
let fromByte = function
| age when age >= MinAge -> Ok <| Age age
| _ -> Error $"The minimum age is '%u{MinAge}'."
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 $"Plan '%s{str}' not found."
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
Validation
There are a few different options available depending on your use case.
Parser.validate
Passes along the error string from the validation function.
let! age = "age" ?= byte |> Parser.validate Age.fromByte
The minimum age is '12'.
Parse.valid
Produces detailed error messages when validation fails.
let! age = "age" ?= valid byte Age.fromByte
Error: Could not parse property 'age'.
Message: Tried parsing '10' to Age.
Details: The minimum age is '12'.
{
"id": "c8eae96a-025d-4bc9-88f8-f204e95f2883",
"name": "Alice",
"age": 10
}
Parse.custom
Produces detailed error messages when validation fails.
let! age = "age" ?= age
Error: Could not parse property 'age'.
Message: Tried parsing '10' to Age.
Details: The minimum age is '12'.
{
"id": "c8eae96a-025d-4bc9-88f8-f204e95f2883",
"name": "Alice",
"age": 10
}
Creating JSON
We can create JSON strings with the Json type.
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
|> JNum.nil Age.asByte
"email", JStr <| Email.asString user.Email
"profiles",
user.Profiles
|> JStr.arr ProfileId.asString
"subscription",
JObj [
"plan", JStr <| Plan.asString user.Subscription.Plan
"isCanceled", JBit user.Subscription.IsCanceled
"renewsAt",
user.Subscription.RenewsAt
|> JStr.nil _.ToString()
]
]
Note: Use JNum<'a> and JNum.nil<'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 does not [...]
{
"plan": "Pro",
"isCanceled": false,
"renewsAt": "202612-25T10:30:00Z"
}
Array
Error: Could not parse property 'profiles[1]'.
Message: Tried parsing '927eb20f-cd62-470c-aafc-c3ce6b9' to ProfileId.
[
"01458283-b6e3-4ae7-ae54-a68eb587cdc0",
"927eb20f-cd62-470c-aafc-c3ce6b9",
"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 is compatible. 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. |
-
net10.0
- FSharp.Core (>= 8.0.100)
-
net8.0
- FSharp.Core (>= 8.0.100)
- System.Text.Json (>= 9.0.0)
-
net9.0
- FSharp.Core (>= 8.0.100)
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.7.1 | 36 | 1/1/2026 |
| 0.7.0 | 173 | 12/22/2025 |
| 0.6.0 | 203 | 11/9/2025 |
| 0.5.2 | 165 | 10/5/2025 |
| 0.5.1 | 170 | 9/25/2025 |
| 0.5.0 | 224 | 9/21/2025 |
| 0.4.0 | 151 | 9/13/2025 |
| 0.3.0 | 217 | 8/27/2025 |
| 0.2.7 | 84 | 8/23/2025 |
| 0.2.6 | 151 | 8/19/2025 |
| 0.2.5 | 127 | 8/17/2025 |
| 0.2.4 | 174 | 8/13/2025 |
| 0.2.3 | 165 | 8/8/2025 |
| 0.2.2 | 261 | 8/5/2025 |
| 0.2.1 | 211 | 8/4/2025 |
| 0.2.0 | 154 | 7/31/2025 |
| 0.1.9-alpha | 160 | 7/27/2025 |
| 0.1.8-alpha | 106 | 7/18/2025 |
| 0.1.7-alpha | 159 | 7/9/2025 |
| 0.1.6-alpha | 118 | 7/4/2025 |
| 0.1.5-alpha | 122 | 6/20/2025 |
| 0.1.4-alpha | 170 | 6/18/2025 |
| 0.1.3-alpha | 138 | 5/30/2025 |
| 0.1.2-alpha | 195 | 5/16/2025 |
| 0.1.1-alpha | 187 | 5/16/2025 |
| 0.1.0-alpha | 195 | 5/16/2025 |