Skip to content

Commit

Permalink
Merge pull request #38 from Zalastax/master
Browse files Browse the repository at this point in the history
Automatic camelCase conversion
  • Loading branch information
BlackEdder committed Apr 30, 2015
2 parents 4de0fca + 663c7a6 commit 62f4567
Show file tree
Hide file tree
Showing 4 changed files with 126 additions and 33 deletions.
6 changes: 5 additions & 1 deletion source/painlessjson/annotations.d
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
module painlessjson.annotations;

import painlessjson.traits;
import painlessjson.string;

struct SerializedToName
{
Expand Down Expand Up @@ -41,7 +42,7 @@ struct SerializeFromIgnore
{
}

template serializationToName(alias T, string defaultName)
template serializationToName(alias T, string defaultName, bool alsoAcceptUnderscore)
{
static string helper()
{
Expand All @@ -53,6 +54,9 @@ template serializationToName(alias T, string defaultName)
&& getAnnotation!(T, SerializedToName).name)
{
return getAnnotation!(T, SerializedToName).name;
} else static if(alsoAcceptUnderscore)
{
return camelCaseToUnderscore(defaultName);
}
else
{
Expand Down
107 changes: 75 additions & 32 deletions source/painlessjson/painlessjson.d
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
module painlessjson.painlessjson;

import std.string;
import std.conv;
import std.json;
import std.range;
import std.traits;
import std.typecons : TypeTuple;
import painlessjson.traits;
import painlessjson.annotations;
import painlessjson.string;

version (unittest)
{
Expand All @@ -18,16 +20,28 @@ version (unittest)

}

struct SerializationOptions
{
bool alsoAcceptUnderscore;
bool convertToUnderscore;
}

enum defaultSerializatonOptions = SerializationOptions(true, false);





//See if we can use something else than __traits(compiles, (T t){JSONValue(t);})
private JSONValue defaultToJSONImpl(T)(in T object) if (__traits(compiles, (in T t) {
private JSONValue defaultToJSONImpl(T, SerializationOptions options)(in T object) if (__traits(compiles, (in T t) {
JSONValue(t);
}))
{
return JSONValue(object);
}

//See if we can use something else than !__traits(compiles, (T t){JSONValue(t);})
private JSONValue defaultToJSONImpl(T)(in T object) if (isArray!T && !__traits(compiles, (in T t) {
private JSONValue defaultToJSONImpl(T, SerializationOptions options)(in T object) if (isArray!T && !__traits(compiles, (in T t) {
JSONValue(t);
}))
{
Expand All @@ -36,7 +50,7 @@ private JSONValue defaultToJSONImpl(T)(in T object) if (isArray!T && !__traits(c
return JSONValue(jsonRange);
}

private JSONValue defaultToJSONImpl(T)(in T object) if (isAssociativeArray!T)
private JSONValue defaultToJSONImpl(T, SerializationOptions options)(in T object) if (isAssociativeArray!T)
{
JSONValue[string] jsonAA;
foreach (key, value; object)
Expand All @@ -46,7 +60,7 @@ private JSONValue defaultToJSONImpl(T)(in T object) if (isAssociativeArray!T)
return JSONValue(jsonAA);
}

private JSONValue defaultToJSONImpl(T)(in T object) if (!isBuiltinType!T
private JSONValue defaultToJSONImpl(T, SerializationOptions options )(in T object) if (!isBuiltinType!T
&& !__traits(compiles, (in T t) { JSONValue(t); }))
{
JSONValue[string] json;
Expand All @@ -55,13 +69,13 @@ private JSONValue defaultToJSONImpl(T)(in T object) if (!isBuiltinType!T
{
static if (__traits(compiles,
{
json[serializationToName!(__traits(getMember, object, name), name)] = __traits(getMember,
json[serializationToName!(__traits(getMember, object, name), name, false)] = __traits(getMember,
object, name).toJSON;
}) && !hasAnyOfTheseAnnotations!(__traits(getMember, object,
name), SerializeIgnore, SerializeToIgnore)
&& isFieldOrProperty!(__traits(getMember, object, name)))
{
json[serializationToName!(__traits(getMember, object, name), name)] = __traits(getMember,
json[serializationToName!(__traits(getMember, object, name), name,(options.convertToUnderscore))] = __traits(getMember,
object, name).toJSON;
}
}
Expand All @@ -72,19 +86,19 @@ private JSONValue defaultToJSONImpl(T)(in T object) if (!isBuiltinType!T
Convert any type to JSON<br />
Can be overridden by &#95;toJSON
+/
JSONValue defaultToJSON(T)(in T t){
return defaultToJSONImpl(t);
JSONValue defaultToJSON(T, SerializationOptions options = defaultSerializatonOptions)(in T t){
return defaultToJSONImpl!(T, options)(t);
}

/// Template function that converts any object to JSON
JSONValue toJSON(T)(in T t)
JSONValue toJSON(T, SerializationOptions options = defaultSerializatonOptions)(in T t)
{
static if (__traits(compiles, (in T t) { return t._toJSON(); }))
{
return t._toJSON();
}
else
return defaultToJSON!T(t);
return defaultToJSON!(T, options)(t);
}

/// Converting common types
Expand Down Expand Up @@ -201,6 +215,19 @@ unittest
assertEqual(point, fromJSON!(Tuple!(int, "x", int, "y"))(parseJSON(q{{"x":5,"y":6}})));
}

/// Convert camel case to underscore automatically
unittest
{
CamelCaseConversion value;
value.wasCamelCase = 5;
value.was_underscore = 7;

auto valueAsJSON = value.toJSON!(CamelCaseConversion,SerializationOptions(false, true));

assertEqual(valueAsJSON["was_camel_case"].integer, 5);
assertEqual(valueAsJSON["was_underscore"].integer, 7);
}

/// Overloaded toJSON
unittest
{
Expand Down Expand Up @@ -258,44 +285,44 @@ unittest
assertEqual(z.toJSON["add"].str, "bla");
}

private T defaultFromJSONImpl(T)(in JSONValue json) if (is(T == JSONValue))
private T defaultFromJSONImpl(T, SerializationOptions options)(in JSONValue json) if (is(T == JSONValue))
{
return json;
}

private T defaultFromJSONImpl(T)(in JSONValue json) if (isIntegral!T)
private T defaultFromJSONImpl(T, SerializationOptions options)(in JSONValue json) if (isIntegral!T)
{
return to!T(json.integer);
}

private T defaultFromJSONImpl(T)(in JSONValue json) if (isFloatingPoint!T)
private T defaultFromJSONImpl(T, SerializationOptions options)(in JSONValue json) if (isFloatingPoint!T)
{
if (json.type == JSON_TYPE.INTEGER)
return to!T(json.integer);
else
return to!T(json.floating);
}

private T defaultFromJSONImpl(T)(in JSONValue json) if (is(T == string))
private T defaultFromJSONImpl(T, SerializationOptions options)(in JSONValue json) if (is(T == string))
{
return to!T(json.str);
}

private T defaultFromJSONImpl(T)(in JSONValue json) if (isBoolean!T)
private T defaultFromJSONImpl(T, SerializationOptions options)(in JSONValue json) if (isBoolean!T)
{
if (json.type == JSON_TYPE.TRUE)
return true;
else
return false;
}

private T defaultFromJSONImpl(T)(in JSONValue json) if (isArray!T && !is(T == string))
private T defaultFromJSONImpl(T, SerializationOptions options)(in JSONValue json) if (isArray!T && !is(T == string))
{
T t; //Se is we can find another way of finding t.front
return map!((js) => fromJSON!(typeof(t.front))(js))(json.array).array;
}

private T defaultFromJSONImpl(T)(in JSONValue json) if (isAssociativeArray!T)
private T defaultFromJSONImpl(T, SerializationOptions options)(in JSONValue json) if (isAssociativeArray!T)
{
T t;
const JSONValue[string] jsonAA = json.object;
Expand All @@ -310,7 +337,7 @@ private T defaultFromJSONImpl(T)(in JSONValue json) if (isAssociativeArray!T)
Convert to given type from JSON.<br />
Can be overridden by &#95;fromJSON.
+/
private T defaultFromJSONImpl(T)(in JSONValue json) if (!isBuiltinType!T && !is(T == JSONValue))
private T defaultFromJSONImpl(T, SerializationOptions options)(in JSONValue json) if (!isBuiltinType!T && !is(T == JSONValue))
{
static if (hasAccessibleDefaultsConstructor!(T))
{
Expand All @@ -337,6 +364,12 @@ private T defaultFromJSONImpl(T)(in JSONValue json) if (!isBuiltinType!T && !is
mixin(
"if ( \"" ~ fromName ~ "\" in jsonAA) t." ~ name ~ "= fromJSON!(" ~ fullyQualifiedName!(
typeof(__traits(getMember, t, name))) ~ ")(jsonAA[\"" ~ fromName ~ "\"]);");
static if(options.alsoAcceptUnderscore)
{
mixin(
"if ( \"" ~ camelCaseToUnderscore(fromName) ~ "\" in jsonAA) t." ~ name ~ "= fromJSON!(" ~ fullyQualifiedName!(
typeof(__traits(getMember, t, name))) ~ ")(jsonAA[\"" ~ camelCaseToUnderscore(fromName) ~ "\"]);");
}
}
}
return t;
Expand All @@ -355,16 +388,16 @@ private T defaultFromJSONImpl(T)(in JSONValue json) if (!isBuiltinType!T && !is
{
static if (__traits(compiles,
{
return getInstanceFromCustomConstructor!(T, overload)(json);
return getInstanceFromCustomConstructor!(T, overload, false)(json);
}))
{
if (jsonValueHasAllFieldsNeeded!(overload)(json))
if (jsonValueHasAllFieldsNeeded!(overload, (options.alsoAcceptUnderscore))(json))
{
ulong overloadScore = constructorOverloadScore!(overload)(json);
ulong overloadScore = constructorOverloadScore!(overload, (options.alsoAcceptUnderscore))(json);
if (overloadScore < bestOverloadScore)
{
bestOverload = function(JSONValue value) {
return getInstanceFromCustomConstructor!(T, overload)(value);
return getInstanceFromCustomConstructor!(T, overload, (options.alsoAcceptUnderscore))(value);
};
bestOverloadScore = overloadScore;
}
Expand All @@ -385,8 +418,8 @@ private T defaultFromJSONImpl(T)(in JSONValue json) if (!isBuiltinType!T && !is
Convert to given type from JSON.<br />
Can be overridden by &#95;fromJSON.
+/
T defaultFromJSON(T)(in JSONValue json){
return defaultFromJSONImpl!T(json);
T defaultFromJSON(T, SerializationOptions options = defaultSerializatonOptions)(in JSONValue json){
return defaultFromJSONImpl!(T, options)(json);
}

template hasAccessibleDefaultsConstructor(T)
Expand All @@ -412,7 +445,7 @@ T getIntstanceFromDefaultConstructor(T)()
}
}

T getInstanceFromCustomConstructor(T, alias Ctor)(in JSONValue json)
T getInstanceFromCustomConstructor(T, alias Ctor, bool alsoAcceptUnderscore)(in JSONValue json)
{
import std.typecons : staticIota;

Expand All @@ -426,6 +459,8 @@ T getInstanceFromCustomConstructor(T, alias Ctor)(in JSONValue json)
if (paramName in json.object)
{
args[i] = fromJSON!(Types[i])(json[paramName]);
} else if( alsoAcceptUnderscore && camelCaseToUnderscore(paramName)){
args[i] = fromJSON!(Types[i])(json[camelCaseToUnderscore(paramName)]);
}
else
{
Expand All @@ -451,7 +486,7 @@ T getInstanceFromCustomConstructor(T, alias Ctor)(in JSONValue json)
}
}

bool jsonValueHasAllFieldsNeeded(alias Ctor)(in JSONValue json)
bool jsonValueHasAllFieldsNeeded(alias Ctor, bool alsoAcceptUnderscore)(in JSONValue json)
{
import std.typecons : staticIota;

Expand All @@ -462,15 +497,15 @@ bool jsonValueHasAllFieldsNeeded(alias Ctor)(in JSONValue json)
foreach (i; staticIota!(0, params.length))
{
enum paramName = params[i];
if (!(paramName in json.object) && is(defaults[i] == void))
if (!((paramName in json.object) || ( alsoAcceptUnderscore && (camelCaseToUnderscore(paramName) in json.object))) && is(defaults[i] == void))
{
return false;
}
}
return true;
}

ulong constructorOverloadScore(alias Ctor)(in JSONValue json)
ulong constructorOverloadScore(alias Ctor, bool alsoAcceptUnderscore)(in JSONValue json)
{
import std.typecons : staticIota;

Expand All @@ -482,7 +517,7 @@ ulong constructorOverloadScore(alias Ctor)(in JSONValue json)
foreach (i; staticIota!(0, params.length))
{
enum paramName = params[i];
if (paramName in json.object)
if (paramName in json.object || ( alsoAcceptUnderscore && (camelCaseToUnderscore(paramName) in json.object)))
{
overloadScore--;
}
Expand All @@ -500,7 +535,7 @@ template hasAccessibleConstructor(T)
foreach (overload; Overloads)
{
if (__traits(compiles, getInstanceFromCustomConstructor!(T,
overload)(JSONValue())))
overload, false)(JSONValue())))
{
return true;
}
Expand All @@ -513,14 +548,14 @@ template hasAccessibleConstructor(T)
}

/// Convert from JSONValue to any other type
T fromJSON(T)(in JSONValue json)
T fromJSON(T, SerializationOptions options = defaultSerializatonOptions)(in JSONValue json)
{
static if (__traits(compiles, { return T._fromJSON(json); }))
{
return T._fromJSON(json);
}
else
return defaultFromJSON!T(json);
return defaultFromJSON!(T,options)(json);
}

/// Converting common types
Expand Down Expand Up @@ -664,3 +699,11 @@ unittest
assertEqual(person.id, 34);
assertEqual(person.name, "Undefined");
}

/// Accept underscore and convert it to camelCase automatically
unittest
{
auto value = fromJSON!CamelCaseConversion(parseJSON(q{{"was_camel_case":8,"was_underscore":9}}));
assertEqual(value.wasCamelCase, 8);
assertEqual(value.was_underscore, 9);
}
40 changes: 40 additions & 0 deletions source/painlessjson/string.d
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
module painlessjson.string;

import std.string;
import std.conv;
import std.array;
import std.ascii : isLower, isUpper;

version (unittest)
{
import dunit.toolkit;

}

unittest {
enum hello_world = camelCaseToUnderscore("helloWorld");
assertEqual(hello_world,"hello_world");
enum my_json = camelCaseToUnderscore("myJSON");
assertEqual(my_json,"my_json");
}

string camelCaseToUnderscore(string input){
auto stringBuilder = appender!string;
stringBuilder.reserve(input.length*2);
bool previousWasLower = false;
foreach(c;input){
if(previousWasLower && c.isUpper())
{
stringBuilder.put('_');
}

if(c.isLower())
{
previousWasLower = true;
} else{
previousWasLower = false;
}
stringBuilder.put(c);
}
return stringBuilder.data.toLower();
}
Loading

0 comments on commit 62f4567

Please sign in to comment.