Skip to content

Cloud Controller encodes JSON decimal numbers as strings when proxying service broker payloads #4799

@philippthun

Description

@philippthun

Issue

Summary

When Cloud Controller proxies JSON payloads that contain decimal numbers (e.g. 0.1) it may change the JSON type from number to string (e.g. "0.1").

This is unexpected for clients and breaks "type-preserving" pass-through semantics for broker-provided JSON.

Scenario

Endpoint: GET /v3/service_instances/:guid/parameters (managed service instance)

Observed behavior:

  1. Cloud Controller queries the service broker for parameters.
  2. Cloud Controller parses the returned JSON string.
  3. Decimal numbers are loaded as BigDecimal.
  4. Cloud Controller serializes the "parameters" field back to JSON.
  5. The response contains strings instead of JSON numbers.

Service broker returns:

{
  "parameters": {
    "key": 0.1
  }
}

Cloud Controller responds with:

{
  "parameters": {
    "key": "0.1"
  }
}

Expected behavior

Cloud Controller should ideally preserve JSON numeric types when proxying broker-provided JSON:

{
  "parameters": {
    "key": 0.1
  }
}

Background / Root cause analysis

Cloud Controller uses Oj in Rails mode:

  • mode: :rails
  • Oj::Rails.optimize

1) Oj loads decimals as BigDecimal

Cloud Controller configures Oj with bigdecimal_load: :bigdecimal, meaning JSON numbers with decimals/exponents are parsed into BigDecimal.

Reference:

2) Rails converts BigDecimal to String before encoding JSON

In Rails mode, the JSON pipeline typically invokes as_json before encoding. ActiveSupport’s BigDecimal#as_json returns a String, so the numeric value is already stringified before the JSON encoder runs.

This results in a response where decimal values are encoded as JSON strings.

Impact / Affected payloads

Cloud Controller passes through various objects where types should ideally not change:

  • Configuration parameters
    • service instances and service bindings
  • Credentials
    • service bindings
  • Catalog metadata
    • service offerings and service plans
  • Mount config
    • service bindings

Possible solutions

Option 1: bigdecimal_load: :auto (parse many decimals as Float)

Switch parsing from bigdecimal_load: :bigdecimal to bigdecimal_load: :auto so that many "simple" decimal tokens are parsed as Float instead of BigDecimal. Those values would then typically be serialized as JSON numbers.

Major drawback (value preservation): this can change numeric values.

:auto uses a heuristic based largely on significant digits / "fits in float precision", but that is not the same as "exactly representable in IEEE-754 binary floating point". Many decimal fractions cannot be represented exactly as Float (e.g. values like 0.1), and near the precision boundary you can observe last-digit drift.

In other words, Option 1 may improve type preservation ("number stays number"), but it risks breaking value preservation, which is worse than a type change.

Option 2: Preserve values with BigDecimal, but encode BigDecimal as JSON numbers

Keep bigdecimal_load: :bigdecimal to ensure no Float conversion happens and numeric values remain exact within Cloud Controller.

Then make the JSON encoding pipeline emit BigDecimal as a JSON number instead of a string, by:

  1. Preventing Rails/ActiveSupport from converting BigDecimal to String during as_json
    • override BigDecimal#as_json to return self
  2. Setting Oj bigdecimal_as_decimal: true so Oj serializes BigDecimals as JSON numbers

Tradeoffs:

  • This changes current Rails JSON semantics globally unless carefully scoped.

Option 3: Keep current behavior (BigDecimals encoded as JSON strings)

Leave the behavior as-is: parse decimals into BigDecimal, then emit them as JSON strings.

Advantages:

  • Value preservation is guaranteed inside Cloud Controller (no Float conversion, no rounding drift).
  • Maximum cross-language safety: Many consumers (especially JavaScript) parse JSON numbers as IEEE-754 floats. Emitting decimals as strings avoids accidental precision loss or rounding in clients, proxies, logs, or intermediate systems.
  • Stable, explicit contract: A string strongly signals "treat as decimal, not float", and avoids subtle numeric bugs downstream.
  • Lowest risk: no changes to global JSON parsing/encoding behavior.

Disadvantage:

  • Not type-preserving for pass-through payloads: JSON numbers from brokers may become strings in CC responses (e.g. 0.1"0.1"), which can break clients expecting numeric JSON types.

Additional remark: lexical form cannot be guaranteed

Once Cloud Controller parses a JSON number token into a numeric value and later re-serializes it, it cannot guarantee the exact original spelling (dropping trailing zeros, changing exponent vs fixed notation, etc.). The goal can be type/value preservation, but not exact lexical round-tripping.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions