Skip to content

fix(core): parse JSON numbers in exponent notation#8225

Closed
MohMaya wants to merge 2 commits into
aws:mainfrom
MohMaya:fix/json-reviver-exponent-notation
Closed

fix(core): parse JSON numbers in exponent notation#8225
MohMaya wants to merge 2 commits into
aws:mainfrom
MohMaya:fix/json-reviver-exponent-notation

Conversation

@MohMaya

@MohMaya MohMaya commented Jul 24, 2026

Copy link
Copy Markdown

Issue

Fixes #8224

Description

jsonReviver throws on any JSON number written in exponent notation, e.g. 1.784316439424E9:

Error: @smithy/core/serde - NumericValue must only contain [0-9], at most one decimal point ".", and an optional negation prefix "-".

Exponent notation is valid JSON, and AWS services emit it — ECS serializes epoch-second timestamps that way, so every ecs:DescribeServices response currently fails to deserialize.

The reviver decides a token is precision-losing by comparing the raw source text against String(value):

if (value > Number.MAX_SAFE_INTEGER || value < Number.MIN_SAFE_INTEGER || numericString !== String(value)) {
  const isFractional = numericString.includes(".");
  if (isFractional) {
    return new NumericValue(numericString, "bigDecimal");
  } else {
    return BigInt(numericString);
  }
}

An exponent-notation token always fails that string comparison ("1.784316439424E9" !== "1784316439.424"), so it is treated as precision-losing even when the double round-trips exactly. Both branches then reject it:

  • fractional → NumericValue, whose format is /^-?\d*(\.\d+)?$/ and forbids E
  • non-fractional → BigInt("1E9") throws SyntaxError: Cannot convert 1E9 to a BigInt

This change normalizes exponent notation to plain decimal before the classification, so the existing logic sees a form it understands. Values that round-trip (the common case, including ECS timestamps) now fall through to a plain number; values that genuinely exceed double precision are still preserved as BigInt / NumericValue.

Expansion is bounded by MAX_EXPANDED_DIGITS (1024) so an extreme exponent cannot cause a large string allocation; past that bound the reviver returns the parsed double rather than throwing.

Behavior notes for reviewers

Two consequences worth a look, both currently exceptions rather than values:

  • 1e1000 / 1e-1000 were SyntaxError / NumericValue throws; they are now preserved exactly (BigInt and NumericValue respectively) instead of collapsing to Infinity / 0. This follows the reviver's stated purpose, but it is a behavior choice — happy to return the double instead if you'd prefer the narrower change.
  • Beyond MAX_EXPANDED_DIGITS the parsed double is returned (Infinity / 0), matching no-reviver behavior. The threshold is arbitrary; say the word if you want a different bound.

I did not widen NumericValue's format regex in smithy-typescript, since fixing the classification here keeps the change in one repo. If you'd rather accept the full JSON number grammar there, that would also solve it and I'm glad to follow up.

Testing

Added cases to jsonReviver.spec.ts, gated on contextSourceAvailable like the existing tests:

  • exponent notation parses to plain numbers — including the ECS-style 1.784316439424E9, integral 1E9, lowercase 1e9, negative -2.5E3, and fractional 1.23E1
  • precision is still preserved for exponent values that need it — 1.5E-10NumericValue("0.00000000015"), 1.2345678901234567890E30BigInt
  • extreme exponents fall back to the parsed double instead of throwing

The existing tests in that file (small ints/decimals, 37-digit BigInt, 41-digit bigDecimal) are unchanged and still pass — those inputs contain no exponent, so they take the original path untouched.

Verified end to end by patching the compiled fix into @aws-sdk/core@3.977.0 in a local install: describeServices goes from the error above to returning createdAt as a Date, both against a stubbed response and against the live ECS API.

tsc --strict --noEmit is clean on the changed file.

Additional context

Regression range: @aws-sdk/core@3.976.0 is fine, 3.977.0 fails. Suspect #8214, which enabled the reviver more broadly via needsReviver / parseJsonBody.

botocore parses these identical bodies without complaint, so the divergence is specific to this reviver.


By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.

@MohMaya

MohMaya commented Jul 24, 2026

Copy link
Copy Markdown
Author

Duplicate of PR #8226

@MohMaya MohMaya closed this Jul 24, 2026
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.

Deserialization fails on JSON numbers in exponent notation (regression in @aws-sdk/core 3.977.0) — breaks all ecs:DescribeServices calls

1 participant