JSON: option to decode special strings as floats.

Passing JsonSpecialFloats.yes to parseJSON prompts the parser to interpret
"NaN", "Infinite", and "-Infinite" as the special floating-point values they
represent.

Adds the overload parseJSON(JSONValue, SpecialFloats) that takes a JSONValue and
the JsonSpecialFloats flag so users do not have to pass maxDepth in order to
specify special float handling behavior.
This commit is contained in:
Ryan 2015-04-10 14:11:19 -04:00
parent ef0b39334d
commit c33dca96be

View file

@ -72,7 +72,7 @@ enum JSONFloatLiteral : string
/**
Flag that enables encoding special float values (NaN/Inf) as strings.
*/
alias SpecialFloats = Flag!"SpecialFloats";
alias JsonSpecialFloats = Flag!"JsonSpecialFloats";
/**
JSON type enumeration
@ -612,7 +612,7 @@ struct JSONValue
/// Implicitly calls $(D toJSON) on this JSONValue.
/// $(I specialFloats) will enable encoding NaN/Inf as strings.
string toString(in SpecialFloats specialFloats = SpecialFloats.no) const
string toString(in JsonSpecialFloats specialFloats = JsonSpecialFloats.no) const
{
return toJSON(&this, false, specialFloats);
}
@ -620,7 +620,7 @@ struct JSONValue
/// Implicitly calls $(D toJSON) on this JSONValue, like $(D toString), but
/// also passes $(I true) as $(I pretty) argument.
/// $(I specialFloats) will enable encoding NaN/Inf as strings.
string toPrettyString(in SpecialFloats specialFloats = SpecialFloats.no) const
string toPrettyString(in JsonSpecialFloats specialFloats = JsonSpecialFloats.no) const
{
return toJSON(&this, true, specialFloats);
}
@ -629,7 +629,8 @@ struct JSONValue
/**
Parses a serialized string and returns a tree of JSON values.
*/
JSONValue parseJSON(T)(T json, int maxDepth = -1) if(isInputRange!T)
JSONValue parseJSON(T)(T json, int maxDepth = -1, JsonSpecialFloats specialFloats = JsonSpecialFloats.no)
if(isInputRange!T)
{
import std.ascii : isWhite, isDigit, isHexDigit, toUpper, toLower;
import std.utf : toUTF8;
@ -769,6 +770,22 @@ JSONValue parseJSON(T)(T json, int maxDepth = -1) if(isInputRange!T)
return str.data.length ? str.data : "";
}
bool tryGetSpecialFloat(string str, out double val) {
switch(str) {
case JSONFloatLiteral.nan:
val = double.nan;
return true;
case JSONFloatLiteral.inf:
val = double.infinity;
return true;
case JSONFloatLiteral.negativeInf:
val = -double.infinity;
return true;
default:
return false;
}
}
void parseValue(JSONValue* value)
{
depth++;
@ -822,8 +839,17 @@ JSONValue parseJSON(T)(T json, int maxDepth = -1) if(isInputRange!T)
break;
case '"':
auto str = parseString();
// if special float parsing is enabled, check if string represents NaN/Inf
if (specialFloats && tryGetSpecialFloat(str, value.store.floating)) {
// found a special float, its value was placed in value.store.floating
value.type_tag = JSON_TYPE.FLOAT;
break;
}
value.type_tag = JSON_TYPE.STRING;
value.store.str = parseString();
value.store.str = str;
break;
case '0': .. case '9':
@ -923,6 +949,15 @@ JSONValue parseJSON(T)(T json, int maxDepth = -1) if(isInputRange!T)
return root;
}
/**
Parses a serialized string and returns a tree of JSON values.
*/
JSONValue parseJSON(T)(T json, JsonSpecialFloats specialFloats)
if(isInputRange!T)
{
return parseJSON!T(json, -1, specialFloats);
}
/**
Takes a tree of JSON values and returns the serialized string.
@ -930,9 +965,9 @@ Any Object types will be serialized in a key-sorted order.
If $(D pretty) is false no whitespaces are generated.
If $(D pretty) is true serialized string is formatted to be human-readable.
If $(D specialFloats) is SpecialFloats.yes, encode special floats (NaN/Infinity) as strings.
If $(D specialFloats) is JsonSpecialFloats.yes, encode special floats (NaN/Infinity) as strings.
*/
string toJSON(in JSONValue* root, in bool pretty = false, in SpecialFloats specialFloats = SpecialFloats.no)
string toJSON(in JSONValue* root, in bool pretty = false, in JsonSpecialFloats specialFloats = JsonSpecialFloats.no)
{
auto json = appender!string();
@ -1444,15 +1479,41 @@ EOF";
// handling of special float values (NaN, Inf, -Inf)
unittest
{
import std.math : isNaN, isInfinity;
import std.exception : assertThrown;
// when passing SpecialFloats.yes, encode NaN/Inf as strings
assert(JSONValue(float.nan).toString(SpecialFloats.yes) == q{"NaN"});
assert(JSONValue(double.infinity).toString(SpecialFloats.yes) == q{"Infinite"});
assert(JSONValue(-real.infinity).toString(SpecialFloats.yes) == q{"-Infinite"});
// expected representations of NaN and Inf
enum {
nanString = '"' ~ JSONFloatLiteral.nan ~ '"',
infString = '"' ~ JSONFloatLiteral.inf ~ '"',
negativeInfString = '"' ~ JSONFloatLiteral.negativeInf ~ '"',
}
// when passing SpecialFloats.no, thow on converting NaN/Inf
// when passing JsonSpecialFloats.yes, encode NaN/Inf as strings
assert(JSONValue(float.nan).toString(JsonSpecialFloats.yes) == nanString);
assert(JSONValue(double.infinity).toString(JsonSpecialFloats.yes) == infString);
assert(JSONValue(-real.infinity).toString(JsonSpecialFloats.yes) == negativeInfString);
// when passing JsonSpecialFloats.no, thow on converting NaN/Inf
assertThrown!JSONException(JSONValue(float.nan).toString);
assertThrown!JSONException(JSONValue(double.infinity).toString);
assertThrown!JSONException(JSONValue(-real.infinity).toString);
// when parsing json with JsonSpecialFloats.yes, decode special strings as floats
JSONValue jvNan = parseJSON(nanString, JsonSpecialFloats.yes);
JSONValue jvInf = parseJSON(infString, JsonSpecialFloats.yes);
JSONValue jvNegInf = parseJSON(negativeInfString, JsonSpecialFloats.yes);
assert(jvNan.floating.isNaN);
assert(jvInf.floating.isInfinity && jvInf.floating > 0);
assert(jvNegInf.floating.isInfinity && jvNegInf.floating < 0);
// when parsing json with JsonSpecialFloats.no, decode special strings as strings
jvNan = parseJSON(nanString);
jvInf = parseJSON(infString);
jvNegInf = parseJSON(negativeInfString);
assert(jvNan.str == JSONFloatLiteral.nan);
assert(jvInf.str == JSONFloatLiteral.inf);
assert(jvNegInf.str == JSONFloatLiteral.negativeInf);
}