Skip to content

Skip intermediate serde_json::Value allocation for Context::from_serde() #2265

@martekz

Description

@martekz

Category

User level API features/changes

Describe the feature you'd like to request

We're using Cedar as an embedded authorization engine in a Rust financial governance system (product lifecycle management, trading entitlements, pre-trade checks). In this setup, the authorization context is already a Rust struct (or a "rkyv" object), so it never arrives as JSON.

Currently, building a Context requires going through serde_json::Value:

// Our Rust struct → serde_json::Value → Cedar Context
let json_value = serde_json::to_value(&our_context)?;
let cedar_ctx = Context::from_json_value(json_value, Some((&schema, &action)))?;

This allocates an intermediate HashMap<String, Value> plus individual Value nodes that Cedar then converts into its internal restricted expression representation.

For typical web-service authorization (one check per HTTP request), this is negligible. But for high-frequency embedded use cases such as pre-trade entitlement checks, streaming risk limit validation, batch authorization over thousands of entities, the per-check allocation overhead becomes measurable.

Proposed API:

impl Context {
    /// Build a Context directly from any Serialize type, skipping the
    /// intermediate serde_json::Value allocation.
    pub fn from_serde<T: Serialize>(
        value: &T,
        schema: Option<(&Schema, &EntityUid)>,
    ) -> Result<Self, ContextJsonError>;
}

This would deserialize directly into Cedar's internal representation (restricted expressions) without the intermediate JSON value tree. The same approach could benefit Entities::from_json_value() for similar reasons.

Describe alternatives you've considered

Alternatives considered:

1. Pre-allocating and reusing a serde_json::Value buffer

We could build the Value once and then mutate field values in place between checks. This would avoid repeated allocation, but would then require careful lifetime management and we would still pay the cost of converting Value to restricted expression on every call.

2. Caching Context objects

For checks where the context is identical (in our use-case e.g. same jurisdiction, same suitability class), we could cache the constructed Context. This would likely help when many entities share the same context attributes, but wouldn't help when context varies per check (e.g., different delegation chains, per-entity suitability).

3. Using cedar-policy-core directly

We could theoretically bypass the public cedar-policy API and construct ast::Context from restricted expressions directly. This would skip JSON entirely, but it would also couple us to Cedar's internal representation which isn't part of the public API contract and could break across versions.

Where Option 3 is the most performant today but also the least sustainable. A public from_serde path would give the same performance with API stability guarantees. Hence the feature request...

Additional context

No response

Is this something that you'd be interested in working on?

  • 👋 I may be able to implement this feature request
  • ⚠️ This feature might incur a breaking change

Metadata

Metadata

Assignees

No one assigned

    Labels

    feature-requestThis issue requets a substantial new featurepending-triageThe cedar maintainers haven't looked at this yet. Automicaly added to all new issues.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions