JanusPrime.OData 0.1.0

dotnet add package JanusPrime.OData --version 0.1.0
                    
NuGet\Install-Package JanusPrime.OData -Version 0.1.0
                    
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="JanusPrime.OData" Version="0.1.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="JanusPrime.OData" Version="0.1.0" />
                    
Directory.Packages.props
<PackageReference Include="JanusPrime.OData" />
                    
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 JanusPrime.OData --version 0.1.0
                    
#r "nuget: JanusPrime.OData, 0.1.0"
                    
#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 JanusPrime.OData@0.1.0
                    
#: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=JanusPrime.OData&version=0.1.0
                    
Install as a Cake Addin
#tool nuget:?package=JanusPrime.OData&version=0.1.0
                    
Install as a Cake Tool

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 any and all
  • 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:

  1. Receive an OData query string in an ASP.NET Core REST endpoint
  2. Parse and normalize it against a CLR root type
  3. Apply normalization policies
  4. 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:

  • displayName is resolved to the CLR property DisplayName
  • the normalized request stays CLR-centric
  • a later translator can map DisplayName to the JSON field displayName for 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 $select for a type
  • marking certain properties as non-filterable
  • disabling $expand on 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:

  • $top maximum set to 100
  • PasswordHash not selectable
  • InternalNotes not filterable
  • Manager expandable
  • SecretToken not 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 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • 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