Farse 0.2.2
See the version list below for details.
dotnet add package Farse --version 0.2.2
NuGet\Install-Package Farse -Version 0.2.2
<PackageReference Include="Farse" Version="0.2.2" />
<PackageVersion Include="Farse" Version="0.2.2" />
<PackageReference Include="Farse" />
paket add Farse --version 0.2.2
#r "nuget: Farse, 0.2.2"
#:package Farse@0.2.2
#addin nuget:?package=Farse&version=0.2.2
#tool nuget:?package=Farse&version=0.2.2
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.2, macOS Sequoia 15.5 (24F74) [Darwin 24.5.0]
Apple M1 Pro, 1 CPU, 8 logical and 8 physical cores
.NET SDK 9.0.203
[Host] : .NET 9.0.4 (9.0.425.16305), Arm64 RyuJIT AdvSIMD DEBUG
DefaultJob : .NET 9.0.4 (9.0.425.16305), Arm64 RyuJIT AdvSIMD
| Method | Mean | Ratio | Gen0 | Gen1 | Allocated | Alloc Ratio |
|----------------------- |----------:|------:|-------:|-------:|----------:|------------:|
| System.Text.Json | 3.718 us | 0.76 | 0.1106 | - | 696 B | 0.22 |
| System.Text.Json* | 4.717 us | 0.97 | 0.4044 | 0.0153 | 2562 B | 0.80 |
| Farse | 4.862 us | 1.00 | 0.5112 | - | 3208 B | 1.00 |
| Newtonsoft.Json* | 6.126 us | 1.26 | 1.5182 | 0.0229 | 9544 B | 2.98 |
| Thoth.System.Text.Json | 8.051 us | 1.66 | 1.5717 | 0.0153 | 9944 B | 3.10 |
| Newtonsoft.Json | 9.990 us | 2.05 | 2.8229 | 0.1373 | 17720 B | 5.52 |
| Thoth.Json.Net | 10.304 us | 2.12 | 3.3569 | 0.1526 | 21136 B | 6.59 |
* Serialization
Example
Given the following JSON string.
{
"id": "c8eae96a-025d-4bc9-88f8-f204e95f2883",
"name": "Alice",
"age": null,
"email": "alice@domain.com",
"profiles": [
"01458283-b6e3-4ae7-ae54-a68eb587cdc0",
"bf00d1e2-ee53-4969-9507-86bed7e96432",
"927eb20f-cd62-470c-aafc-c3ce6b9248b0"
],
"subscription": {
"plan": "Pro",
"isCanceled": true,
"renewsAt": null
}
}
And the two (optional) operators.
// Parses a required property.
let (&=) = Parse.req
// Parses an optional property.
let (?=) = Parse.opt
We can create a parser.
open Farse
open Farse.Operators
module User =
open Parse
let parser =
parser {
let! id = "id" &= UserId.parser
let! name = "name" &= string
let! age = "age" ?= Age.parser
let! email = "email" &= Email.parser
let! profiles = "profiles" &= set ProfileId.parser
// Inlined parser example.
let! subscription = "subscription" &= parser {
let! plan = "plan" &= Plan.parser
let! isCanceled = "isCanceled" &= bool
let! renewsAt = "renewsAt" ?= dateTime
return {
Plan = plan
IsCanceled = isCanceled
RenewsAt = renewsAt
}
}
// Simple "path" example, which can be very useful
// when we just want to parse a (few) nested value(s).
let! _isCanceled = "subscription.isCanceled" &= bool
return {
Id = id
Name = name
Age = age
Email = email
Profiles = profiles
Subscription = subscription
}
}
With the following types.
open Farse
type UserId = UserId of Guid
module UserId =
let asString (UserId x) =
string x
let parser =
Parse.guid
|> Parser.map UserId
type Age = Age of byte
module Age =
let asByte (Age x) = x
let fromByte age =
match age with
| age when age >= 12uy -> Ok <| Age age
| age -> Error $"Invalid age: %u{age}."
let parser =
Parse.byte
|> Parser.validate fromByte
type Email = Email of string
module Email =
let asString (Email x) = x
let fromString str =
// Some validation.
Ok <| Email str
let parser =
Parse.string
|> Parser.validate fromString
type ProfileId = ProfileId of Guid
module ProfileId =
let asString (ProfileId x) =
string x
let parser =
Parse.guid
|> Parser.map ProfileId
type Plan =
| Pro
| Standard
| Free
module Plan =
let fromString = function
| "Pro" -> Ok Pro
| "Standard" -> Ok Standard
| "Free" -> Ok Free
| invalid -> Error $"Invalid plan: %s{invalid}."
let asString = function
| Pro -> "Pro"
| Standard -> "Standard"
| Free -> "Free"
let parser =
Parse.string
|> Parser.validate fromString
type Subscription = {
Plan: Plan
IsCanceled: bool
RenewsAt: DateTime 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
Creating JSON
We can also create JSON strings with the Json type.
Note: JNum has overloads. Use JNum.int if you want to be more explicit.
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
]
]
This creates an object, but you can create any type and convert it with Json.asString.
Errors
More examples can be found here.
Error: Could not parse property 'prop'.
Expected: Number, actual: String.
Object:
{
"prop": "1"
}
Farse doesn't throw exceptions and only catches JsonException, that is thrown when parsing invalid JSON.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net6.0 is compatible. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 is compatible. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. 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. |
-
net6.0
- FSharp.Core (>= 6.0.0)
- System.Text.Json (>= 8.0.6)
-
net7.0
- FSharp.Core (>= 6.0.0)
- System.Text.Json (>= 8.0.6)
-
net8.0
- FSharp.Core (>= 6.0.0)
- System.Text.Json (>= 8.0.6)
-
net9.0
- FSharp.Core (>= 6.0.0)
- System.Text.Json (>= 8.0.6)
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 | 195 | 11/9/2025 |
| 0.5.2 | 162 | 10/5/2025 |
| 0.5.1 | 166 | 9/25/2025 |
| 0.5.0 | 221 | 9/21/2025 |
| 0.4.0 | 147 | 9/13/2025 |
| 0.3.0 | 213 | 8/27/2025 |
| 0.2.7 | 81 | 8/23/2025 |
| 0.2.6 | 148 | 8/19/2025 |
| 0.2.5 | 125 | 8/17/2025 |
| 0.2.4 | 172 | 8/13/2025 |
| 0.2.3 | 162 | 8/8/2025 |
| 0.2.2 | 257 | 8/5/2025 |
| 0.2.1 | 208 | 8/4/2025 |
| 0.2.0 | 151 | 7/31/2025 |
| 0.1.9-alpha | 159 | 7/27/2025 |
| 0.1.8-alpha | 105 | 7/18/2025 |
| 0.1.7-alpha | 157 | 7/9/2025 |
| 0.1.6-alpha | 115 | 7/4/2025 |
| 0.1.5-alpha | 121 | 6/20/2025 |
| 0.1.4-alpha | 168 | 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 |