{"id":28508865,"url":"https://github.com/tommililja/farse","last_synced_at":"2025-06-25T09:06:04.650Z","repository":{"id":293816131,"uuid":"963428400","full_name":"tommililja/Farse","owner":"tommililja","description":"F# parsing library using System.Text.Json","archived":false,"fork":false,"pushed_at":"2025-06-18T21:44:24.000Z","size":35,"stargazers_count":16,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-06-18T22:30:35.760Z","etag":null,"topics":["fsharp","json","parsing"],"latest_commit_sha":null,"homepage":"","language":"F#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/tommililja.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2025-04-09T17:05:01.000Z","updated_at":"2025-06-18T21:44:27.000Z","dependencies_parsed_at":"2025-05-17T10:34:44.914Z","dependency_job_id":"f29cd058-ab7e-4cf5-8719-b6349860f314","html_url":"https://github.com/tommililja/Farse","commit_stats":null,"previous_names":["tommililja/farse"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/tommililja/Farse","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tommililja%2FFarse","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tommililja%2FFarse/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tommililja%2FFarse/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tommililja%2FFarse/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/tommililja","download_url":"https://codeload.github.com/tommililja/Farse/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tommililja%2FFarse/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":261841950,"owners_count":23217912,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["fsharp","json","parsing"],"created_at":"2025-06-08T22:00:49.278Z","updated_at":"2025-06-25T09:06:04.640Z","avatar_url":"https://github.com/tommililja.png","language":"F#","funding_links":[],"categories":[],"sub_categories":[],"readme":"![Build](https://github.com/tommililja/Farse/actions/workflows/dotnet.yml/badge.svg)\n[![NuGet Version](https://img.shields.io/nuget/v/Farse.svg)](https://www.nuget.org/packages/Farse)\n\n# Farse\n\n\u003eSimple parsing library for F# using System.Text.Json.\n\nHeavily inspired by Thoth.Json.Net and its composability; Farse uses a slightly different syntax, includes a computational expression, and a few custom operators that simplify parsing. It also tries to keep a low overhead while still providing utility and acceptable error messages.\n\n## Installation\n\nThings are still changing but a pre-release version is available.\n\n```shell\ndotnet add package Farse --prerelease\n```\n\n## Benchmarks\n\nThere are some initial benchmarks [here](https://github.com/tommililja/Farse/blob/main/src/Farse.Benchmarks/Benchmarks.fs).\n\n```shell\nBenchmarkDotNet v0.15.2, macOS Sequoia 15.5 (24F74) [Darwin 24.5.0]\nApple M1 Pro, 1 CPU, 8 logical and 8 physical cores\n.NET SDK 9.0.203\n  [Host]     : .NET 9.0.4 (9.0.425.16305), Arm64 RyuJIT AdvSIMD DEBUG\n  DefaultJob : .NET 9.0.4 (9.0.425.16305), Arm64 RyuJIT AdvSIMD\n```\n\n```shell\n| Method                    | Mean      | Ratio | Gen0   | Gen1   | Allocated | Alloc Ratio |\n|-------------------------- |----------:|------:|-------:|-------:|----------:|------------:|\n| System.Text.Json          |  3.674 us |  0.72 | 0.1106 |      - |     696 B |        0.17 |\n| 'System.Text.Json (Auto)' |  3.683 us |  0.72 | 0.4082 | 0.0076 |    2562 B |        0.64 |\n| Farse                     |  5.105 us |  1.00 | 0.6409 |      - |    4024 B |        1.00 |\n| 'Newtonsoft.Json (Auto)'  |  6.240 us |  1.22 | 1.5182 | 0.0229 |    9544 B |        2.37 |\n| Thoth.System.Text.Json    |  8.239 us |  1.61 | 1.5869 | 0.0305 |    9944 B |        2.47 |\n| Newtonsoft.Json           |  8.910 us |  1.75 | 2.8229 | 0.1373 |   17720 B |        4.40 |\n| Thoth.Json.Net            | 10.510 us |  2.06 | 3.3569 | 0.1526 |   21136 B |        5.25 |\n```\n\n## Example\n\nGiven the following JSON string.\n\n```json\n{\n    \"id\": \"c8eae96a-025d-4bc9-88f8-f204e95f2883\",\n    \"name\": \"Alice\",\n    \"age\": null,\n    \"email\": \"alice@domain.com\",\n    \"groups\": [\n        \"01458283-b6e3-4ae7-ae54-a68eb587cdc0\",\n        \"bf00d1e2-ee53-4969-9507-86bed7e96432\",\n        \"927eb20f-cd62-470c-aafc-c3ce6b9248b0\"\n    ],\n    \"subscription\": {\n        \"plan\": \"Pro\",\n        \"isCanceled\": true,\n        \"renewsAt\": null\n    }\n}\n```\n\nAnd the two (optional) operators.\n\n```fsharp\n/// \u003csummary\u003e\n/// Parses a required property.\n/// \u003c/summary\u003e\nlet (\u0026=) = Parse.req\n\n/// \u003csummary\u003e\n/// Parses an optional property.\n/// \u003c/summary\u003e\nlet (?=) = Parse.opt\n```\n\nWe can create the following parser.\n\n```fsharp\nopen Farse\nopen Farse.Operators\n\nmodule User =\n    open Parse\n\n    let parser =\n        parser {\n            let! id = \"id\" \u0026= UserId.parser\n            let! name = \"name\" \u0026= string\n            let! age = \"age\" ?= int\n            let! email = \"email\" \u0026= Email.parser\n            let! groups = \"groups\" \u0026= list GroupId.parser\n\n            let! subscription = \"subscription\" \u0026= parser {\n                let! plan = \"plan\" \u0026= Plan.parser\n                let! isCanceled = \"isCanceled\" \u0026= bool\n                let! renewsAt = \"renewsAt\" ?= dateTime\n    \n                return {\n                    Plan = plan\n                    IsCanceled = isCanceled\n                    RenewsAt = renewsAt\n                }\n            }\n\n            // Simple \"path\" support, which can be very useful\n            // when we just want to parse a (few) nested value(s).\n            let! isCanceled = \"subscription.isCanceled\" \u0026= bool\n      \n            return {\n                Id = id\n                Name = name\n                Age = age\n                Email = email\n                Groups = groups\n                Subscription = subscription\n            }\n        }\n```\n\nWith the following types.\n\n```fsharp\nopen Farse\n\ntype UserId = UserId of Guid\n\nmodule UserId =\n\n    let asString (UserId x) =\n        string x\n\n    let parser =\n        Parse.guid\n        |\u003e Parser.map UserId\n\ntype Email = Email of string\n\nmodule Email =\n\n    let asString (Email x) = x\n\n    let fromString str =\n        // Some validation\n        Ok (Email str)\n\n    let parser =\n        Parse.string\n        |\u003e Parser.validate fromString\n\ntype GroupId = GroupId of Guid\n\nmodule GroupId =\n\n    let asString (GroupId x) =\n        string x\n\n    let parser =\n        Parse.guid\n        |\u003e Parser.map GroupId\n\ntype Plan =\n    | Pro\n    | Standard\n    | Free\n\nmodule Plan =\n\n    let fromString = function\n        | \"Pro\" -\u003e Ok Pro\n        | \"Standard\" -\u003e Ok Standard\n        | \"Free\" -\u003e Ok Free\n        | invalid -\u003e Error $\"Invalid plan: %s{invalid}.\"\n\n    let asString = function\n        | Pro -\u003e \"Pro\"\n        | Standard -\u003e \"Standard\"\n        | Free -\u003e \"Free\"\n\n    let parser =\n        Parse.string\n        |\u003e Parser.validate fromString\n\ntype Subscription = {\n    Plan: Plan\n    IsCanceled: bool\n    RenewsAt: DateTime option\n}\n\ntype User = {\n    Id: UserId\n    Name: string\n    Age: int option\n    Email: Email\n    Groups: GroupId list\n    Subscription: Subscription\n}\n```\n\nThen we can just run the parser.\n\n```fsharp\nlet user =\n    User.parser\n    |\u003e Parser.parse json\n    |\u003e Result.defaultWith failwith\n\nprintfn \"%s\" user.Name\n```\n\nWe can also construct JSON strings.\n\n```fsharp\nlet jsonString =\n    JsonString.create [\n        \"id\", JStr (UserId.asString user.Id)\n        \"name\", JStr user.Name\n        \"age\",\n            user.Age\n            |\u003e Option.map (Int \u003e\u003e JNum)\n            |\u003e JOpt\n        \"email\", JStr (Email.asString user.Email)\n        \"groups\",\n            user.Groups\n            |\u003e List.map (GroupId.asString \u003e\u003e JStr)\n            |\u003e JArr\n        \"subscription\",\n            JObj [\n                \"plan\", JStr (Plan.asString user.Subscription.Plan)\n                \"isCanceled\", JBit user.Subscription.IsCanceled\n                \"renewsAt\",\n                    user.Subscription.RenewsAt\n                    |\u003e Option.map (DateTime.asString \u003e\u003e JStr)\n                    |\u003e JOpt\n            ]\n    ]\n\nlet json =\n    jsonString\n    |\u003e JsonString.asString\n\nprintfn \"%s\" json\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftommililja%2Ffarse","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftommililja%2Ffarse","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftommililja%2Ffarse/lists"}