Skip to content

feat: add LDValueConverter and LDContextEncoder to common#187

Open
mattrmc1 wants to merge 2 commits into
mainfrom
mmccarthy/AIC-2939/add-context-encoder-to-common
Open

feat: add LDValueConverter and LDContextEncoder to common#187
mattrmc1 wants to merge 2 commits into
mainfrom
mmccarthy/AIC-2939/add-context-encoder-to-common

Conversation

@mattrmc1

@mattrmc1 mattrmc1 commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds two general-purpose, public utilities to the shared common module (com.launchdarkly.sdk) for converting LaunchDarkly data-model types into plain Java structures:

  • LDValueConverter — converts an LDValue tree into plain Java values (String, Long, Double, Boolean, List, Map, or null).
  • LDContextEncoder — encodes an LDContext into a plain nested Map<String, Object>, using LDValueConverter for leaf attribute values.

Both are self-contained (no new dependencies) and live next to LDValue / LDContext so they can be shared across artifacts. This change is additive only — no existing types are modified.

Where this will be used

These utilities are being promoted into common so they can be shared rather than reimplemented per artifact. They aren't referenced within common itself yet — a follow-up change will wire them into the AI SDK (server-ai):

  • LDValueConverter will replace the AI SDK's internal copy used by its config parser to expose model.parameters, model.custom, and tool parameters / customParameters as plain Java maps on the public config surface (without leaking LDValue).
  • LDContextEncoder will replace the AI SDK's Interpolator context-encoding logic that builds the ldctx variable exposed to Mustache prompt templates (e.g. {{ldctx.key}}, {{ldctx.name}}).

Because common is a separately published artifact, it must be released with these utilities before the AI SDK can depend on them; the follow-up then removes the AI SDK's local copies. Landing this on its own keeps the shared utilities and their tests decoupled from the AI SDK release. The encoder is intentionally general-purpose (not Mustache-specific), so it's reusable anywhere a context needs to be rendered into a generic nested structure.

LDValueConverter

public static Object toJavaObject(LDValue value);       // LDValue tree -> plain Java value
public static Map<String, Object> toMap(LDValue value); // JSON object -> Map, else null
public static final int MAX_DEPTH = 100;
  • Conversion is defensive and never throws on malformed or pathological input.
  • Numbers decode to Long when they are mathematically integral and within the IEEE-754 exact-integer range (|value| <= 2^53); otherwise to Double. Whole numbers outside ±2^53 return the nearest Double.
  • Nesting depth is capped at MAX_DEPTH; values deeper than the cap are dropped (null) to bound stack usage on adversarial input.
  • Object fields use a LinkedHashMap to preserve insertion order; returned collections are unmodifiable.

LDContextEncoder

public static Map<String, Object> encode(LDContext context); // never null

Encodes an LDContext into a nested map without round-tripping through JSON serialization:

  • A null or invalid context produces an empty map.
  • A single-kind context produces kind, key, name (only when non-null), anonymous (always present), and one entry per custom attribute.
  • A multi-kind context produces {"kind":"multi", "key":<fullyQualifiedKey>, <kindName>:{...}, ...}, where each per-kind nested map omits kind (it is implied by the property key), mirroring LaunchDarkly's standard context JSON shape.

Test plan

  • common module build and tests pass
  • LDValueConverterTest — null/JSON-null handling, integral vs. fractional numbers, the ±2^53 boundary (inside and just outside), NaN/Infinity, strings/booleans, nested objects and arrays, field-order preservation, toMap returns null for non-objects, unmodifiable results, and depth-cap behavior on deeply nested input
  • LDContextEncoderTest — null/invalid context, single-kind (kind/key/anonymous, name present/omitted, anonymous true/false), custom attribute objects/arrays and deeply nested attributes, multi-kind (kind:multi + fully-qualified key, per-kind objects, kind omission on nested objects, nested anonymous/name), and custom context kinds

Note

Low Risk
Additive-only new public APIs in common with no changes to existing behavior; edge cases (depth cap, multi-kind "key" collision) are documented and tested.

Overview
Adds two public utilities in com.launchdarkly.sdk for turning LaunchDarkly model types into plain Java structures without JSON round-trips.

LDValueConverter walks an LDValue tree into String, Long, Double, Boolean, unmodifiable List/Map, or null, with integral numbers in ±2^53 as Long, a depth cap of 100, and defensive handling so conversion does not throw.

LDContextEncoder maps an LDContext to nested Map<String, Object> matching standard context JSON (single-kind vs multi-kind, optional name, always anonymous, custom attrs via the converter). Null or invalid contexts yield an empty map; multi-kind output sets top-level key to the fully qualified key after per-kind entries so it wins over a member kind named "key" (documented trade-off in tests).

Comprehensive unit tests cover both classes; no existing types are modified.

Reviewed by Cursor Bugbot for commit 76872ec. Bugbot is set up for automated code reviews on this repo. Configure here.

@jsonbailey

Copy link
Copy Markdown
Contributor

@mattrmc1 can you add a description for what this will be used for. I see it being added but not used anywhere.

@mattrmc1 mattrmc1 marked this pull request as ready for review July 8, 2026 19:58
@mattrmc1 mattrmc1 requested a review from a team as a code owner July 8, 2026 19:58

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 71f3f8e. Configure here.

@mattrmc1

mattrmc1 commented Jul 8, 2026

Copy link
Copy Markdown
Contributor Author

PR for the actual implementation once this is published to common: #188

/**
* Maximum nesting depth converted before deeper values are dropped.
*/
public static final int MAX_DEPTH = 100;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where does this come from?

@tanderson-ld tanderson-ld self-requested a review July 9, 2026 20:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants