BDE 4.14.0 Production release
Loading...
Searching...
No Matches
bdljsn_jsonnumber

Detailed Description

Outline

Purpose

Provide a value-semantic type representing a JSON number.

Classes

Description

This component provides a single value-semantic class, bdljsn::JsonNumber, that represents a JSON number. The value of a bdljsn::JsonNumber object is set at construction using a string representation of the JSON number (see {JSON Textual Specification}) or from one of several C++ arithmetic types (see {Supported Conversions}).

Arithmetic operations are not defined for bdljsn::JsonNumber objects. For such operations, the value of a bdljsn::JsonNumber object can be converted to any of those supported types, though the conversion may not be exact.

The bdlsn::JsonNumber equality operation returns true if the string representation of the number (returned by the value accessor method) is the same, even where the two strings represent the same number (e.g., "10" and "1e1"). This definition of equality reflects the fact that the JSON textual representation for the two JsonNumber objects will be different. The function isEqual is provided for (a more expensive) numeric equality comparison.

JSON Textual Specification

JSON numbers are defined by strings that match the grammar given at https://www.rfc-editor.org/rfc/rfc8259#section-6. The equivalent regular expression is:

/^-?(0|[1-9][0-9]*)(\.[0-9]+)?([eE][-+]?[0-9]+)?\z/

Note that "\z" matches end-of-string but not a preceding '
'.

For example:

1
2.1
-3
4e1
5.1e+2
6.12e-3
7e+04
-8.1e+005

Notice that:

The value of a bdljsn::JsonNumber object is determined by its given string representation, which is not normalized. Unequal strings lead to unequal bdljsn::JsonNumber objects even if their numerical values are equal. Numerical equality can be tested with the isEqual method. Note that the isEqual method is more computationally expensive than the equality and inequality operators:

// The following 'JsonNumber' objects do not compare equal because their
// string representations are different:
assert(bdljsn::JsonNumber("1") != bdljsn::JsonNumber("1.0"));
// But, they are numerically equal, so 'isEqual' returns 'true':
assert(bdljsn::JsonNumber("1").isEqual(bdljsn::JsonNumber("1.0")));
Definition bdljsn_jsonnumber.h:408

Supported Conversions

The value of a bdljsn::JsonNumber object can be converted to an assortment of useful types:

In addition to named conversion functions (like asInt and asDouble) This component provides explicit conversion operations for floating point types (float, double, Decimal64) on platforms where explicit conversions are supported.

Handling Inexact Conversions: Floating Point Vs Integral Types

Converting a bdlsjn::JsonNumber to another representation may result in a value that is not the same as the original bdljsn::JsonNumber. Either the bdljsn::JsonNumber may represent a numeric value outside of the representable range of the requested type (i.e., it is too large, or too small), or the value may not be representable exactly. bdljsn::JsonNumber conversions will return the closest approximation of the bdljsn::JsonNumber, even when a non-zero status is returned indicating an inexact conversion.

All the provided conversions to integral types have signatures that require a return status, whereas conversion functions are provided for floating point types that do not return a status. This is because:

  1. Floating point representations have specific values to indicate a bdljsn::JsonNumber is outside of the representable range (-INF, +INF).
  2. Truncating the fractional part of a number to coerce a value to an integer is typically an error (the data being processed did not meet the programmer's expectation), whereas returning the closest floating point approximation to a bdljsn::JsonNumber is very often not an error.

Exact Decima64 Representations

For users requiring precise conversions to bdldfp::Decimal64, the function asDecimal64Exact returns additional status indicating whether the conversion is exact. An exact conversion for a Decimal64 is one that preserves all the significant digits resulting in a decimal representation having the same numerical value as the original JSON text. Note that asDecimal64Exact has very similar performance to asDecimal64 (i.e., there is not a notable performance penalty to determining this property).

Known Issues With asDecima64Exact

Currently asDecimal64Exact will return bdljsn::JsonNumber::k_NOT_EXACT if the input is 0 with an exponent outside of the range ([-398, 369]). For example, 0e-400. This reflects the behavior of the underlying 3rd party implementation. Please contact BDE if this is a concern.

Usage

This section illustrates intended use of this component.

Example 1: Creating JSON Number Object from User Input

The specification of values for JSON numbers often starts with user input textual representations of those values. As the specifications for valid representation are complicated and not always intuitive it is prudent to validate that input using the bdljsn::JsonNumber::isValidNumber function; otherwise, one might try to create a bdljsn::JsonNumber object from an invalid specification and that leads to undefined behavior.

First, as a expedient for this example, we organize in an array input that might well be entered by some user:

struct {
const char *d_text_p;
const char *d_description_p;
bool d_expected;
} USER_INPUT[] = {
// VALUE DESCRIPTION EXP
// ---------------------- -------------------------------------- ---
// Invalid Input (that is valid in other contexts).
{ "1.", "Not uncommon way to write '1'." , 0 }
, { "1,000", "No commas allowed" , 0 }
, { "01", "Leading '0', disallowed by JSON." , 0 }
, { "", "0 per 'atoi', disallowed by JSON." , 0 }
, { "Hello, world!", "0 per 'atoi', disallowed by JSON." , 0 }
, { "NaN", "invalid number" , 0 }
, { "INF", "invalid number" , 0 }
, { "-INF", "invalid number" , 0 }
, { "+INF", "invalid number" , 0 }
// Valid input (some surprising)
, { "1234567890", "Integral value" , 1 }
, { "1234567890.123456", "Non-integral value" , 1 }
, { "1234567890.1234567", "Beyond Decimal64 precision" , 1 }
, { "-9223372036854775809", "INT64_MIN, underflow, but valid JSON", 1 }
, { "1.5e27", "INT64_MAX, overflow, but valid JSON", 1 }
, { "999999999999999999999999999999999999999999999999999999999999"
"e"
"999999999999999999999999999999999999999999999999999999999999",
"astronomic value" , 1 }
};
const bsl::size_t NUM_USER_INPUT = sizeof USER_INPUT / sizeof *USER_INPUT;

Now, if and only if the input is valid, we use the input to construct a bdljsn::JsonNumber object and add that object to a vector for later processing.

bsl::vector<bdljsn::JsonNumber> userInput; // when valid input
for (bsl::size_t ti = 0; ti < NUM_USER_INPUT; ++ti) {
const char *TEXT = USER_INPUT[ti].d_text_p;
const char *DESC = USER_INPUT[ti].d_description_p; (void) DESC;
const bool EXP = USER_INPUT[ti].d_expected;
const bool isValid = bdljsn::JsonNumber::isValidNumber(TEXT);
assert(EXP == isValid);
if (isValid) {
userInput.push_back(bdljsn::JsonNumber(TEXT));
}
}
static bool isValidNumber(const bsl::string_view &text)
Definition bdljsn_jsonnumber.h:722
Definition bslstl_vector.h:1025
void push_back(const VALUE_TYPE &value)
Definition bslstl_vector.h:3760

Finally, we confirm that the vector has the expected number of elements:

assert(6 == userInput.size());
size_type size() const BSLS_KEYWORD_NOEXCEPT
Return the number of elements in this vector.
Definition bslstl_vector.h:2664

Example 2: Using bdljsn::JsonNumber Objects

We saw in {Example 1} that bdljsn::JsonNumber objects can validly hold values numeric values that cannot be converted to any of the supported types (e.g., the "astronomic value") for arithmetic operations. Applications that accept arbitrary bdljsn::JsonNumber objects should be prepared to categorize the contained value and adapt their handling accordingly. In practice, applications may have some assurances of the contents of received bdljsn::JsonNumber objects. Here, we intentionally avoid such assumptions to explore the wide range of variations that can arise.

Legend, in the output below:

First, we set up a framework (in this case, a for loop) for examining our input, the same userInput vector created in {Example 1}:

for (bsl::size_t i = 0; i < userInput.size(); ++i) {
const bdljsn::JsonNumber obj = userInput[i];

Then, we categorize the value as integral or not:

if (obj.isIntegral()) {
bsl::cout << "Integral: ";
bool isIntegral() const
Definition bdljsn_jsonnumber.h:946

If integral, we check if the value is a usable range. Let us assume that bslsl::Type::Int64 is as large a number as we can accept.

Then, we convert the JSON number to that type and check for overflow and underflow:

int rc = obj.asInt64(&value);
switch (rc) {
case 0: {
bsl::cout << value << " : OK to USE" << bsl::endl;
} break;
bsl::cout << obj.value() << ": NG too large" << bsl::endl;
} break;
bsl::cout << obj.value() << ": NG too small" << bsl::endl;
} break;
assert(0 == "reached");
} break;
}
@ k_UNDERFLOW
Definition bdljsn_jsonnumber.h:424
@ k_NOT_INTEGRAL
Definition bdljsn_jsonnumber.h:425
@ k_OVERFLOW
Definition bdljsn_jsonnumber.h:423
const bsl::string & value() const
Return the textual representation of this JsonNumber.
Definition bdljsn_jsonnumber.h:952
int asInt64(bsls::Types::Int64 *result) const
Definition bdljsn_jsonnumber.h:969
long long Int64
Definition bsls_types.h:132

Next, if the value is not integral, we try to handle it as a floating point value – a bdldfp::Decimal64 in this example – and further categorize it as exact/inexact, too large/small.

} else {
bsl::cout << "Not-Integral: ";
int rc = obj.asDecimal64Exact(&value);
switch (rc) {
case 0: {
bsl::cout << value << " : exact: OK to USE";
} break;
bsl::cout << value << ": inexact: USE approximation";
} break;
assert(0 == "reached");
} break;
}
const bdldfp::Decimal64 INF =
bsl::numeric_limits<bdldfp::Decimal64>::infinity();
if ( INF == value) {
bsl::cout << ": NG too large" << bsl::endl;
} else if (-INF == value) {
bsl::cout << ": NG too small" << bsl::endl;
} else {
bsl::cout << bsl::endl;
}
}
}
Definition bdldfp_decimal.h:1834
@ k_INEXACT
Definition bdljsn_jsonnumber.h:428
int asDecimal64Exact(bdldfp::Decimal64 *result) const
Definition bdljsn_jsonnumber.h:1007

Finally, we observe for particular input:

Integral: 1234567890 : OK to USE
Not-Integral: 1234567890.123456 : exact: OK to USE
Not-Integral: 1234567890.123457: inexact: USE approximation
Integral: -9223372036854775809: NG too small
Integral: 1.5e27: NG too large
Integral: 999999999999999999999999999999999999999999999999999999999999e9999
99999999999999999999999999999999999999999999999999999999: NG too large