JanusPrime.OData
0.1.0
dotnet add package JanusPrime.OData --version 0.1.0
NuGet\Install-Package JanusPrime.OData -Version 0.1.0
<PackageReference Include="JanusPrime.OData" Version="0.1.0" />
<PackageVersion Include="JanusPrime.OData" Version="0.1.0" />
<PackageReference Include="JanusPrime.OData" />
paket add JanusPrime.OData --version 0.1.0
#r "nuget: JanusPrime.OData, 0.1.0"
#:package JanusPrime.OData@0.1.0
#addin nuget:?package=JanusPrime.OData&version=0.1.0
#tool nuget:?package=JanusPrime.OData&version=0.1.0
JanusPrime.OData
JanusPrime.OData is a lightweight C# library for parsing and normalizing OData query strings for dynamic backends without requiring EDM models.
It is designed for dynamic backends where:
- schemas evolve over time,
- there are many entity types,
- properties change frequently,
- queries must be translated to custom storage engines such as Cosmos DB, SQL, search engines, or proprietary stores.
Instead of binding queries to EDM metadata, JanusPrime.OData parses OData query options into a clean AST and normalizes them against CLR types and configurable policies.
Why JanusPrime.OData
Traditional OData stacks are often model-centric and assume a stable EDM.
JanusPrime.OData takes a different approach:
- No EDM required
- Works well with dynamic or evolving storage schemas
- Resolves query paths against CLR types
- Applies normalization and validation policies
- Produces a storage-agnostic query shape that can later be translated to Cosmos DB, SQL, or other backends
This makes it especially useful in modern API layers where OData is used as an input query language, not as a full protocol stack.
Features
- Parse OData query strings into an AST
- Normalize
$select,$filter,$orderby,$expand,$top,$skip,$count,$search - Resolve property paths against CLR types
- Case-insensitive property resolution
- Per-type and per-property normalization policies
- MaxTop enforcement
- Property restrictions for select, filter, orderby, and expand
- Storage-agnostic architecture
- Suitable for custom translators, including Cosmos DB scenarios
Supported query options
| Query option | Status |
|---|---|
$select |
Supported |
$filter |
Supported |
$orderby |
Supported |
$expand |
Supported |
$top |
Supported |
$skip |
Supported |
$count |
Supported |
$search |
Supported |
Filter support
JanusPrime.OData supports a pragmatic subset of the OData $filter syntax.
Supported today
- Property identifiers
- Nested property paths using
/ - String literals in single quotes
- Numeric literals
- Boolean literals
null- Parenthesized expressions
- Comparison operators:
eq,ne,gt,ge,lt,le - Logical operators:
and,or,not
Example filters
$filter=DisplayName eq 'Mario'
$filter=Age ge 18 and IsActive eq true
$filter=Manager/DisplayName eq 'Luigi'
$filter=not (Status eq 'Deleted')
Filter support is intentionally conservative and expands only when behavior is clearly defined and covered by tests.
OData standard scope
This library intentionally supports a limited, implementation-defined subset of OData.
It is not a full OData server stack and does not attempt to provide complete standard coverage.
| Capability | Status |
|---|---|
| Comparison operators | Supported |
| Logical operators | Supported |
| Nested property paths | Supported |
| String/number/boolean/null literals | Supported |
| Canonical OData functions | Limited |
Lambda any / all |
Not supported |
| Arithmetic operators | Not supported |
| EDM semantic binding | Not supported |
Out of scope or not guaranteed
- EDM-driven semantic binding
- Full metadata support
- Full canonical function coverage
- Lambda operators such as
anyandall - Arithmetic expressions
- Parameter aliases
- Full EDM literal compatibility
- Protocol-level compliance with the complete OData standard
Non-goals
JanusPrime.OData is not intended to be:
- a full OData server implementation
- an EDM-based metadata framework
- a direct query provider for Entity Framework
- a complete replacement for Microsoft’s end-to-end OData stack
Its purpose is focused and explicit: parse and normalize OData query input so that applications can translate it into their own storage/query model.
Typical use case
A common usage pattern is:
- Receive an OData query string in an ASP.NET Core REST endpoint
- Parse and normalize it against a CLR root type
- Apply normalization policies
- Translate the normalized request into a backend-specific query, for example a Cosmos DB SQL query
Installation
dotnet add package JanusPrime.OData
Build from source
Clone the repository and build the solution with Visual Studio 2022 or the .NET SDK.
Quick example
var normalizer = new ODataNormalizer(policy);
var result = normalizer.Normalize<UserDocument>(
"?$select=displayName,id&$filter=userType eq 'Member'&$top=20");
if (!result.Success)
{
foreach (var diagnostic in result.Diagnostics)
{
Console.WriteLine($"{diagnostic.Code}: {diagnostic.Message}");
}
return;
}
var request = result.Value;
In a typical pipeline:
displayNameis resolved to the CLR propertyDisplayName- the normalized request stays CLR-centric
- a later translator can map
DisplayNameto the JSON fielddisplayNamefor Cosmos DB
Architecture
Parser → AST → Normalizer → Translator → Storage query
Benchmarks
The repository includes a dedicated BenchmarkDotNet project for parser microbenchmarks: JanusPrime.OData.Benchmarks.
It measures parsing time and managed allocations for the main OData query scenarios, including $select, $filter, $orderby, and $expand.
Run it with:
dotnet run -c Release --project JanusPrime.OData.Benchmarks
Detailed benchmark documentation and the latest measured results are available in JanusPrime.OData.Benchmarks/README.md.
Design principles
1. CLR-first normalization
The library normalizes query paths against CLR types, not storage-specific field names.
Example:
- incoming OData:
displayName - normalized property:
DisplayName - Cosmos translator output:
displayName
This keeps the parsing and normalization layers independent from storage concerns.
2. Storage-agnostic output
The library does not assume SQL, Cosmos DB, MongoDB, or any specific backend.
Normalization produces a clean intermediate representation that can be translated later.
3. Policy-based validation
Normalization policies allow you to enforce rules such as:
- maximum allowed
$top - disallowing
$selectfor a type - marking certain properties as non-filterable
- disabling
$expandon specific navigation properties
4. Reflection-based shape resolution
Entity shapes are resolved from CLR types through a shape provider.
This allows path resolution without EDM and supports applications with dynamic and evolving models.
ASP.NET Core integration
JanusPrime.OData is a good fit for ASP.NET Core APIs that expose REST/JSON endpoints and accept OData-style query options.
Recommended flow:
- inject or centralize an
ODataNormalizer - call
Normalize<T>()for the root entity handled by the endpoint - translate the normalized request into your own query model
If policies differ by entity type, a single normalizer can still be used when the policy is configured with per-type rules.
Cosmos DB integration
A common pattern with Cosmos DB is:
- documents store JSON fields in camelCase
- CLR types use PascalCase
- JanusPrime.OData normalizes to CLR property names
- a Cosmos-specific translator maps CLR names to JSON field names
This separation keeps the library independent from physical storage naming conventions.
Example policy scenarios
Typical policy rules include:
$topmaximum set to 100PasswordHashnot selectableInternalNotesnot filterableManagerexpandableSecretTokennot orderable
Roadmap
Planned or evolving areas may include:
- improved diagnostics
- more complete function handling
- additional translators and integration samples
- richer documentation and examples
- NuGet publication and CI packaging
Status
This project is under active development.
Public API and behavior may still evolve before a stable 1.0 release.
Contributing
Issues, suggestions, and contributions are welcome.
For major changes, please open an issue first so the direction can be discussed before implementation.
License
Licensed under the Apache License 2.0. See LICENSE for details.
| 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
- No dependencies.
-
net8.0
- No dependencies.
-
net9.0
- No dependencies.
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.1.0 | 128 | 4/9/2026 |