ParseySharp 0.2.4-beta.1

This is a prerelease version of ParseySharp.
dotnet add package ParseySharp --version 0.2.4-beta.1
                    
NuGet\Install-Package ParseySharp -Version 0.2.4-beta.1
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="ParseySharp" Version="0.2.4-beta.1" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="ParseySharp" Version="0.2.4-beta.1" />
                    
Directory.Packages.props
<PackageReference Include="ParseySharp" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add ParseySharp --version 0.2.4-beta.1
                    
#r "nuget: ParseySharp, 0.2.4-beta.1"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package ParseySharp@0.2.4-beta.1
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=ParseySharp&version=0.2.4-beta.1&prerelease
                    
Install as a Cake Addin
#tool nuget:?package=ParseySharp&version=0.2.4-beta.1&prerelease
                    
Install as a Cake Tool

ParseySharp

Write your parser once. Run it everywhere (JSON, XML, YAML, Protobuf, MessagePack, Avro). First-class ASP.NET Core integration.

This repository hosts the ParseySharp core and its ecosystem packages. Parsers return path-aware, accumulated errors, so you get precise paths (e.g., ["[3]","paymentMethod","cvv"]) and all issues across arrays/records in a single response.

  • Core: ParseySharp/
  • ASP.NET Core: ParseySharp.AspNetCore/
  • Carrier adapters: ParseySharp.* (YamlDotNet, Protobuf, MessagePack, Avro, etc.)
  • Swagger helpers: ParseySharp.Swashbuckle/
  • Samples: Samples/

Install

Add packages as needed (pick from the table below):

dotnet add package ParseySharp
# Optional adapters / integrations
# dotnet add package ParseySharp.AspNetCore
# dotnet add package ParseySharp.YamlDotNet
# dotnet add package ParseySharp.Protobuf
# dotnet add package ParseySharp.MessagePack
# dotnet add package ParseySharp.Avro
# dotnet add package ParseySharp.Swashbuckle

30‑second Quick Start

Build one parser and reuse it across carriers.

using ParseySharp;
using ParseySharp.Refine;

public abstract record PaymentMethod;
public sealed record Card(string Number, int Cvv) : PaymentMethod;
public sealed record Ach(string RoutingNumber, string AccountNumber) : PaymentMethod;

public enum PaymentMethodType { Card, Ach }

public readonly record struct ValidPayment(Refine.Refined<PaymentMethod, ValidPayment> Inner)
  : Refine.IRefine<PaymentMethod, ValidPayment>
{
  public static LanguageExt.Seq<string> Errors(PaymentMethod x) =>
    x switch
    {
      Card c =>
        "CVV is invalid".ErrUnless(() => c.Cvv is >= 100 and <= 999 || c.Cvv is >= 1000 and <= 9999),
      Ach a =>
        "ACH account number is invalid".ErrUnless(() => !string.IsNullOrWhiteSpace(a.AccountNumber)),
      _ => LanguageExt.Seq<string>.Empty
    };

  public static LanguageExt.Either<LanguageExt.Seq<string>, ValidPayment> Refined(PaymentMethod x)
    => Refine.Create<PaymentMethod, ValidPayment>(x).Map(v => new ValidPayment(v));
}

var paymentParser =
  (from paymentMethodType in Parse.Enum<PaymentMethodType>().At("type", [])
   from pm in paymentMethodType switch
   {
     PaymentMethodType.Card => (
         Parse.As<string>().At("number"),
         Parse.Int32Flex().At("cvv")
       ).Apply((number, cvv) => (PaymentMethod)new Card(number, cvv)).As(),
     PaymentMethodType.Ach => (
         Parse.As<string>().At("routingNumber"),
         Parse.As<string>().At("accountNumber")
       ).Apply((routingNumber, accountNumber) => (PaymentMethod)new Ach(routingNumber, accountNumber)).As(),
     _ => Parse.Fail<PaymentMethod>("Unsupported type")
   }
   select pm)
  .As()
  .Filter(ValidPayment.Refined);

// JSON (works the same for XML/YAML/Protobuf/MessagePack/Avro)
var cardJson = """{ "type":"Card", "number":"4111111111111111", "cvv": 123 }""";
var okCard = paymentParser.ParseJson()(System.Text.Json.JsonDocument.Parse(cardJson).RootElement);

var achJson = """{ "type":"Ach", "routingNumber":"021000021", "accountNumber":"000123456789" }""";
var okAch = paymentParser.ParseJson()(System.Text.Json.JsonDocument.Parse(achJson).RootElement);

// Array parse with accumulated, path-aware errors
var invalidBatchJson = """
[
  { "type":"Card", "number":"4111111111111111", "cvv": 123 },
  { "type":"Card", "number":"4111111111111111", "cvv": "abc" }, // type mismatch
  { "type":"Card", "cvv": 123 } // missing field; error at parent
]
""";
var invalidBatchResult = paymentParser
  .Seq()
  .ParseJson()(System.Text.Json.JsonDocument.Parse(invalidBatchJson).RootElement);

// Produces the following errors:
// [
//   {
//     "message": "Invalid integer: abc",
//     "expected": "Int32",
//     "actual": "abc",
//     "path": ["[1]", "cvv"]
//   },
//   {
//     "message": "Missing property number",
//     "expected": "String",
//     "actual": { "type": "Card", "cvv": 123 },
//     "path": ["[2]"]
//   }
// ]

Concepts in 60 seconds

  • Parse.As<T>() — atomic parser for T
  • .At("field", []) — navigate to a field/path
  • .Option() — optional field (Option<T>)
  • .Apply((...) => new T(...)) — map tuple results into your type
  • .As() — finalize the parser
  • Return type: Validation<Seq<ParsePathErr>, T> with path-aware errors

Practical example

A common pattern is a discriminated payload: a type field decides which fields are required. The parser branches on type and then applies a refinement step to ensure business rules are met.

// See Samples/ParseySharp.SampleWeb/Checkout.cs for a complete version
var paymentParser =
  (from paymentMethodType in Parse.Enum<PaymentMethodType>().At("type", [])
   from pm in paymentMethodType switch
   {
     PaymentMethodType.Card => (
         Parse.As<string>().At("number", []),
         Parse.Int32Flex().At("cvv", [])
       ).Apply((number, cvv) => (PaymentMethod)new Card(number, cvv)).As(),
     PaymentMethodType.Ach => (
         Parse.As<string>().At("routingNumber", []),
         Parse.As<string>().At("accountNumber", [])
       ).Apply((routingNumber, accountNumber) => (PaymentMethod)new Ach(routingNumber, accountNumber)).As()
   }
   select pm)
  .As()
  .Filter(x => Refine.Create<PaymentMethod, ValidPayment>(x).Map(v => new ValidPayment(v)));

This uses C# LINQ query syntax over Parse<T> to express dependent parsing (the second from depends on the first’s value) and Refine to enforce domain invariants. The same parser runs over JSON, XML, YAML, Protobuf, MessagePack, Avro, etc.

Recipe index

  • Optional fields: .Option()
  • Alternatives: p1.OrElse(p2)
  • Validation: .Filter(pred => Left("why"))
  • Arrays and streaming: parser.Seq(), streaming variants
  • Combine fields into a record: tuple + .Apply(...)
  • Normalize types: string→int via .Filter or OrElse

See ParseySharp.Tests/ and Samples/ for working recipes.

  • Default primitives for flexible, carrier-friendly parsing: ParseySharp/DefaultParsers.cs
  • End-to-end example used throughout this README: Samples/ParseySharp.SampleWeb/Checkout.cs

ASP.NET Core in two lines

Minimal API:

app.MapParsedPost("/payment", paymentParser, (ValidPayment x) => Results.Ok(x))
   .AcceptsJson().AcceptsXml().AcceptsFormUrlEncoded();

MVC:

[HttpPost("/mvc/payment")]
[AcceptsJson][AcceptsXml][AcceptsFormUrlEncoded]
public Task<IActionResult> Post(CancellationToken ct)
  => this.ParsedAsync(paymentParser, x => Ok(x), ct);

Swagger helpers: add ParseySharp.Swashbuckle and RequestModel annotations (Minimal API: SetRequestModel<T>(), MVC: [RequestModel<T>]).

Carrier adapters

  • JSON (System.Text.Json): Core
  • Newtonsoft.Json: ParseySharp.NewtonsoftJson
  • XML: Core
  • YAML: ParseySharp.YamlDotNet
  • Protobuf: ParseySharp.Protobuf
  • MessagePack: ParseySharp.MessagePack
  • Avro: ParseySharp.Avro
  • DataTables: Core
  • In‑memory objects (Map/List/etc): Core
  • Reflection: Core

Samples and tests

  • Samples: Samples/ParseySharp.SampleWeb/ (Minimal API + MVC, multi‑format endpoints)
  • Tests: ParseySharp.Tests/ (broad carrier coverage)

License

Apache-2.0. See LICENSE.

Extending to new formats (4 functions)

To add a new carrier, you only need to supply four functions to a ParsePathNav<TCarrier>:

  • Prop (drill into a carrier by name)
  • Index (drill into a carrier by array index)
  • Unbox (extract a primitive value from a carrier)
  • Clone (clone or otherwise produce an owned value for safe iteration/yielding)

Here’s a concrete illustration using a familiar type, System.Text.Json.JsonElement.

using System.Text.Json;

public static class ParsePathNavJson
{
  public static readonly ParsePathNav<JsonElement> Json = new(
    Prop: (node, name) =>
      node.ValueKind == JsonValueKind.Object && node.TryGetProperty(name, out var child)
        ? Right<Unknown<JsonElement>, Option<JsonElement>>(Optional(child))
        : node.ValueKind == JsonValueKind.Object
          ? Right<Unknown<JsonElement>, Option<JsonElement>>(None)
          : Left<Unknown<JsonElement>, Option<JsonElement>>(Unknown.New(node)),

    Index: (node, i) =>
      node.ValueKind == JsonValueKind.Array && i >= 0 && i < node.GetArrayLength()
        ? Right<Unknown<JsonElement>, Option<JsonElement>>(Optional(node[i]))
        : node.ValueKind == JsonValueKind.Array
          ? Right<Unknown<JsonElement>, Option<JsonElement>>(None)
          : Left<Unknown<JsonElement>, Option<JsonElement>>(Unknown.New(node)),

    Unbox: node => node.ValueKind switch
    {
      JsonValueKind.Null   => Right<Unknown<JsonElement>, Unknown<object>>(new Unknown<object>.None()),
      JsonValueKind.String => Right<Unknown<JsonElement>, Unknown<object>>(Unknown.New<object>(node.GetString()!)),
      JsonValueKind.True   => Right<Unknown<JsonElement>, Unknown<object>>(Unknown.New<object>(true)),
      JsonValueKind.False  => Right<Unknown<JsonElement>, Unknown<object>>(Unknown.New<object>(false)),
      // handle other cases
      _ => Left<Unknown<JsonElement>, Unknown<object>>(Unknown.New(node))
    },

    Clone: node =>
      node.ValueKind == JsonValueKind.Undefined
        ? JsonDocument.Parse("null").RootElement.Clone()
        : node.Clone()
  );
}

// Using your custom navigator with any parser:
//   var result = parser.RunWithNav(ParsePathNavJson.Json)(jsonElement);

See a production-grade example in ParseySharp.Avro/Navigate.Avro.cs, which defines a navigator over Avro generic values by implementing these four functions.

Product Compatible and additional computed target framework versions.
.NET 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (11)

Showing the top 5 NuGet packages that depend on ParseySharp:

Package Downloads
ParseySharp.AspNetCore

ASP.NET Core integration for ParseySharp: parse multi-format request bodies and bind to models.

ParseySharp.MessagePack

MessagePack navigator for ParseySharp: run parsers over MessagePack nodes.

ParseySharp.Avro

Avro navigator for ParseySharp: run parsers over Avro generic values.

ParseySharp.Protobuf

Protobuf navigator for ParseySharp: run parsers over Google.Protobuf messages and WellKnownTypes.

ParseySharp.NewtonsoftJson

Newtonsoft.Json navigator for ParseySharp: run parsers over JToken.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.2.4-beta.1 142 10/7/2025
0.2.3-beta.1 131 10/7/2025
0.2.1-beta.1 349 9/17/2025
0.2.0-beta.1 259 9/16/2025
0.1.0-beta.2 448 9/15/2025
0.1.0-beta.1 216 9/15/2025