JSON: Nan/Inf code review tweaks.

Replace Flag!"JsonSpecialFloats" with a flagset enum to allow future
extensibility.
Add more documentation to modified functions.
This commit is contained in:
Ryan 2015-04-13 22:36:47 -04:00
parent c33dca96be
commit fecf9b3972

View file

@ -70,9 +70,12 @@ enum JSONFloatLiteral : string
} }
/** /**
Flag that enables encoding special float values (NaN/Inf) as strings. Flags that control how json is encoded and parsed.
*/ */
alias JsonSpecialFloats = Flag!"JsonSpecialFloats"; enum JsonOptions {
none, /// standard parsing
specialFloatLiterals = 0x1, /// encode NaN and Inf float values as strings
}
/** /**
JSON type enumeration JSON type enumeration
@ -611,25 +614,30 @@ struct JSONValue
} }
/// Implicitly calls $(D toJSON) on this JSONValue. /// Implicitly calls $(D toJSON) on this JSONValue.
/// $(I specialFloats) will enable encoding NaN/Inf as strings. /// $(I options) can be used to tweak the conversion behavior.
string toString(in JsonSpecialFloats specialFloats = JsonSpecialFloats.no) const string toString(in JsonOptions options = JsonOptions.none) const
{ {
return toJSON(&this, false, specialFloats); return toJSON(&this, false, options);
} }
/// Implicitly calls $(D toJSON) on this JSONValue, like $(D toString), but /// Implicitly calls $(D toJSON) on this JSONValue, like $(D toString), but
/// also passes $(I true) as $(I pretty) argument. /// also passes $(I true) as $(I pretty) argument.
/// $(I specialFloats) will enable encoding NaN/Inf as strings. /// $(I options) can be used to tweak the conversion behavior
string toPrettyString(in JsonSpecialFloats specialFloats = JsonSpecialFloats.no) const string toPrettyString(in JsonOptions options = JsonOptions.none) const
{ {
return toJSON(&this, true, specialFloats); return toJSON(&this, true, options);
} }
} }
/** /**
Parses a serialized string and returns a tree of JSON values. Parses a serialized string and returns a tree of JSON values.
Throws a $(XREF json,JSONException) if the depth exceeds the max depth.
Params:
json = json-formatted string to parse
maxDepth = maximum depth of nesting allowed, -1 disables depth checking
options = enable decoding string representations of NaN/Inf as float values
*/ */
JSONValue parseJSON(T)(T json, int maxDepth = -1, JsonSpecialFloats specialFloats = JsonSpecialFloats.no) JSONValue parseJSON(T)(T json, int maxDepth = -1, JsonOptions options = JsonOptions.none)
if(isInputRange!T) if(isInputRange!T)
{ {
import std.ascii : isWhite, isDigit, isHexDigit, toUpper, toLower; import std.ascii : isWhite, isDigit, isHexDigit, toUpper, toLower;
@ -842,7 +850,9 @@ if(isInputRange!T)
auto str = parseString(); auto str = parseString();
// if special float parsing is enabled, check if string represents NaN/Inf // if special float parsing is enabled, check if string represents NaN/Inf
if (specialFloats && tryGetSpecialFloat(str, value.store.floating)) { if ((options & JsonOptions.specialFloatLiterals) &&
tryGetSpecialFloat(str, value.store.floating))
{
// found a special float, its value was placed in value.store.floating // found a special float, its value was placed in value.store.floating
value.type_tag = JSON_TYPE.FLOAT; value.type_tag = JSON_TYPE.FLOAT;
break; break;
@ -951,11 +961,15 @@ if(isInputRange!T)
/** /**
Parses a serialized string and returns a tree of JSON values. Parses a serialized string and returns a tree of JSON values.
Throws a $(XREF json,JSONException) if the depth exceeds the max depth.
Params:
json = json-formatted string to parse
options = enable decoding string representations of NaN/Inf as float values
*/ */
JSONValue parseJSON(T)(T json, JsonSpecialFloats specialFloats) JSONValue parseJSON(T)(T json, JsonOptions options)
if(isInputRange!T) if(isInputRange!T)
{ {
return parseJSON!T(json, -1, specialFloats); return parseJSON!T(json, -1, options);
} }
/** /**
@ -965,9 +979,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 false no whitespaces are generated.
If $(D pretty) is true serialized string is formatted to be human-readable. If $(D pretty) is true serialized string is formatted to be human-readable.
If $(D specialFloats) is JsonSpecialFloats.yes, encode special floats (NaN/Infinity) as strings. Set the $(specialFloatLiterals) flag is set in $(D options) to encode NaN/Infinity as strings.
*/ */
string toJSON(in JSONValue* root, in bool pretty = false, in JsonSpecialFloats specialFloats = JsonSpecialFloats.no) string toJSON(in JSONValue* root, in bool pretty = false, in JsonOptions options = JsonOptions.none)
{ {
auto json = appender!string(); auto json = appender!string();
@ -1094,21 +1108,21 @@ string toJSON(in JSONValue* root, in bool pretty = false, in JsonSpecialFloats s
auto val = value.store.floating; auto val = value.store.floating;
if (val.isNaN) { if (val.isNaN) {
if (specialFloats) { if (options & JsonOptions.specialFloatLiterals) {
toString(JSONFloatLiteral.nan); toString(JSONFloatLiteral.nan);
} }
else { else {
throw new JSONException( throw new JSONException(
"Cannot encode NaN. Consider passing the specialFloats flag."); "Cannot encode NaN. Consider passing the specialFloatLiterals flag.");
} }
} }
else if (val.isInfinity) { else if (val.isInfinity) {
if (specialFloats) { if (options & JsonOptions.specialFloatLiterals) {
toString((val > 0) ? JSONFloatLiteral.inf : JSONFloatLiteral.negativeInf); toString((val > 0) ? JSONFloatLiteral.inf : JSONFloatLiteral.negativeInf);
} }
else { else {
throw new JSONException( throw new JSONException(
"Cannot encode Infinity. Consider passing the specialFloats flag."); "Cannot encode Infinity. Consider passing the specialFloatLiterals flag.");
} }
} }
else { else {
@ -1489,26 +1503,26 @@ unittest
negativeInfString = '"' ~ JSONFloatLiteral.negativeInf ~ '"', negativeInfString = '"' ~ JSONFloatLiteral.negativeInf ~ '"',
} }
// when passing JsonSpecialFloats.yes, encode NaN/Inf as strings // with the specialFloatLiterals option, encode NaN/Inf as strings
assert(JSONValue(float.nan).toString(JsonSpecialFloats.yes) == nanString); assert(JSONValue(float.nan).toString(JsonOptions.specialFloatLiterals) == nanString);
assert(JSONValue(double.infinity).toString(JsonSpecialFloats.yes) == infString); assert(JSONValue(double.infinity).toString(JsonOptions.specialFloatLiterals) == infString);
assert(JSONValue(-real.infinity).toString(JsonSpecialFloats.yes) == negativeInfString); assert(JSONValue(-real.infinity).toString(JsonOptions.specialFloatLiterals) == negativeInfString);
// when passing JsonSpecialFloats.no, thow on converting NaN/Inf // without the specialFloatLiterals option, throw on encoding NaN/Inf
assertThrown!JSONException(JSONValue(float.nan).toString); assertThrown!JSONException(JSONValue(float.nan).toString);
assertThrown!JSONException(JSONValue(double.infinity).toString); assertThrown!JSONException(JSONValue(double.infinity).toString);
assertThrown!JSONException(JSONValue(-real.infinity).toString); assertThrown!JSONException(JSONValue(-real.infinity).toString);
// when parsing json with JsonSpecialFloats.yes, decode special strings as floats // when parsing json with specialFloatLiterals option, decode special strings as floats
JSONValue jvNan = parseJSON(nanString, JsonSpecialFloats.yes); JSONValue jvNan = parseJSON(nanString, JsonOptions.specialFloatLiterals);
JSONValue jvInf = parseJSON(infString, JsonSpecialFloats.yes); JSONValue jvInf = parseJSON(infString, JsonOptions.specialFloatLiterals);
JSONValue jvNegInf = parseJSON(negativeInfString, JsonSpecialFloats.yes); JSONValue jvNegInf = parseJSON(negativeInfString, JsonOptions.specialFloatLiterals);
assert(jvNan.floating.isNaN); assert(jvNan.floating.isNaN);
assert(jvInf.floating.isInfinity && jvInf.floating > 0); assert(jvInf.floating.isInfinity && jvInf.floating > 0);
assert(jvNegInf.floating.isInfinity && jvNegInf.floating < 0); assert(jvNegInf.floating.isInfinity && jvNegInf.floating < 0);
// when parsing json with JsonSpecialFloats.no, decode special strings as strings // when parsing json without the specialFloatLiterals option, decode special strings as strings
jvNan = parseJSON(nanString); jvNan = parseJSON(nanString);
jvInf = parseJSON(infString); jvInf = parseJSON(infString);
jvNegInf = parseJSON(negativeInfString); jvNegInf = parseJSON(negativeInfString);