├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── autotest.sh ├── dub.json ├── source └── painlessjson │ ├── annotations.d │ ├── package.d │ ├── painlessjson.d │ ├── string.d │ ├── unittesttypes.d │ └── unittesttypes_local_import.d └── travis.sh /.gitignore: -------------------------------------------------------------------------------- 1 | *.a 2 | .dub/ 3 | docs* 4 | __* 5 | .*.swp 6 | .*.swo 7 | bin/ 8 | .stversions 9 | dub.selections.json 10 | tags 11 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: d 2 | script: "./travis.sh" 3 | sudo: false 4 | matrix: 5 | include: 6 | - d: dmd 7 | env: 8 | - secure: F1pwSI3BTHI/W0vaUNr2HeS7+25jLJiH2CF9pTW7cySKJGJkyOJ6gJkv71HYxq/cYVda8Y7qy+a//lZFQeWlEmMW4FTblXTE0qVvUzfCw3VRKLGYIMMMi2JzyWJODH+8Q2m19mS9OneZR5zL2NmopJn6wGrfUWZZuerk3XP57C0= 9 | - USE_NETWORK=true 10 | - USE_NETWORK=false 11 | - d: dmd-2.087.0 12 | - d: dmd-2.086.1 13 | - d: dmd-2.085.1 14 | - d: dmd-2.084.1 15 | - d: dmd-2.083.1 16 | - d: dmd-2.078.3 17 | - d: dmd-2.077.1 18 | - d: dmd-2.076.1 19 | - d: ldc 20 | - d: ldc-1.16.0 21 | - d: ldc-1.15.0 22 | - d: ldc-1.14.0 23 | - d: ldc-1.13.0 24 | - d: ldc-1.7.0 25 | - d: ldc-1.6.0 26 | 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Boost Software License - Version 1.0 - August 17th, 2003 2 | 3 | Permission is hereby granted, free of charge, to any person or organization 4 | obtaining a copy of the software and accompanying documentation covered by 5 | this license (the "Software") to use, reproduce, display, distribute, 6 | execute, and transmit the Software, and to prepare derivative works of the 7 | Software, and to permit third-parties to whom the Software is furnished to 8 | do so, all subject to the following: 9 | 10 | The copyright notices in the Software and this entire statement, including 11 | the above license grant, this restriction and the following disclaimer, 12 | must be included in all copies of the Software, in whole or in part, and 13 | all derivative works of the Software, unless such copies or derivative 14 | works are solely in the form of machine-executable object code generated by 15 | a source language processor. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 20 | SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 21 | FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 22 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Painless JSON [![Build Status](https://travis-ci.org/BlackEdder/painlessjson.svg?branch=master)](https://travis-ci.org/BlackEdder/painlessjson) 2 | 3 | Library to painlessly convert your custom types (structs and classes) to and from JSON. This library provides the function toJSON and fromJSON to automatically convert any type to and from JSON. It is possible to override the implementation by defining your own `_toJSON` and `_fromJSON` member functions for a type or with User Defined Attributes. The default conversion works by converting all member variables of a type to and from JSON (including functions with the `@property` attribute). Constructors will be used automatically if no default-constructor is available. 4 | 5 | Painlessjson works by serializing a class/struct using compile time reflection and converts the public fields to a JSON object. You can influence the serialisation/seserialization with the following User Defined Attributes: 6 | 7 | 8 | - `@SerializeToIgnore` & `@SerializeFromIgnore` disable serialization in the to step or the from step 9 | - `@SerializeIgnore` is the same as combining `@SerializeToIgnore` and `@SerializeToIgnore`, and disables serialization/deserialization for the variable 10 | - `@SerializedName('Name')` Use specified name when serializing/deserializing 11 | - `@SerializedName('To', 'From')` Use a different name when serializing/deserializing 12 | - `@SerializedToName('To')` & `@SerializedFromName('From')` Alternative way of defining names. 13 | 14 | ## Installation 15 | 16 | Installation is mostly managed through , so you can add it to your dependencies in your dub.json file. 17 | 18 | You can also generate the library by hand with: 19 | 20 | ```sh 21 | git clone http://github.com/BlackEdder/painlessjson.git 22 | cd painlessjson 23 | dub build -b release 24 | ``` 25 | 26 | ## Examples 27 | 28 | ```D 29 | import std.json; 30 | import painlessjson; 31 | 32 | struct Point 33 | { 34 | double x = 0; 35 | double y = 1; 36 | } 37 | 38 | Point point; 39 | auto json = point.toJSON; // => q{{"x":0,"y":1}} 40 | auto newPoint = fromJSON!Point(parseJSON(q{{"x":-1,"y":2}})); 41 | 42 | class IdAndName 43 | { 44 | immutable string name; 45 | immutable int id; 46 | this(int id, string name) 47 | { 48 | this.id = id; 49 | this.name = name; 50 | } 51 | this(int id) 52 | { 53 | this.id = id; 54 | this.name = "Undefined"; 55 | } 56 | } 57 | 58 | auto person = fromJSON!IdAndName(parseJSON(q{{"id":34, "name": "Jason Pain"}})); 59 | assertEqual(person.id, 34); 60 | assertEqual(person.name, "Jason Pain"); 61 | ``` 62 | 63 | More detailed examples can be found in the [master branch documentation on Github][master docs github], the [master branch documentation on ddocs.org][master docs ddocs], or the [latest release documentation on ddocs.org][release docs]. The classes/structs used in the examples are defined [here][unittest types]. 64 | 65 | ## Performance 66 | 67 | The library uses compile time reflection to find the fields in your classes. This generates the same code as a handwritten implementation would. It uses std.json on the backend and performance is mainly determined by the std.json implementation. At the moment of writing (2014) std.json is known to be slow compared to other languages. Hopefully, this will be improved over time. 68 | 69 | ## Tested compilers 70 | ![DMD-2.087.0](https://img.shields.io/badge/DMD-2.087.0-brightgreen.svg) 71 | ![DMD-2.086.1](https://img.shields.io/badge/DMD-2.086.1-brightgreen.svg) 72 | ![DMD-2.085.1](https://img.shields.io/badge/DMD-2.085.1-brightgreen.svg) 73 | ![DMD-2.084.1](https://img.shields.io/badge/DMD-2.084.1-brightgreen.svg) 74 | ![DMD-2.083.1](https://img.shields.io/badge/DMD-2.083.1-brightgreen.svg) 75 | ![DMD-2.082.1](https://img.shields.io/badge/DMD-2.082.1-brightgreen.svg) 76 | ![DMD-2.081.2](https://img.shields.io/badge/DMD-2.081.2-brightgreen.svg) 77 | ![DMD-2.080.1](https://img.shields.io/badge/DMD-2.080.1-brightgreen.svg) 78 | ![DMD-2.079.1](https://img.shields.io/badge/DMD-2.079.1-brightgreen.svg) 79 | ![DMD-2.078.3](https://img.shields.io/badge/DMD-2.078.3-brightgreen.svg) 80 | ![DMD-2.077.1](https://img.shields.io/badge/DMD-2.077.1-brightgreen.svg) 81 | ![DMD-2.076.1](https://img.shields.io/badge/DMD-2.076.1-brightgreen.svg) 82 | ![DMD-2.075.1](https://img.shields.io/badge/DMD-2.075.1-red.svg) 83 | 84 | ![LDC-1.16.0](https://img.shields.io/badge/LDC-1.16.0-brightgreen.svg) 85 | ![LDC-1.15.0](https://img.shields.io/badge/LDC-1.15.0-brightgreen.svg) 86 | ![LDC-1.14.0](https://img.shields.io/badge/LDC-1.14.0-brightgreen.svg) 87 | ![LDC-1.13.0](https://img.shields.io/badge/LDC-1.13.0-brightgreen.svg) 88 | ![LDC-1.12.0](https://img.shields.io/badge/LDC-1.12.0-brightgreen.svg) 89 | ![LDC-1.11.0](https://img.shields.io/badge/LDC-1.11.0-brightgreen.svg) 90 | ![LDC-1.10.0](https://img.shields.io/badge/LDC-1.10.0-brightgreen.svg) 91 | ![LDC-1.9.0](https://img.shields.io/badge/LDC-1.9.0-brightgreen.svg) 92 | ![LDC-1.8.0](https://img.shields.io/badge/LDC-1.8.0-brightgreen.svg) 93 | ![LDC-1.7.0](https://img.shields.io/badge/LDC-1.7.0-brightgreen.svg) 94 | ![LDC-1.6.0](https://img.shields.io/badge/LDC-1.6.0-brightgreen.svg) 95 | ![LDC-1.5.0](https://img.shields.io/badge/LDC-1.5.0-red.svg) 96 | 97 | ![GDC-8.2.1](https://img.shields.io/badge/GDC-8.2.1-red.svg) 98 | 99 | [master docs github]: http://blackedder.github.io/painlessjson/painlessjson.html 100 | [master docs ddocs]: http://ddocs.org/painlessjson/~master/painlessjson/painlessjson.html 101 | [release docs]: http://ddocs.org/painlessjson/~master/painlessjson/painlessjson.html 102 | [unittest types]: https://github.com/BlackEdder/painlessjson/blob/master/source/painlessjson/unittesttypes.d 103 | -------------------------------------------------------------------------------- /autotest.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Immediately run once 3 | dub test 4 | 5 | trap "dub test" INT 6 | 7 | function watch_tests() { 8 | while : 9 | do 10 | #file=`inotifywait -q -e CREATE bin/ --format %f` 11 | file=`inotifywait -q -e ATTRIB source/painlessjson/ --format %f` 12 | sleep 1 13 | dub test 14 | done 15 | } 16 | 17 | watch_tests 18 | -------------------------------------------------------------------------------- /dub.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "painlessjson", 3 | "description": "Converting custom types to and from JSON the painless way.", 4 | "homepage": "https://github.com/BlackEdder/painlessjson", 5 | "copyright": "Copyright © 2014-2015, Edwin van Leeuwen", 6 | "authors": ["Edwin van Leeuwen","Pierre Krafft"], 7 | "license": "BSL-1.0", 8 | "dependencies": { 9 | "dunit": "~>1.0.14", 10 | "painlesstraits": "~>0.3.0" 11 | }, 12 | "configurations": [ 13 | { 14 | "name": "library", 15 | "targetType": "library" 16 | }, 17 | { 18 | "name": "unittest", 19 | "dependencies": { 20 | "dunit": "~>1.0.14" 21 | } 22 | } 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /source/painlessjson/annotations.d: -------------------------------------------------------------------------------- 1 | module painlessjson.annotations; 2 | 3 | import painlessjson.string; 4 | import painlesstraits; 5 | 6 | struct SerializedToName 7 | { 8 | string name; 9 | } 10 | 11 | struct SerializedFromName 12 | { 13 | string name; 14 | } 15 | 16 | struct SerializedName 17 | { 18 | string to; 19 | string from; 20 | this(string serializedName) 21 | { 22 | to = from = serializedName; 23 | } 24 | 25 | this(string to, string from) 26 | { 27 | this.to = to; 28 | this.from = from; 29 | } 30 | 31 | } 32 | 33 | struct SerializeIgnore 34 | { 35 | } 36 | 37 | struct SerializeToIgnore 38 | { 39 | } 40 | 41 | struct SerializeFromIgnore 42 | { 43 | } 44 | 45 | template serializationToName(alias T, string defaultName, bool alsoAcceptUnderscore) 46 | { 47 | static string helper() 48 | { 49 | static if (hasValueAnnotation!(T, SerializedName) && getAnnotation!(T, SerializedName).to) 50 | { 51 | return getAnnotation!(T, SerializedName).to; 52 | } 53 | else static if (hasValueAnnotation!(T, SerializedToName) 54 | && getAnnotation!(T, SerializedToName).name) 55 | { 56 | return getAnnotation!(T, SerializedToName).name; 57 | } else static if(alsoAcceptUnderscore) 58 | { 59 | return camelCaseToUnderscore(defaultName); 60 | } 61 | else 62 | { 63 | return defaultName; 64 | } 65 | } 66 | 67 | enum string serializationToName = helper; 68 | } 69 | 70 | template serializationFromName(alias T, string defaultName) 71 | { 72 | static string helper() 73 | { 74 | static if (hasValueAnnotation!(T, SerializedName) && getAnnotation!(T, 75 | SerializedName).from) 76 | { 77 | return getAnnotation!(T, SerializedName).from; 78 | } 79 | else static if (hasValueAnnotation!(T, SerializedFromName) 80 | && getAnnotation!(T, SerializedFromName).name) 81 | { 82 | return getAnnotation!(T, SerializedFromName).name; 83 | } 84 | else 85 | { 86 | return defaultName; 87 | } 88 | } 89 | 90 | enum string serializationFromName = helper; 91 | } 92 | -------------------------------------------------------------------------------- /source/painlessjson/package.d: -------------------------------------------------------------------------------- 1 | module painlessjson; 2 | 3 | public import painlessjson.painlessjson; 4 | 5 | public import painlessjson.annotations; 6 | -------------------------------------------------------------------------------- /source/painlessjson/painlessjson.d: -------------------------------------------------------------------------------- 1 | module painlessjson.painlessjson; 2 | 3 | import std.algorithm : map; 4 | import std.string; 5 | import std.conv; 6 | import std.json; 7 | import std.range; 8 | import std.traits; 9 | import std.typecons : Tuple; 10 | import std.typetuple : TypeTuple; 11 | import painlessjson.annotations; 12 | import painlessjson.string; 13 | import painlesstraits; 14 | 15 | version (unittest) 16 | { 17 | import std.stdio : writeln; 18 | import painlessjson.unittesttypes; 19 | import dunit.toolkit; 20 | } 21 | 22 | struct SerializationOptions 23 | { 24 | bool alsoAcceptUnderscore; 25 | bool convertToUnderscore; 26 | } 27 | 28 | enum defaultSerializatonOptions = SerializationOptions(true, false); 29 | 30 | static if (is(typeof(JSONType.integer))) 31 | { 32 | enum JSONType_int = JSONType.integer; 33 | enum JSONType_true = JSONType.true_; 34 | enum JSONType_null = JSONType.null_; 35 | } 36 | else 37 | { 38 | enum JSONType_int = JSON_TYPE.INTEGER; 39 | enum JSONType_true = JSON_TYPE.TRUE; 40 | enum JSONType_null = JSON_TYPE.NULL; 41 | } 42 | 43 | 44 | 45 | //See if we can use something else than __traits(compiles, (T t){JSONValue(t);}) 46 | private JSONValue defaultToJSONImpl(T, SerializationOptions options)(in T object) if (__traits(compiles, (in T t) { 47 | JSONValue(t); 48 | })) 49 | { 50 | return JSONValue(object); 51 | } 52 | 53 | //See if we can use something else than !__traits(compiles, (T t){JSONValue(t);}) 54 | private JSONValue defaultToJSONImpl(T, SerializationOptions options)(in T object) if (isArray!T && !__traits(compiles, (in T t) { 55 | JSONValue(t); 56 | })) 57 | { 58 | JSONValue[] jsonRange; 59 | jsonRange = map!((el) => el.toJSON)(object).array; 60 | return JSONValue(jsonRange); 61 | } 62 | 63 | // AA for simple types, excluding those handled by the JSONValue constructor 64 | private JSONValue defaultToJSONImpl(T, SerializationOptions options)(in T object) 65 | if (isAssociativeArray!T && isBuiltinType!(KeyType!T) && !isAssociativeArray!(KeyType!T) && 66 | !__traits(compiles, (in T t) { JSONValue(t);})) 67 | { 68 | JSONValue[string] jsonAA; 69 | foreach (key, value; object) 70 | { 71 | 72 | jsonAA[to!string(key)] = value.toJSON; 73 | } 74 | return JSONValue(jsonAA); 75 | } 76 | 77 | // AA for compound types 78 | private JSONValue defaultToJSONImpl(T, SerializationOptions options)(in T object) if (isAssociativeArray!T && (!isBuiltinType!(KeyType!T) || isAssociativeArray!(KeyType!T))) 79 | { 80 | JSONValue[string] jsonAA; 81 | foreach (key, value; object) 82 | { 83 | 84 | jsonAA[key.toJSON.toString] = value.toJSON; 85 | } 86 | return JSONValue(jsonAA); 87 | } 88 | 89 | // platform-independent tuple serializer using _0, _1, ... keys for unnamed keys (because key names could either be _0, _0UL or something else which is implmenetation defined) 90 | private JSONValue defaultToJSONImpl(T, SerializationOptions options)(in T object) if (is(T : Tuple!U, U...)) 91 | { 92 | JSONValue[string] jsonAA; 93 | static foreach (i, name; T.fieldNames) 94 | { 95 | static if (name.length != 0) 96 | jsonAA[name] = object[i].toJSON; 97 | else 98 | jsonAA["_" ~ (cast(int)i).to!string] = object[i].toJSON; 99 | } 100 | return JSONValue(jsonAA); 101 | } 102 | 103 | private JSONValue defaultToJSONImpl(T, SerializationOptions options )(in T object) if (!isBuiltinType!T 104 | && !is(T : Tuple!U, U...) && !__traits(compiles, (in T t) { JSONValue(t); })) 105 | { 106 | JSONValue[string] json; 107 | // Getting all member variables (there is probably an easier way) 108 | foreach (name; __traits(allMembers, T)) 109 | { 110 | static if (__traits(compiles, 111 | { 112 | json[serializationToName!(__traits(getMember, object, name), name, false)] = __traits(getMember, 113 | object, name).toJSON; 114 | }) && !hasAnyOfTheseAnnotations!(__traits(getMember, object, 115 | name), SerializeIgnore, SerializeToIgnore) 116 | && isFieldOrProperty!(__traits(getMember, object, name)) 117 | && __traits(getProtection, __traits(getMember, object, name)) != "private") 118 | { 119 | json[serializationToName!(__traits(getMember, object, name), name,(options.convertToUnderscore))] = __traits(getMember, 120 | object, name).toJSON; 121 | } 122 | } 123 | return JSONValue(json); 124 | } 125 | 126 | /++ 127 | Convert any type to JSON
128 | Can be overridden by _toJSON 129 | +/ 130 | JSONValue defaultToJSON(T, SerializationOptions options = defaultSerializatonOptions)(in T t){ 131 | return defaultToJSONImpl!(T, options)(t); 132 | } 133 | 134 | /// Template function that converts any object to JSON 135 | JSONValue toJSON(T, SerializationOptions options = defaultSerializatonOptions)(in T t) 136 | { 137 | static if (__traits(compiles, (in T t) { return t._toJSON(); })) 138 | { 139 | return t._toJSON(); 140 | } 141 | else 142 | return defaultToJSON!(T, options)(t); 143 | } 144 | 145 | /// Converting common types 146 | unittest 147 | { 148 | assertEqual(5.toJSON!int, JSONValue(5)); 149 | assert(4.toJSON != JSONValue(5)); //TODO: Wait for DUnit to implement assertNotEqual 150 | assertEqual(5.4.toJSON, JSONValue(5.4)); 151 | assertEqual(toJSON("test"), JSONValue("test")); 152 | assertEqual(toJSON(JSONValue("test")), JSONValue("test")); 153 | } 154 | 155 | /// Converting InputRanges 156 | unittest 157 | { 158 | assertEqual([1, 2].toJSON.toString, "[1,2]"); 159 | } 160 | 161 | /// User structs 162 | unittest 163 | { 164 | Point p; 165 | assertEqual(toJSON(p).toString, q{{"x":0,"y":1}}); 166 | } 167 | 168 | /// Array of structs 169 | unittest 170 | { 171 | Point[] ps = [Point(-1, 1), Point(2, 3)]; 172 | assertEqual(toJSON(ps).toString, q{[{"x":-1,"y":1},{"x":2,"y":3}]}); 173 | } 174 | 175 | /// User class 176 | unittest 177 | { 178 | PointC p = new PointC(1, -2); 179 | assertEqual(toJSON(p).toString, q{{"x":1,"y":-2}}); 180 | } 181 | 182 | /// User class with private fields 183 | unittest 184 | { 185 | PointPrivate p = new PointPrivate(-1, 2); 186 | assertEqual(toJSON(p).toString, q{{"x":-1,"y":2}}); 187 | auto pnt = p.toJSON.fromJSON!PointPrivate; 188 | assertEqual(p.x, -1); 189 | assertEqual(p.y, 2); 190 | } 191 | 192 | /// User class with defaultToJSON 193 | unittest 194 | { 195 | PointDefaultFromJSON p = new PointDefaultFromJSON(-1, 2); 196 | assertEqual(toJSON(p).toString, q{{"_x":-1,"y":2}}); 197 | auto pnt = p.toJSON.fromJSON!PointDefaultFromJSON; 198 | assertEqual(p.x, -1); 199 | assertEqual(p.y, 2); 200 | } 201 | 202 | /// User class with private fields and @property 203 | unittest 204 | { 205 | auto p = PointPrivateProperty(-1, 2); 206 | assertEqual(toJSON(p).toString, q{{"x":-1,"y":2,"z":1}}); 207 | } 208 | 209 | /// User class with SerializedName annotation 210 | unittest 211 | { 212 | auto p = PointSerializationName(-1, 2); 213 | assertEqual(toJSON(p)["xOut"].floating, -1); 214 | assertEqual(toJSON(p)["yOut"].floating, 2); 215 | } 216 | 217 | /// User class with SerializeIgnore annotations 218 | unittest 219 | { 220 | auto p = PointSerializationIgnore(-1, 5, 4); 221 | assertEqual(toJSON(p).toString, q{{"z":5}}); 222 | } 223 | 224 | /// Array of classes 225 | unittest 226 | { 227 | PointC[] ps = [new PointC(-1, 1), new PointC(2, 3)]; 228 | assertEqual(toJSON(ps).toString, q{[{"x":-1,"y":1},{"x":2,"y":3}]}); 229 | } 230 | 231 | /// Associative array 232 | unittest 233 | { 234 | string[int] aa = [0 : "a", 1 : "b"]; 235 | assert(aa.toJSON.toString == q{{"0":"a","1":"b"}}); 236 | Point[int] aaStruct = [0 : Point(-1, 1), 1 : Point(2, 0)]; 237 | assertEqual(aaStruct.toJSON.toString, q{{"0":{"x":-1,"y":1},"1":{"x":2,"y":0}}}); 238 | assertEqual(["key": "value"].toJSON.toString, q{{"key":"value"}}); 239 | } 240 | 241 | /// Associative array containing struct 242 | unittest 243 | { 244 | assertEqual(["test": SimpleStruct("test2")].toJSON().toString, q{{"test":{"str":"test2"}}}); 245 | } 246 | 247 | /// Associative array with struct key 248 | unittest 249 | { 250 | assertEqual([SimpleStruct("key"): "value", SimpleStruct("key2"): "value2"].toJSON().toString, q{{"{\"str\":\"key\"}":"value","{\"str\":\"key2\"}":"value2"}}); 251 | } 252 | 253 | /// struct with inner struct and AA 254 | unittest 255 | { 256 | auto testStruct = StructWithStructAndAA(["key1": "value1"], ["key2": StructWithStructAndAA.Inner("value2")]); 257 | auto converted = testStruct.toJSON(); 258 | assertEqual(converted["stringToInner"].toString, q{{"key2":{"str":"value2"}}}); 259 | assertEqual(converted["stringToString"].toString, q{{"key1":"value1"}}); 260 | } 261 | 262 | /// Unnamed tuples 263 | unittest 264 | { 265 | Tuple!(int, int) point; 266 | point[0] = 5; 267 | point[1] = 6; 268 | assertEqual(toJSON(point).toString, q{{"_0":5,"_1":6}}); 269 | } 270 | 271 | /// Named tuples 272 | unittest 273 | { 274 | Tuple!(int, "x", int, "y") point; 275 | point.x = 5; 276 | point.y = 6; 277 | assertEqual(point, fromJSON!(Tuple!(int, "x", int, "y"))(parseJSON(q{{"x":5,"y":6}}))); 278 | } 279 | 280 | /// Convert camel case to underscore automatically 281 | unittest 282 | { 283 | CamelCaseConversion value; 284 | value.wasCamelCase = 5; 285 | value.was_underscore = 7; 286 | 287 | auto valueAsJSON = value.toJSON!(CamelCaseConversion,SerializationOptions(false, true)); 288 | 289 | assertEqual(valueAsJSON["was_camel_case"].integer, 5); 290 | assertEqual(valueAsJSON["was_underscore"].integer, 7); 291 | } 292 | 293 | /// Overloaded toJSON 294 | unittest 295 | { 296 | class A 297 | { 298 | double x = 0; 299 | double y = 1; 300 | JSONValue toJSON() 301 | { 302 | JSONValue[string] json; 303 | json["x"] = x; 304 | return JSONValue(json); 305 | } 306 | 307 | } 308 | 309 | auto a = new A; 310 | assertEqual(a.toJSON.toString, q{{"x":0}}); 311 | 312 | class B 313 | { 314 | double x = 0; 315 | double y = 1; 316 | } 317 | 318 | // Both templates will now work for B, so this is ambiguous in D. 319 | // Under dmd it looks like the toJSON!T that is loaded first is the one used 320 | JSONValue toJSON(T : B)(T b) 321 | { 322 | JSONValue[string] json; 323 | json["x"] = b.x; 324 | return JSONValue(json); 325 | } 326 | 327 | auto b = new B; 328 | assertEqual(b.toJSON.toString, q{{"x":0,"y":1}}); 329 | 330 | class Z 331 | { 332 | double x = 0; 333 | double y = 1; 334 | // Adding an extra value 335 | JSONValue toJSON() 336 | { 337 | JSONValue[string] json = painlessjson.toJSON!Z(this).object; 338 | json["add"] = "bla".toJSON; 339 | return JSONValue(json); 340 | } 341 | 342 | } 343 | 344 | auto z = new Z; 345 | assertEqual(z.toJSON["x"].floating, 0); 346 | assertEqual(z.toJSON["y"].floating, 1); 347 | assertEqual(z.toJSON["add"].str, "bla"); 348 | } 349 | 350 | private T defaultFromJSONImpl(T, SerializationOptions options)(in JSONValue json) if (is(T == JSONValue)) 351 | { 352 | return json; 353 | } 354 | 355 | private T defaultFromJSONImpl(T, SerializationOptions options)(in JSONValue json) if (isIntegral!T) 356 | { 357 | return to!T(json.integer); 358 | } 359 | 360 | private T defaultFromJSONImpl(T, SerializationOptions options)(in JSONValue json) if (isFloatingPoint!T) 361 | { 362 | if (json.type == JSONType_int) 363 | return to!T(json.integer); 364 | else 365 | return to!T(json.floating); 366 | } 367 | 368 | private T defaultFromJSONImpl(T, SerializationOptions options)(in JSONValue json) if (is(T == string)) 369 | { 370 | return to!T(json.str); 371 | } 372 | 373 | private T defaultFromJSONImpl(T, SerializationOptions options)(in JSONValue json) if (isBoolean!T) 374 | { 375 | if (json.type == JSONType_true) 376 | return true; 377 | else 378 | return false; 379 | } 380 | 381 | private T defaultFromJSONImpl(T, SerializationOptions options)(in JSONValue json) if (isArray!T && !is(T == string)) 382 | { 383 | T t; //Se is we can find another way of finding t.front 384 | return map!((js) => fromJSON!(typeof(t.front))(js))(json.array).array; 385 | } 386 | 387 | // AA for simple keys 388 | private T defaultFromJSONImpl(T, SerializationOptions options)(in JSONValue json) if (isAssociativeArray!T && isBuiltinType!(KeyType!T) && !isAssociativeArray!(KeyType!T)) 389 | { 390 | T t; 391 | const JSONValue[string] jsonAA = json.object; 392 | foreach (k, v; jsonAA) 393 | { 394 | t[to!(KeyType!T)(k)] = fromJSON!(ValueType!T)(v); 395 | } 396 | return t; 397 | } 398 | 399 | // AA for compound keys 400 | private T defaultFromJSONImpl(T, SerializationOptions options)(in JSONValue json) if (isAssociativeArray!T && (!isBuiltinType!(KeyType!T) || isAssociativeArray!(KeyType!T))) 401 | { 402 | KeyType!T keyConverter(string stringKey) { 403 | try{ 404 | return fromJSON!(KeyType!T)(parseJSON(stringKey)); 405 | } 406 | catch(JSONException) { 407 | throw new Exception("Couldn't convert JSON key \"" ~ stringKey ~ "\" to " ~ KeyType!T.stringof ~ " which is the key type of " ~ T.stringof); 408 | } 409 | } 410 | 411 | T t; 412 | const JSONValue[string] jsonAA = json.object; 413 | foreach (k, v; jsonAA) 414 | { 415 | t[keyConverter(k)] = fromJSON!(ValueType!T)(v); 416 | 417 | } 418 | return t; 419 | } 420 | 421 | // Default tuple implementation 422 | private T defaultFromJSONImpl(T, SerializationOptions options)(in JSONValue json) if (is(T : Tuple!U, U...)) 423 | { 424 | T t; 425 | const JSONValue[string] jsonAA = json.object; 426 | static foreach (i, name; T.fieldNames) 427 | {{ 428 | static if (name.length != 0) 429 | enum effectiveName = name; 430 | else 431 | enum effectiveName = "_" ~ (cast(int)i).to!string; 432 | 433 | if (auto v = effectiveName in jsonAA) 434 | t[i] = fromJSON!(typeof(t[i]))(*v); 435 | }} 436 | return t; 437 | } 438 | 439 | /++ 440 | Convert to given type from JSON.
441 | Can be overridden by _fromJSON. 442 | +/ 443 | private T defaultFromJSONImpl(T, SerializationOptions options)(in JSONValue json) 444 | if (!isBuiltinType!T && !is(T == JSONValue) && !is(T : Tuple!U, U...)) 445 | { 446 | static if (hasAccessibleDefaultsConstructor!(T)) 447 | { 448 | T t = getIntstanceFromDefaultConstructor!T; 449 | mixin("const JSONValue[string] jsonAA = json.object;"); 450 | foreach (name; __traits(allMembers, T)) 451 | { 452 | 453 | static if (__traits(compiles,{mixin("import " ~ moduleName!(__traits(getMember, t, name)) ~ ";");})) 454 | { 455 | mixin("import " ~ moduleName!(__traits(getMember, t, name)) ~ ";"); 456 | } 457 | 458 | static if (__traits(compiles, 459 | mixin( 460 | "t." ~ name ~ "= fromJSON!(" ~ fullyQualifiedName!(typeof(__traits(getMember, 461 | t, name))) ~ ")(jsonAA[\"aFieldName\"])")) 462 | && !hasAnyOfTheseAnnotations!(__traits(getMember, t, name), 463 | SerializeIgnore, SerializeFromIgnore) 464 | && isFieldOrProperty!(__traits(getMember, t, name))) 465 | { 466 | enum string fromName = serializationFromName!(__traits(getMember, t, 467 | name), name); 468 | mixin( 469 | "if ( \"" ~ fromName ~ "\" in jsonAA) t." ~ name ~ "= fromJSON!(" ~ fullyQualifiedName!( 470 | typeof(__traits(getMember, t, name))) ~ ")(jsonAA[\"" ~ fromName ~ "\"]);"); 471 | static if(options.alsoAcceptUnderscore) 472 | { 473 | mixin( 474 | "if ( \"" ~ camelCaseToUnderscore(fromName) ~ "\" in jsonAA) t." ~ name ~ "= fromJSON!(" ~ fullyQualifiedName!( 475 | typeof(__traits(getMember, t, name))) ~ ")(jsonAA[\"" ~ camelCaseToUnderscore(fromName) ~ "\"]);"); 476 | } 477 | } 478 | } 479 | return t; 480 | } 481 | else static if (hasAccessibleConstructor!T) 482 | { 483 | if (__traits(hasMember, T, "__ctor")) 484 | { 485 | alias Overloads = TypeTuple!(__traits(getOverloads, T, "__ctor")); 486 | alias constructorFunctionType = T function(JSONValue value) @system; 487 | ulong bestOverloadScore = ulong.max; 488 | constructorFunctionType bestOverload; 489 | 490 | // Find the constructor overloads that matches our json content the best 491 | foreach (overload; Overloads) 492 | { 493 | static if (__traits(compiles, 494 | { 495 | return getInstanceFromCustomConstructor!(T, overload, false)(json); 496 | })) 497 | { 498 | if (jsonValueHasAllFieldsNeeded!(overload, (options.alsoAcceptUnderscore))(json)) 499 | { 500 | ulong overloadScore = constructorOverloadScore!(overload, (options.alsoAcceptUnderscore))(json); 501 | if (overloadScore < bestOverloadScore) 502 | { 503 | bestOverload = function(JSONValue value) { 504 | return getInstanceFromCustomConstructor!(T, overload, (options.alsoAcceptUnderscore))(value); 505 | }; 506 | bestOverloadScore = overloadScore; 507 | } 508 | } 509 | } 510 | } 511 | if (bestOverloadScore < ulong.max) 512 | { 513 | return bestOverload(json); 514 | } 515 | throw new JSONException( 516 | "JSONValue can't satisfy any constructor: " ~ json.toPrettyString); 517 | } 518 | } 519 | else static assert(false, "Cannot be automatically generate a defaultFromJSONImpl for type " ~ T.stringof 520 | ~ ". (no accessible and suitable constructors) Create a _fromJSON member method for custom deserialization."); 521 | } 522 | 523 | /++ 524 | Convert to given type from JSON.
525 | Can be overridden by _fromJSON. 526 | +/ 527 | T defaultFromJSON(T, SerializationOptions options = defaultSerializatonOptions)(in JSONValue json){ 528 | return defaultFromJSONImpl!(T, options)(json); 529 | 530 | } 531 | 532 | template hasAccessibleDefaultsConstructor(T) 533 | { 534 | static bool helper() 535 | { 536 | return (is(T == struct) && __traits(compiles, { T t; })) 537 | || (is(T == class) && __traits(compiles, { T t = new T; })); 538 | } 539 | 540 | enum bool hasAccessibleDefaultsConstructor = helper(); 541 | } 542 | 543 | T getIntstanceFromDefaultConstructor(T)() 544 | { 545 | static if (is(T == struct) && __traits(compiles, { T t; })) 546 | { 547 | return T(); 548 | } 549 | else static if (is(T == class) && __traits(compiles, { T t = new T; })) 550 | { 551 | return new T(); 552 | } 553 | } 554 | 555 | T getInstanceFromCustomConstructor(T, alias Ctor, bool alsoAcceptUnderscore)(in JSONValue json) 556 | { 557 | enum params = ParameterIdentifierTuple!(Ctor); 558 | alias defaults = ParameterDefaultValueTuple!(Ctor); 559 | alias Types = ParameterTypeTuple!(Ctor); 560 | Tuple!(Types) args; 561 | static foreach (i; 0 .. params.length) 562 | {{ 563 | enum paramName = params[i]; 564 | if (paramName in json.object) 565 | { 566 | args[i] = fromJSON!(Types[i])(json[paramName]); 567 | } else if( alsoAcceptUnderscore && camelCaseToUnderscore(paramName)){ 568 | args[i] = fromJSON!(Types[i])(json[camelCaseToUnderscore(paramName)]); 569 | } 570 | else 571 | { 572 | // no value specified in json 573 | static if (is(defaults[i] == void)) 574 | { 575 | throw new JSONException( 576 | "parameter " ~ paramName ~ " has no default value and was not specified"); 577 | } 578 | else 579 | { 580 | args[i] = defaults[i]; 581 | } 582 | } 583 | }} 584 | static if (is(T == class)) 585 | { 586 | return new T(args.expand); 587 | } 588 | else 589 | { 590 | return T(args.expand); 591 | } 592 | } 593 | 594 | bool jsonValueHasAllFieldsNeeded(alias Ctor, bool alsoAcceptUnderscore)(in JSONValue json) 595 | { 596 | enum params = ParameterIdentifierTuple!(Ctor); 597 | alias defaults = ParameterDefaultValueTuple!(Ctor); 598 | alias Types = ParameterTypeTuple!(Ctor); 599 | Tuple!(Types) args; 600 | static foreach (i; 0 .. params.length) 601 | {{ 602 | enum paramName = params[i]; 603 | if (!((paramName in json.object) || ( alsoAcceptUnderscore && (camelCaseToUnderscore(paramName) in json.object))) && is(defaults[i] == void)) 604 | { 605 | return false; 606 | } 607 | }} 608 | return true; 609 | } 610 | 611 | ulong constructorOverloadScore(alias Ctor, bool alsoAcceptUnderscore)(in JSONValue json) 612 | { 613 | enum params = ParameterIdentifierTuple!(Ctor); 614 | alias defaults = ParameterDefaultValueTuple!(Ctor); 615 | alias Types = ParameterTypeTuple!(Ctor); 616 | Tuple!(Types) args; 617 | ulong overloadScore = json.object.length; 618 | static foreach (i; 0 .. params.length) 619 | {{ 620 | enum paramName = params[i]; 621 | if (paramName in json.object || ( alsoAcceptUnderscore && (camelCaseToUnderscore(paramName) in json.object))) 622 | { 623 | overloadScore--; 624 | } 625 | }} 626 | return overloadScore; 627 | } 628 | 629 | template hasAccessibleConstructor(T) 630 | { 631 | static bool helper() 632 | { 633 | if (__traits(hasMember, T, "__ctor")) 634 | { 635 | alias Overloads = TypeTuple!(__traits(getOverloads, T, "__ctor")); 636 | bool anyInstanciates = false; 637 | foreach (overload; Overloads) 638 | { 639 | anyInstanciates |= __traits(compiles, getInstanceFromCustomConstructor!(T, 640 | overload, false)(JSONValue())); 641 | } 642 | return anyInstanciates; 643 | } 644 | } 645 | 646 | enum bool hasAccessibleConstructor = helper(); 647 | } 648 | 649 | /// Convert from JSONValue to any other type 650 | T fromJSON(T, SerializationOptions options = defaultSerializatonOptions)(in JSONValue json) 651 | { 652 | static if (__traits(compiles, { return T._fromJSON(json); })) 653 | { 654 | return T._fromJSON(json); 655 | } 656 | else 657 | { 658 | if (json.type == JSONType_null) 659 | return T.init; 660 | return defaultFromJSON!(T,options)(json); 661 | } 662 | } 663 | 664 | unittest { 665 | struct P 666 | { string name; } 667 | 668 | auto jv = parseJSON(`{"name": null}`); 669 | auto j = fromJSON!P(jv); 670 | } 671 | 672 | /// Converting common types 673 | unittest 674 | { 675 | assertEqual(fromJSON!int(JSONValue(1)), 1); 676 | assertEqual(fromJSON!double(JSONValue(1.0)), 1); 677 | assertEqual(fromJSON!double(JSONValue(1.3)), 1.3); 678 | assertEqual(fromJSON!string(JSONValue("str")), "str"); 679 | assertEqual(fromJSON!bool(JSONValue(true)), true); 680 | assertEqual(fromJSON!bool(JSONValue(false)), false); 681 | assertEqual(fromJSON!JSONValue(JSONValue(true)), JSONValue(true)); 682 | } 683 | 684 | /// Converting arrays 685 | unittest 686 | { 687 | assertEqual(fromJSON!(int[])(toJSON([1, 2])), [1, 2]); 688 | assertEqual(fromJSON!(Point[])(parseJSON(q{[{"x":-1,"y":2},{"x":3,"y":4}]})), [Point(-1,2),Point(3,4)]); 689 | } 690 | 691 | /// Array as member of other class 692 | unittest 693 | { 694 | // Types need to be defined in different module, otherwise 695 | // type is not known at compile time 696 | import painlessjson.unittesttypes_local_import; 697 | 698 | string jsonString = q{[ {"duration": "10"} ]}; 699 | Route[] routes = parseJSON(jsonString).fromJSON!(Route[]); 700 | assertEqual(routes.length, 1); 701 | 702 | jsonString = q{{"routes":[ {"duration": "10"} ] }}; 703 | JourneyPlan jp; 704 | jp = parseJSON(jsonString).fromJSON!JourneyPlan; 705 | assertEqual(jp.routes.length, 1); 706 | } 707 | 708 | /// Associative arrays 709 | unittest 710 | { 711 | string[int] aaInt = [0 : "a", 1 : "b"]; 712 | assertEqual(aaInt, fromJSON!(string[int])(parseJSON(q{{"0" : "a", "1": "b"}}))); 713 | 714 | string[string] aaString = ["hello" : "world", "json" : "painless"]; 715 | assertEqual(aaString, fromJSON!(string[string])(parseJSON(q{{"hello" : "world", "json" : "painless"}}))); 716 | } 717 | 718 | /// Associative array containing struct 719 | unittest 720 | { 721 | auto parsed = fromJSON!(SimpleStruct[string])(parseJSON(q{{"key": {"str": "value"}}})); 722 | assertEqual(parsed , ["key": SimpleStruct("value")]); 723 | } 724 | 725 | /// Associative array with struct key 726 | unittest 727 | { 728 | JSONValue value = parseJSON(q{{"{\"str\":\"key\"}":"value", "{\"str\":\"key2\"}":"value2"}}); 729 | auto parsed = fromJSON!(string[SimpleStruct])(value); 730 | assertEqual( 731 | parsed 732 | , [SimpleStruct("key"): "value", SimpleStruct("key2"): "value2"]); 733 | } 734 | 735 | /// struct with inner struct and AA 736 | unittest 737 | { 738 | auto testStruct = StructWithStructAndAA(["key1": "value1"], ["key2": StructWithStructAndAA.Inner("value2")]); 739 | auto testJSON = parseJSON(q{{"stringToInner":{"key2":{"str":"value2"}},"stringToString":{"key1":"value1"}}}); 740 | assertEqual(fromJSON!StructWithStructAndAA(testJSON), testStruct); 741 | } 742 | 743 | /// Error reporting from inner objects 744 | unittest 745 | { 746 | import std.exception : collectExceptionMsg; 747 | import std.algorithm : canFind; 748 | void throwFunc() { 749 | fromJSON!(string[SimpleStruct])(parseJSON(q{{"{\"str\": \"key1\"}": "value", "key2":"value2"}})); 750 | } 751 | auto errorMessage = collectExceptionMsg(throwFunc()); 752 | assert(errorMessage.canFind("key2")); 753 | assert(errorMessage.canFind("string[SimpleStruct]")); 754 | assert(!errorMessage.canFind("key1")); 755 | } 756 | 757 | /// Structs from JSON 758 | unittest 759 | { 760 | auto p = fromJSON!Point(parseJSON(q{{"x":-1,"y":2}})); 761 | assertEqual(p.x, -1); 762 | assertEqual(p.y, 2); 763 | p = fromJSON!Point(parseJSON(q{{"x":2}})); 764 | assertEqual(p.x, 2); 765 | assertEqual(p.y, 1); 766 | p = fromJSON!Point(parseJSON(q{{"y":3}})); 767 | assertEqual(p.x, 0); 768 | assertEqual(p.y, 3); 769 | p = fromJSON!Point(parseJSON(q{{"x":-1,"y":2,"z":3}})); 770 | assertEqual(p.x, -1); 771 | assertEqual(p.y, 2); 772 | } 773 | 774 | /// Class from JSON 775 | unittest 776 | { 777 | auto p = fromJSON!PointC(parseJSON(q{{"x":-1,"y":2}})); 778 | assertEqual(p.x, -1); 779 | assertEqual(p.y, 2); 780 | } 781 | 782 | /** 783 | Convert class from JSON using "_fromJSON" 784 | */ 785 | 786 | unittest 787 | { 788 | auto p = fromJSON!PointPrivate(parseJSON(q{{"x":-1,"y":2}})); 789 | assertEqual(p.x, -1); 790 | assertEqual(p.y, 2); 791 | } 792 | 793 | /// Convert struct from JSON using properties 794 | 795 | unittest 796 | { 797 | auto p = fromJSON!PointPrivateProperty(parseJSON(q{{"x":-1,"y":2,"z":3}})); 798 | assertEqual(p.x, -1); 799 | assertEqual(p.y, 2); 800 | } 801 | 802 | /// User class with SerializedName annotation 803 | unittest 804 | { 805 | auto p = fromJSON!PointSerializationName(parseJSON(q{{"xOut":-1,"yOut":2}})); 806 | assertEqual(p.x, 2); 807 | assertEqual(p.y, -1); 808 | } 809 | 810 | /// User class with SerializeIgnore annotations 811 | unittest 812 | { 813 | auto p = fromJSON!PointSerializationIgnore(parseJSON(q{{"z":15}})); 814 | assertEqual(p.x, 0); 815 | assertEqual(p.y, 1); 816 | assertEqual(p.z, 15); 817 | } 818 | 819 | /// Unnamed tuples 820 | unittest 821 | { 822 | Tuple!(int, int) point; 823 | point[0] = 5; 824 | point[1] = 6; 825 | assertEqual(point, fromJSON!(Tuple!(int, int))(parseJSON(q{{"_0":5,"_1":6}}))); 826 | } 827 | 828 | /// No default constructor 829 | unittest 830 | { 831 | auto p = fromJSON!PointUseConstructor(parseJSON(q{{"x":2, "y":5}})); 832 | assertEqual(p.x, 2); 833 | assertEqual(p.y, 5); 834 | } 835 | 836 | /// Multiple constructors and all JSON-values are there 837 | unittest 838 | { 839 | auto person = fromJSON!IdAndName(parseJSON(q{{"id":34, "name": "Jason Pain"}})); 840 | assertEqual(person.id, 34); 841 | assertEqual(person.name, "Jason Pain"); 842 | } 843 | 844 | /// Multiple constructors and some JSON-values are missing 845 | unittest 846 | { 847 | auto person = fromJSON!IdAndName(parseJSON(q{{"id":34}})); 848 | assertEqual(person.id, 34); 849 | assertEqual(person.name, "Undefined"); 850 | } 851 | 852 | /// Accept underscore and convert it to camelCase automatically 853 | unittest 854 | { 855 | auto value = fromJSON!CamelCaseConversion(parseJSON(q{{"was_camel_case":8,"was_underscore":9}})); 856 | assertEqual(value.wasCamelCase, 8); 857 | assertEqual(value.was_underscore, 9); 858 | } 859 | -------------------------------------------------------------------------------- /source/painlessjson/string.d: -------------------------------------------------------------------------------- 1 | module painlessjson.string; 2 | 3 | import std.string; 4 | import std.conv; 5 | import std.array; 6 | import std.ascii : isLower, isUpper; 7 | 8 | version (unittest) 9 | { 10 | import dunit.toolkit; 11 | 12 | } 13 | 14 | unittest { 15 | enum hello_world = camelCaseToUnderscore("helloWorld"); 16 | assertEqual(hello_world,"hello_world"); 17 | enum my_json = camelCaseToUnderscore("myJSON"); 18 | assertEqual(my_json,"my_json"); 19 | } 20 | 21 | string camelCaseToUnderscore(string input){ 22 | auto stringBuilder = appender!string; 23 | stringBuilder.reserve(input.length*2); 24 | bool previousWasLower = false; 25 | foreach(c;input){ 26 | if(previousWasLower && c.isUpper()) 27 | { 28 | stringBuilder.put('_'); 29 | } 30 | 31 | if(c.isLower()) 32 | { 33 | previousWasLower = true; 34 | } else{ 35 | previousWasLower = false; 36 | } 37 | stringBuilder.put(c); 38 | } 39 | return stringBuilder.data.toLower(); 40 | } -------------------------------------------------------------------------------- /source/painlessjson/unittesttypes.d: -------------------------------------------------------------------------------- 1 | module painlessjson.unittesttypes; 2 | 3 | import painlessjson.annotations; 4 | import std.json; 5 | import std.algorithm; 6 | import std.stdio; 7 | import painlessjson.painlessjson; 8 | 9 | /// 10 | struct Point 11 | { 12 | double x = 0; /// 13 | double y = 1; /// 14 | this(double x_, double y_) 15 | { 16 | x = x_; 17 | y = y_; 18 | } 19 | 20 | string foo() 21 | { 22 | writeln("Functions should not be called"); 23 | return "Noooooo!"; 24 | } 25 | 26 | static string bar() 27 | { 28 | writeln("Static functions should not be called"); 29 | return "Noooooo!"; 30 | } 31 | 32 | } 33 | 34 | /// 35 | class PointC 36 | { 37 | /// 38 | double x = 0; 39 | double y = 1; 40 | this() 41 | { 42 | } 43 | 44 | this(double x_, double y_) 45 | { 46 | x = x_; 47 | y = y_; 48 | } 49 | 50 | string foo() 51 | { 52 | writeln("Class functions should not be called"); 53 | return "Noooooo!"; 54 | } 55 | 56 | } 57 | 58 | class PointPrivate 59 | { 60 | private double _x; 61 | private double _y; 62 | this(double x_, double y_) 63 | { 64 | _x = x_; 65 | _y = y_; 66 | } 67 | 68 | string foo() 69 | { 70 | writeln("Class functions should not be called"); 71 | return "Noooooo!"; 72 | } 73 | 74 | const double x() 75 | { 76 | return _x; 77 | } 78 | 79 | const double y() 80 | { 81 | return _y; 82 | } 83 | 84 | static PointPrivate _fromJSON(JSONValue value) 85 | { 86 | return new PointPrivate(fromJSON!double(value["x"]), fromJSON!double(value["y"])); 87 | } 88 | 89 | const JSONValue _toJSON() 90 | { 91 | JSONValue[string] json; 92 | json["x"] = JSONValue(x); 93 | json["y"] = JSONValue(y); 94 | return JSONValue(json); 95 | } 96 | 97 | } 98 | 99 | class PointDefaultFromJSON 100 | { 101 | double _x; 102 | private double _y; 103 | 104 | this() 105 | { 106 | } 107 | 108 | this(double x_, double y_) 109 | { 110 | _x = x_; 111 | _y = y_; 112 | } 113 | 114 | const double x() 115 | { 116 | return _x; 117 | } 118 | 119 | const double y() 120 | { 121 | return _y; 122 | } 123 | 124 | static PointDefaultFromJSON _fromJSON(JSONValue value) 125 | { 126 | auto pnt = defaultFromJSON!PointDefaultFromJSON(value); 127 | pnt._y = fromJSON!double(value["y"]); 128 | return pnt; 129 | } 130 | 131 | const JSONValue _toJSON() 132 | { 133 | JSONValue[string] json; 134 | json = this.defaultToJSON.object; 135 | json["y"] = JSONValue(_y); 136 | return JSONValue(json); 137 | } 138 | } 139 | 140 | struct PointPrivateProperty 141 | { 142 | private double _x; 143 | private double _y; 144 | this(double x_, double y_) 145 | { 146 | _x = x_; 147 | _y = y_; 148 | } 149 | 150 | const string foo() 151 | { 152 | writeln("Class functions should not be called"); 153 | return "Noooooo!"; 154 | } 155 | 156 | const @property double x() 157 | { 158 | return _x; 159 | } 160 | 161 | @property void x(double x_) 162 | { 163 | _x = x_; 164 | } 165 | 166 | const @property double y() 167 | { 168 | return _y; 169 | } 170 | 171 | @property void y(double y_) 172 | { 173 | _y = y_; 174 | } 175 | 176 | const @property double z() 177 | { 178 | return 1.0; 179 | } 180 | 181 | const @property void bar(double a, double b) 182 | { 183 | writeln( 184 | "Functions annotated with @property and more than one variable should not be called"); 185 | assert(0); 186 | } 187 | 188 | } 189 | 190 | struct PointSerializationName 191 | { 192 | @SerializedName("xOut", "yOut") double x = 0; 193 | @SerializedToName("yOut") @SerializedFromName("xOut") double y = 1; 194 | this(double x_, double y_) 195 | { 196 | x = x_; 197 | y = y_; 198 | } 199 | 200 | string foo() 201 | { 202 | writeln("Functions should not be called"); 203 | return "Noooooo!"; 204 | } 205 | 206 | static string bar() 207 | { 208 | writeln("Static functions should not be called"); 209 | return "Noooooo!"; 210 | } 211 | 212 | } 213 | 214 | struct SimpleStruct { 215 | string str; 216 | } 217 | 218 | struct StructWithStructAndAA { 219 | struct Inner { 220 | string str; 221 | } 222 | string[string] stringToString; 223 | Inner[string] stringToInner; 224 | } 225 | 226 | /// 227 | struct PointSerializationIgnore 228 | { 229 | @SerializeIgnore double x = 0; /// 230 | @SerializedToName("z") @SerializeFromIgnore double y = 1; /// 231 | @SerializeToIgnore double z = 2; /// 232 | 233 | /// 234 | this(double x_, double y_, double z_) 235 | { 236 | x = x_; 237 | y = y_; 238 | z = z_; 239 | } 240 | 241 | /// 242 | @SerializeIgnore @property double foo() 243 | { 244 | return 0.1; 245 | } 246 | 247 | /// 248 | @SerializeIgnore @property void foo(double a) 249 | { 250 | } 251 | 252 | } 253 | 254 | /// 255 | struct PointUseConstructor 256 | { 257 | @disable this(); 258 | double x = 0; /// 259 | private double _y = 1; /// 260 | this(double x, double y) 261 | { 262 | this.x = x; 263 | this._y = y; 264 | } 265 | 266 | string foo() 267 | { 268 | writeln("Functions should not be called"); 269 | return "Noooooo!"; 270 | } 271 | 272 | static string bar() 273 | { 274 | writeln("Static functions should not be called"); 275 | return "Noooooo!"; 276 | } 277 | 278 | @property double y() 279 | { 280 | return _y; 281 | } 282 | } 283 | 284 | /// 285 | class IdAndName 286 | { 287 | immutable string name; /// 288 | immutable int id; /// 289 | 290 | this(string name) 291 | { 292 | this.id = -1; 293 | this.name = name; 294 | } 295 | 296 | this(int id, string name) 297 | { 298 | this.id = id; 299 | this.name = name; 300 | } 301 | 302 | this(int id) 303 | { 304 | this.id = id; 305 | this.name = "Undefined"; 306 | } 307 | } 308 | 309 | struct CamelCaseConversion 310 | { 311 | int wasCamelCase; 312 | int was_underscore; 313 | } 314 | -------------------------------------------------------------------------------- /source/painlessjson/unittesttypes_local_import.d: -------------------------------------------------------------------------------- 1 | module painlessjson.unittesttypes_local_import; 2 | 3 | struct Route 4 | { 5 | string duration; 6 | } 7 | 8 | struct JourneyPlan 9 | { 10 | Route[] routes; 11 | } 12 | -------------------------------------------------------------------------------- /travis.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e -o pipefail 4 | 5 | dub test --compiler=${DC} 6 | 7 | if [[ $TRAVIS_BRANCH == 'master' ]] ; then 8 | if [ ! -z "$GH_TOKEN" ]; then 9 | git checkout master 10 | dub build -b docs --compiler=${DC} 11 | cd docs 12 | git init 13 | git checkout -b gh-pages 14 | git config user.name "Travis-CI" 15 | git config user.email "travis@nodemeatspace.com" 16 | git add . 17 | git commit -m "Deployed to Github Pages" 18 | #git push --force --quiet "https://${GH_TOKEN}@github.com/${TRAVIS_REPO_SLUG}" HEAD:gh-pages > /dev/null 2>&1 19 | git push --force "https://BlackEdder:$GITHUB_API_KEY@github.com/${TRAVIS_REPO_SLUG}" HEAD:gh-pages 20 | #git push --force "https://${GH_TOKEN}@github.com/${TRAVIS_REPO_SLUG}" HEAD:gh-pages 21 | fi 22 | fi 23 | --------------------------------------------------------------------------------