ParseySharp 0.2.4-beta.1
dotnet add package ParseySharp --version 0.2.4-beta.1
NuGet\Install-Package ParseySharp -Version 0.2.4-beta.1
<PackageReference Include="ParseySharp" Version="0.2.4-beta.1" />
<PackageVersion Include="ParseySharp" Version="0.2.4-beta.1" />
<PackageReference Include="ParseySharp" />
paket add ParseySharp --version 0.2.4-beta.1
#r "nuget: ParseySharp, 0.2.4-beta.1"
#:package ParseySharp@0.2.4-beta.1
#addin nuget:?package=ParseySharp&version=0.2.4-beta.1&prerelease
#tool nuget:?package=ParseySharp&version=0.2.4-beta.1&prerelease
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 forT
.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
orOrElse
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 | Versions 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. |
-
net9.0
- LanguageExt.Core (>= 5.0.0-beta-54)
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 |