Skip to content

Preserve JSON integer precision in request and response helpers #118

Description

@ZilvinasKucinskas

Priority: P1

Describe the issue

Wreq::Response#json silently converts some integral JSON numbers to Float. That loses precision even though Ruby's standard JSON.parse returns an exact arbitrary-precision Integer for the same bytes. The request-side json: converter also rejects integers just above the signed platform range.

Identifiers, timestamps, counters, and pagination tokens are commonly represented as JSON numbers. Silent numeric corruption is particularly hard to diagnose because parsing succeeds and the returned value looks plausible.

Reproduction

Given a response body of:

{"id":9223372036854775808}

wreq 1.2.4 behaves as follows:

require "json"
require "wreq"

response.json["id"]
# => 9.223372036854776e+18 (Float)

JSON.parse(response.bytes)["id"]
# => 9223372036854775808 (Integer)

The request path rejects the same Ruby integer before sending:

client.post(
  "http://127.0.0.1:1",
  json: {id: 9_223_372_036_854_775_808}
)
# RangeError: bignum too big to convert into `long long`

Required behavior

  • Integral JSON tokens must deserialize to exact Ruby Integer values, not Float approximations.
  • Fractional or exponent-form JSON numbers continue to deserialize according to documented Ruby JSON behavior.
  • Request json: serialization should accept the integer range accepted by Ruby's JSON.generate. If the native serializer intentionally supports a smaller range, it must document that limit and raise before any network I/O; silent coercion is never acceptable.
  • Do not turn large numbers into strings unless the caller supplied strings.
  • Preserve the existing return shapes for objects, arrays, strings, booleans, and null.

Delegating the convenience helper to Ruby's JSON implementation is one possible solution; using an integer-preserving native representation is another. The issue is about compatible observable behavior, not a required implementation.

Acceptance criteria

  • Response#json matches JSON.parse(response.bytes) for integral number values.
  • Request and response tests cover 2**53 - 1, 2**53, 2**63 - 1, 2**63, 2**64 - 1, negative boundaries, and an integer beyond 64 bits.
  • Nested arrays and objects preserve the same values.
  • Unsupported input raises a documented Ruby exception before DNS or socket I/O.
  • Invalid JSON still raises the documented decoding/parser exception.

Ruby ecosystem precedent

Ruby's standard JSON.parse maps integral JSON numbers to Integer and fractional numbers to Float. Ruby integers have arbitrary precision, so callers do not normally expect a native binding to narrow a syntactically integral value silently.

wreq-python's JSON converter also uses a platform-sized signed integer representation. That demonstrates a recurring binding-conversion limitation, not a cross-language API contract Ruby should copy.

Relevant wreq-ruby source

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions