├── .gitignore ├── dub.sdl ├── README.md ├── .editorconfig ├── .github └── workflows │ └── ci.yml ├── .travis.yml └── source └── stdx └── data └── json ├── foundation.d ├── value.d ├── generator.d ├── parser.d ├── package.d └── lexer.d /.gitignore: -------------------------------------------------------------------------------- 1 | .dub 2 | docs.json 3 | __dummy.html 4 | *.[ao] 5 | *.obj 6 | 7 | # Since this is a library, ignore `dub.selections.json` 8 | dub.selections.json 9 | -------------------------------------------------------------------------------- /dub.sdl: -------------------------------------------------------------------------------- 1 | name "std_data_json" 2 | description "Phobos candidate JSON implementation." 3 | authors "Sönke Ludwig" 4 | copyright "Copyright © 2014-2015, Sönke Ludwig" 5 | license "BSL-1.0" 6 | 7 | dependency "taggedalgebraic" version=">=0.10.1 <0.12.0" 8 | 9 | x:ddoxFilterArgs "--unittest-examples" "--min-protection=Protected" "--only-documented" 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Phobos candidate package for std.data.json 2 | ========================================== 3 | 4 | This module is an evolution of [vibe.d's JSON module](http://vibed.org/api/vibe.data.json/) with a number of additional features and a cleaned up API. See also the [documentation pages](http://dlang-community.github.io/std_data_json/). 5 | 6 | [![Build Status](https://travis-ci.org/dlang-community/std_data_json.svg?branch=master)](https://travis-ci.org/dlang-community/std_data_json) 7 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig file: http://EditorConfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | end_of_line = lf 7 | insert_final_newline = true 8 | trim_trailing_whitespace = true 9 | indent_style = space 10 | indent_size = 2 11 | max_line_length = 80 12 | 13 | [*.{d,h,hpp,c,cpp,cxx,cs,hs,java,kt,py,rs,sol}] 14 | indent_size = 4 15 | 16 | [{Makefile,go.mod,go.sum,*.go,.gitmodules}] 17 | indent_style = tab 18 | indent_size = 4 19 | 20 | [{CMakeLists.txt,*.cmake}] 21 | indent_size = 2 22 | indent_style = space 23 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | branches: [ master ] 7 | pull_request: 8 | branches: [ master ] 9 | 10 | jobs: 11 | test: 12 | name: "[${{ matrix.os }} | ${{ matrix.dc }}]" 13 | strategy: 14 | matrix: 15 | os: [ubuntu-latest, windows-latest, macOS-latest] 16 | dc: [dmd-latest, ldc-latest] 17 | arch: [x86_64] 18 | runs-on: ${{ matrix.os }} 19 | steps: 20 | - uses: actions/checkout@v3 21 | 22 | - name: Install D compiler 23 | uses: dlang-community/setup-dlang@v1 24 | with: 25 | compiler: ${{ matrix.dc }} 26 | - name: Run tests 27 | env: 28 | DC: ${{matrix.dc}} 29 | ARCH: ${{matrix.arch}} 30 | run: | 31 | dub test 32 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: d 2 | sudo: false 3 | 4 | # Note: DMD v2.078.1-3 has a regression which makes std_data_json 5 | # fails to compile 6 | # This is why LDC v1.8.0 (FE 2.078.3) is not tested. 7 | # Finally, LDC v1.7.0 (FE 2.077.1) is broken with: 8 | # Integer arithmetic operators only work with integral types! 9 | # %6 = add i8* %4, %5, !dbg !5454 10 | # LLVM ERROR: Broken function found, compilation aborted! 11 | 12 | d: 13 | - dmd-2.090.0 14 | - dmd-2.088.1 15 | - dmd-2.087.1 16 | - dmd-2.086.1 17 | - dmd-2.085.1 18 | - dmd-2.084.1 19 | - dmd-2.083.1 20 | - dmd-2.082.1 21 | - dmd-2.081.1 22 | - dmd-2.080.1 23 | - dmd-2.079.1 24 | - dmd-2.078.0 25 | - dmd-2.077.1 26 | - ldc-1.11.0 27 | - ldc-1.10.0 28 | - ldc-1.9.0 29 | - dmd-beta 30 | 31 | script: 32 | - dub test --compiler=${DC} 33 | - dub upgrade && dub test --compiler=${DC} 34 | 35 | matrix: 36 | allow_failures: 37 | - d: dmd-beta 38 | -------------------------------------------------------------------------------- /source/stdx/data/json/foundation.d: -------------------------------------------------------------------------------- 1 | /** 2 | * Exception definitions specific to the JSON processing functions. 3 | * 4 | * Copyright: Copyright 2012 - 2014, Sönke Ludwig. 5 | * License: $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0). 6 | * Authors: Sönke Ludwig 7 | * Source: $(PHOBOSSRC std/data/json/foundation.d) 8 | */ 9 | module stdx.data.json.foundation; 10 | @safe: 11 | 12 | import stdx.data.json.lexer; 13 | 14 | /** 15 | * Represents a location in an input range/file. 16 | * 17 | * The indices are zero based and the column is represented in code units of 18 | * the input (i.e. in bytes in case of a UTF-8 input string). 19 | */ 20 | struct Location 21 | { 22 | /// Optional file name. 23 | string file; 24 | /// The zero based line of the input file. 25 | size_t line = 0; 26 | /// The zero based code unit index of the referenced line. 27 | size_t column = 0; 28 | 29 | /// Returns a string representation of the location. 30 | string toString() const 31 | { 32 | import std.string; 33 | return format("%s(%s:%s)", this.file, this.line, this.column); 34 | } 35 | } 36 | 37 | 38 | /** 39 | * JSON specific exception. 40 | * 41 | * This exception is thrown during the lexing and parsing stages. 42 | */ 43 | class JSONException : Exception 44 | { 45 | /// The bare error message 46 | string message; 47 | 48 | /// The location where the error occured 49 | Location location; 50 | 51 | /// Constructs a new exception from the given message and location 52 | this(string message, Location loc, string file = __FILE__, size_t line = __LINE__) 53 | { 54 | import std.string; 55 | this.message = message; 56 | this.location = loc; 57 | super(format("%s(%s:%s) %s", loc.file, loc.line, loc.column, message), file, line); 58 | } 59 | } 60 | 61 | package void enforceJson(string file = __FILE__, size_t line = __LINE__)(bool cond, lazy string message, lazy Location loc) 62 | { 63 | if (!cond) throw new JSONException(message, loc, file, line); 64 | } 65 | 66 | -------------------------------------------------------------------------------- /source/stdx/data/json/value.d: -------------------------------------------------------------------------------- 1 | /** 2 | * Defines a generic value type for builing and holding JSON documents in memory. 3 | * 4 | * Copyright: Copyright 2012 - 2015, Sönke Ludwig. 5 | * License: $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0). 6 | * Authors: Sönke Ludwig 7 | * Source: $(PHOBOSSRC std/data/json/value.d) 8 | */ 9 | module stdx.data.json.value; 10 | @safe: 11 | 12 | /// 13 | unittest { 14 | // build a simple JSON document 15 | auto aa = ["a": JSONValue("hello"), "b": JSONValue(true)]; 16 | auto obj = JSONValue(aa); 17 | 18 | // JSONValue behaves almost as the contained native D types 19 | assert(obj["a"] == "hello"); 20 | assert(obj["b"] == true); 21 | } 22 | 23 | import stdx.data.json.foundation; 24 | import std.typecons : Nullable; 25 | import taggedalgebraic; 26 | 27 | 28 | /** 29 | * Represents a generic JSON value. 30 | * 31 | * The $(D JSONValue) type is based on $(D std.variant.Algebraic) and as such 32 | * provides the usual binary and unary operators for handling the contained 33 | * raw value. 34 | * 35 | * Raw values can be either $(D null), $(D bool), $(D double), $(D string), 36 | * $(D JSONValue[]) or $(D JSONValue[string]). 37 | */ 38 | struct JSONValue 39 | { 40 | import std.exception : enforce; 41 | import stdx.data.json.lexer : JSONToken; 42 | 43 | /** 44 | * Defines the possible types contained in a `JSONValue` 45 | */ 46 | union PayloadUnion { 47 | typeof(null) null_; /// A JSON `null` value 48 | bool boolean; /// JSON `true` or `false` values 49 | double double_; /// The default field for storing numbers 50 | long integer; /// Only used if `LexOptions.useLong` was set for parsing 51 | WrappedBigInt bigInt; /// Only used if `LexOptions.useBigInt` was set for parsing 52 | @disableIndex .string string; /// String value 53 | JSONValue[] array; /// Array or JSON values 54 | JSONValue[.string] object; /// Dictionary of JSON values (object) 55 | } 56 | 57 | /** 58 | * Alias for a $(D TaggedAlgebraic) able to hold all possible JSON 59 | * value types. 60 | */ 61 | alias Payload = TaggedAlgebraic!PayloadUnion; 62 | 63 | /** 64 | * Holds the data contained in this value. 65 | * 66 | * Note that this is available using $(D alias this), so there is usually no 67 | * need to access this field directly. 68 | */ 69 | Payload payload; 70 | 71 | /** 72 | * Optional location of the corresponding token in the source document. 73 | * 74 | * This field will be automatically populated by the JSON parser if location 75 | * tracking is enabled. 76 | */ 77 | Location location; 78 | 79 | /// 80 | alias payload this; 81 | 82 | /** 83 | * Constructs a JSONValue from the given raw value. 84 | */ 85 | this(T)(T value, Location loc = Location.init) { payload = Payload(value); location = loc; } 86 | /// ditto 87 | void opAssign(T)(T value) { payload = value; } 88 | 89 | /// Tests if the stored value is of a given type. 90 | bool hasType(T)() const { return .hasType!T(payload); } 91 | 92 | /// Tests if the stored value is of kind `Kind.null_`. 93 | bool isNull() const { return payload.kind == Kind.null_; } 94 | 95 | /** 96 | * Returns the raw contained value. 97 | * 98 | * This must only be called if the type of the stored value matches `T`. 99 | * Use `.hasType!T` or `.typeID` for that purpose. 100 | */ 101 | ref inout(T) get(T)() inout { return .get!T(payload); } 102 | 103 | /** 104 | * Enables equality comparisons. 105 | * 106 | * Note that the location is considered token metadata and thus does not 107 | * affect the comparison. 108 | */ 109 | bool opEquals(T)(auto ref inout(T) other) inout 110 | { 111 | import std.traits : Unqual; 112 | 113 | static if (is(Unqual!T == typeof(null))) 114 | { 115 | return this.isNull; 116 | } 117 | else static if (is(Unqual!T == JSONValue)) 118 | { 119 | return this.payload == other.payload; 120 | } 121 | else 122 | { 123 | return this.payload == other; 124 | } 125 | } 126 | } 127 | 128 | /// Shows the basic construction and operations on JSON values. 129 | unittest 130 | { 131 | JSONValue a = 12; 132 | JSONValue b = 13; 133 | 134 | assert(a == 12.0); 135 | assert(b == 13.0); 136 | assert(a + b == 25.0); 137 | 138 | auto c = JSONValue([a, b]); 139 | assert(c[0] == 12.0); 140 | assert(c[1] == 13.0); 141 | assert(c[0] == a); 142 | assert(c[1] == b); 143 | 144 | auto d = JSONValue(["a": a, "b": b]); 145 | assert(d["a"] == 12.0); 146 | assert(d["b"] == 13.0); 147 | assert(d["a"] == a); 148 | assert(d["b"] == b); 149 | } 150 | 151 | // Unittests for JSONValue equality comparisons 152 | unittest 153 | { 154 | JSONValue nullval = null; 155 | assert(nullval.hasType!(typeof(null))()); 156 | assert(nullval == null); 157 | assert(nullval == nullval); 158 | 159 | JSONValue boolval = true; 160 | assert(boolval.hasType!bool()); 161 | assert(boolval == true); 162 | assert(boolval == boolval); 163 | 164 | JSONValue intval = 22; 165 | assert(intval.hasType!long()); 166 | assert(intval == 22); 167 | assert(intval == 22.0); 168 | assert(intval == intval); 169 | 170 | JSONValue longval = 56L; 171 | assert(longval.hasType!long()); 172 | assert(longval == 56); 173 | assert(longval == 56.0); 174 | assert(longval == longval); 175 | 176 | assert(intval + longval == 78); 177 | assert(intval + longval == intval + longval); 178 | 179 | JSONValue floatval = 32.0f; 180 | assert(floatval.hasType!double()); 181 | assert(floatval == 32); 182 | assert(floatval == 32.0); 183 | assert(floatval == floatval); 184 | 185 | JSONValue doubleval = 63.5; 186 | assert(doubleval.hasType!double()); 187 | assert(doubleval == 63.5); 188 | assert(doubleval == doubleval); 189 | 190 | assert(floatval + doubleval == 95.5); 191 | assert(floatval + doubleval == floatval + doubleval); 192 | assert(intval + longval + floatval + doubleval == 173.5); 193 | assert(intval + longval + floatval + doubleval == 194 | intval + longval + floatval + doubleval); 195 | 196 | JSONValue strval = "Hello!"; 197 | assert(strval.hasType!string()); 198 | assert(strval == "Hello!"); 199 | assert(strval == strval); 200 | 201 | auto arrval = JSONValue([floatval, doubleval]); 202 | assert(arrval.hasType!(JSONValue[])()); 203 | assert(arrval == [floatval, doubleval]); 204 | assert(arrval == [32.0, 63.5]); 205 | assert(arrval[0] == floatval); 206 | assert(arrval[0] == 32.0); 207 | assert(arrval[1] == doubleval); 208 | assert(arrval[1] == 63.5); 209 | assert(arrval == arrval); 210 | 211 | auto objval = JSONValue(["f": floatval, "d": doubleval]); 212 | assert(objval.hasType!(JSONValue[string])()); 213 | assert(objval["f"] == floatval); 214 | assert(objval["f"] == 32.0); 215 | assert(objval["d"] == doubleval); 216 | assert(objval["d"] == 63.5); 217 | assert(objval == objval); 218 | } 219 | 220 | 221 | /// Proxy structure that stores BigInt as a pointer to save space in JSONValue 222 | static struct WrappedBigInt { 223 | import std.bigint; 224 | private BigInt* _pvalue; 225 | /// 226 | this(BigInt value) { _pvalue = new BigInt(value); } 227 | /// 228 | @property ref inout(BigInt) value() inout { return *_pvalue; } 229 | } 230 | 231 | 232 | /** 233 | * Allows safe access of sub paths of a `JSONValue`. 234 | * 235 | * Missing intermediate values will not cause an error, but will instead 236 | * just cause the final path node to be marked as non-existent. See the 237 | * example below for the possbile use cases. 238 | */ 239 | auto opt()(auto ref JSONValue val) 240 | { 241 | alias C = JSONValue; // this function is generic and could also operate on BSONValue or similar types 242 | static struct S(F...) { 243 | private { 244 | static if (F.length > 0) { 245 | S!(F[0 .. $-1])* _parent; 246 | F[$-1] _field; 247 | } 248 | else 249 | { 250 | C* _container; 251 | } 252 | } 253 | 254 | static if (F.length == 0) 255 | { 256 | this(ref C container) 257 | { 258 | () @trusted { _container = &container; } (); 259 | } 260 | } 261 | else 262 | { 263 | this (ref S!(F[0 .. $-1]) s, F[$-1] field) 264 | { 265 | () @trusted { _parent = &s; } (); 266 | _field = field; 267 | } 268 | } 269 | 270 | @disable this(); // don't let the reference escape 271 | 272 | @property bool exists() const { return resolve !is null; } 273 | 274 | inout(JSONValue) get() inout 275 | { 276 | auto val = this.resolve(); 277 | if (val is null) 278 | throw new .Exception("Missing JSON value at "~this.path()~"."); 279 | return *val; 280 | } 281 | 282 | inout(T) get(T)(T def_value) inout 283 | { 284 | auto val = resolve(); 285 | if (val is null || !val.hasType!T) 286 | return def_value; 287 | return val.get!T; 288 | } 289 | 290 | alias get this; 291 | 292 | @property auto opDispatch(string name)() 293 | { 294 | return S!(F, string)(this, name); 295 | } 296 | 297 | @property void opDispatch(string name, T)(T value) 298 | { 299 | (*resolveWrite(OptWriteMode.dict))[name] = value; 300 | } 301 | 302 | auto opIndex()(size_t idx) 303 | { 304 | return S!(F, size_t)(this, idx); 305 | } 306 | 307 | auto opIndex()(string name) 308 | { 309 | return S!(F, string)(this, name); 310 | } 311 | 312 | auto opIndexAssign(T)(T value, size_t idx) 313 | { 314 | *(this[idx].resolveWrite(OptWriteMode.any)) = value; 315 | } 316 | 317 | auto opIndexAssign(T)(T value, string name) 318 | { 319 | (*resolveWrite(OptWriteMode.dict))[name] = value; 320 | } 321 | 322 | private inout(C)* resolve() 323 | inout { 324 | static if (F.length > 0) 325 | { 326 | auto c = this._parent.resolve(); 327 | if (!c) return null; 328 | static if (is(F[$-1] : long)) { 329 | if (!c.hasType!(C[])) return null; 330 | if (_field < c.length) return &c.get!(C[])[_field]; 331 | return null; 332 | } 333 | else 334 | { 335 | if (!c.hasType!(C[string])) return null; 336 | return this._field in *c; 337 | } 338 | } 339 | else 340 | { 341 | return _container; 342 | } 343 | } 344 | 345 | private C* resolveWrite(OptWriteMode mode) 346 | { 347 | C* v; 348 | static if (F.length == 0) 349 | { 350 | v = _container; 351 | } 352 | else 353 | { 354 | auto c = _parent.resolveWrite(is(F[$-1] == string) ? OptWriteMode.dict : OptWriteMode.array); 355 | static if (is(F[$-1] == string)) 356 | { 357 | v = _field in *c; 358 | if (!v) 359 | { 360 | (*c)[_field] = mode == OptWriteMode.dict ? C(cast(C[string])null) : C(cast(C[])null); 361 | v = _field in *c; 362 | } 363 | } 364 | else 365 | { 366 | import std.conv : to; 367 | if (_field >= c.length) 368 | throw new Exception("Array index "~_field.to!string()~" out of bounds ("~c.length.to!string()~") for "~_parent.path()~"."); 369 | v = &c.get!(C[])[_field]; 370 | } 371 | } 372 | 373 | final switch (mode) 374 | { 375 | case OptWriteMode.dict: 376 | if (!v.hasType!(C[string])) 377 | throw new .Exception(pname()~" is not a dictionary/object. Cannot set a field."); 378 | break; 379 | case OptWriteMode.array: 380 | if (!v.hasType!(C[])) 381 | throw new .Exception(pname()~" is not an array. Cannot set an entry."); 382 | break; 383 | case OptWriteMode.any: break; 384 | } 385 | 386 | return v; 387 | } 388 | 389 | private string path() const 390 | { 391 | static if (F.length > 0) 392 | { 393 | import std.conv : to; 394 | static if (is(F[$-1] == string)) return this._parent.path() ~ "." ~ this._field; 395 | else return this._parent.path() ~ "[" ~ this._field.to!string ~ "]"; 396 | } 397 | else 398 | { 399 | return ""; 400 | } 401 | } 402 | 403 | private string pname() const 404 | { 405 | static if (F.length > 0) return "Field "~_parent.path(); 406 | else return "Value"; 407 | } 408 | } 409 | 410 | return S!()(val); 411 | } 412 | 413 | /// 414 | unittest 415 | { 416 | import std.exception : assertThrown; 417 | 418 | JSONValue subobj = ["b": JSONValue(1.0), "c": JSONValue(2.0)]; 419 | JSONValue subarr = [JSONValue(3.0), JSONValue(4.0), JSONValue(null)]; 420 | JSONValue obj = ["a": subobj, "b": subarr]; 421 | 422 | // access nested fields using member access syntax 423 | assert(opt(obj).a.b == 1.0); 424 | assert(opt(obj).a.c == 2.0); 425 | 426 | // get can be used with a default value 427 | assert(opt(obj).a.c.get(-1.0) == 2.0); // matched path and type 428 | assert(opt(obj).a.c.get(null) == null); // mismatched type -> return default value 429 | assert(opt(obj).a.d.get(-1.0) == -1.0); // mismatched path -> return default value 430 | 431 | // explicit existence check 432 | assert(!opt(obj).x.exists); 433 | assert(!opt(obj).a.x.y.exists); // works for nested missing paths, too 434 | 435 | // instead of using member syntax, index syntax can be used 436 | assert(opt(obj)["a"]["b"] == 1.0); 437 | 438 | // integer indices work, too 439 | assert(opt(obj).b[0] == 3.0); 440 | assert(opt(obj).b[1] == 4.0); 441 | assert(opt(obj).b[2].exists); 442 | assert(opt(obj).b[2] == null); 443 | assert(!opt(obj).b[3].exists); 444 | 445 | // accessing a missing path throws an exception 446 | assertThrown(opt(obj).b[3] == 3); 447 | 448 | // assignments work, too 449 | opt(obj).b[0] = 12; 450 | assert(opt(obj).b[0] == 12); 451 | 452 | // assignments to non-existent paths automatically create all missing parents 453 | opt(obj).c.d.opDispatch!"e"( 12); 454 | assert(opt(obj).c.d.e == 12); 455 | 456 | // writing to paths with conflicting types will throw 457 | assertThrown(opt(obj).c[2] = 12); 458 | 459 | // writing out of array bounds will also throw 460 | assertThrown(opt(obj).b[10] = 12); 461 | } 462 | 463 | private enum OptWriteMode { 464 | dict, 465 | array, 466 | any 467 | } 468 | -------------------------------------------------------------------------------- /source/stdx/data/json/generator.d: -------------------------------------------------------------------------------- 1 | /** 2 | * Contains routines for converting JSON values to their string represencation. 3 | * 4 | * Synopsis: 5 | * --- 6 | * ... 7 | * --- 8 | * 9 | * Copyright: Copyright 2012 - 2015, Sönke Ludwig. 10 | * License: $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0). 11 | * Authors: Sönke Ludwig 12 | * Source: $(PHOBOSSRC std/data/json/generator.d) 13 | */ 14 | module stdx.data.json.generator; 15 | 16 | import stdx.data.json.lexer; 17 | import stdx.data.json.parser; 18 | import stdx.data.json.value; 19 | import std.bigint; 20 | import std.range; 21 | 22 | 23 | /** 24 | * Converts the given JSON document(s) to its string representation. 25 | * 26 | * The input can be a $(D JSONValue), or an input range of either $(D JSONToken) 27 | * or $(D JSONParserNode) elements. By default, the generator will use newlines 28 | * and tabs to pretty-print the result. Use the `options` template parameter 29 | * to customize this. 30 | * 31 | * Params: 32 | * value = A single JSON document 33 | * nodes = A set of JSON documents encoded as single parser nodes. The nodes 34 | * must be in valid document order, or the parser result will be undefined. 35 | * tokens = List of JSON tokens to be converted to strings. The tokens may 36 | * occur in any order and are simply appended in order to the final string. 37 | * token = A single token to convert to a string 38 | * 39 | * Returns: 40 | * Returns a JSON formatted string. 41 | * 42 | * See_also: $(D writeJSON), $(D toPrettyJSON) 43 | */ 44 | string toJSON(GeneratorOptions options = GeneratorOptions.init)(JSONValue value) 45 | { 46 | import std.array; 47 | auto dst = appender!string(); 48 | value.writeJSON!options(dst); 49 | return dst.data; 50 | } 51 | /// ditto 52 | string toJSON(GeneratorOptions options = GeneratorOptions.init, Input)(Input nodes) 53 | if (isJSONParserNodeInputRange!Input) 54 | { 55 | import std.array; 56 | auto dst = appender!string(); 57 | nodes.writeJSON!options(dst); 58 | return dst.data; 59 | } 60 | /// ditto 61 | string toJSON(GeneratorOptions options = GeneratorOptions.init, Input)(Input tokens) 62 | if (isJSONTokenInputRange!Input) 63 | { 64 | import std.array; 65 | auto dst = appender!string(); 66 | tokens.writeJSON!options(dst); 67 | return dst.data; 68 | } 69 | /// ditto 70 | string toJSON(GeneratorOptions options = GeneratorOptions.init, String)(JSONToken!String token) 71 | { 72 | import std.array; 73 | auto dst = appender!string(); 74 | token.writeJSON!options(dst); 75 | return dst.data; 76 | } 77 | 78 | /// 79 | @safe unittest 80 | { 81 | JSONValue value = true; 82 | assert(value.toJSON() == "true"); 83 | } 84 | 85 | /// 86 | @safe unittest 87 | { 88 | auto a = toJSONValue(`{"a": [], "b": [1, {}]}`); 89 | 90 | // pretty print: 91 | // { 92 | // "a": [], 93 | // "b": [ 94 | // 1, 95 | // {}, 96 | // ] 97 | // } 98 | assert( 99 | a.toJSON() == "{\n\t\"a\": [],\n\t\"b\": [\n\t\t1,\n\t\t{}\n\t]\n}" || 100 | a.toJSON() == "{\n\t\"b\": [\n\t\t1,\n\t\t{}\n\t],\n\t\"a\": []\n}" 101 | ); 102 | 103 | // write compact JSON (order of object fields is undefined) 104 | assert( 105 | a.toJSON!(GeneratorOptions.compact)() == `{"a":[],"b":[1,{}]}` || 106 | a.toJSON!(GeneratorOptions.compact)() == `{"b":[1,{}],"a":[]}` 107 | ); 108 | } 109 | 110 | @safe unittest 111 | { 112 | auto nodes = parseJSONStream(`{"a": [], "b": [1, {}]}`); 113 | assert(nodes.toJSON() == "{\n\t\"a\": [],\n\t\"b\": [\n\t\t1,\n\t\t{}\n\t]\n}"); 114 | assert(nodes.toJSON!(GeneratorOptions.compact)() == `{"a":[],"b":[1,{}]}`); 115 | 116 | auto tokens = lexJSON(`{"a": [], "b": [1, {}, null, true, false]}`); 117 | assert(tokens.toJSON!(GeneratorOptions.compact)() == `{"a":[],"b":[1,{},null,true,false]}`); 118 | 119 | JSONToken!string tok; 120 | tok.string = "Hello World"; 121 | assert(tok.toJSON() == `"Hello World"`); 122 | } 123 | 124 | 125 | /** 126 | * Writes the string representation of the given JSON document(s)/tokens to an 127 | * output range. 128 | * 129 | * See $(D toJSON) for more information. 130 | * 131 | * Params: 132 | * output = The output range to take the result string in UTF-8 encoding. 133 | * value = A single JSON document 134 | * nodes = A set of JSON documents encoded as single parser nodes. The nodes 135 | * must be in valid document order, or the parser result will be undefined. 136 | * tokens = List of JSON tokens to be converted to strings. The tokens may 137 | * occur in any order and are simply appended in order to the final string. 138 | * token = A single token to convert to a string 139 | * 140 | * See_also: $(D toJSON), $(D writePrettyJSON) 141 | */ 142 | void writeJSON(GeneratorOptions options = GeneratorOptions.init, Output)(JSONValue value, ref Output output) 143 | if (isOutputRange!(Output, char)) 144 | { 145 | writeAsStringImpl!options(value, output); 146 | } 147 | /// ditto 148 | void writeJSON(GeneratorOptions options = GeneratorOptions.init, Output, Input)(Input nodes, ref Output output) 149 | if (isOutputRange!(Output, char) && isJSONParserNodeInputRange!Input) 150 | { 151 | //import std.algorithm.mutation : copy; 152 | auto joutput = JSONOutputRange!(Output, options)(output); 153 | foreach (n; nodes) joutput.put(n); 154 | //copy(nodes, joutput); 155 | } 156 | /// ditto 157 | void writeJSON(GeneratorOptions options = GeneratorOptions.init, Output, Input)(Input tokens, ref Output output) 158 | if (isOutputRange!(Output, char) && isJSONTokenInputRange!Input) 159 | { 160 | while (!tokens.empty) 161 | { 162 | tokens.front.writeJSON!options(output); 163 | tokens.popFront(); 164 | } 165 | } 166 | /// ditto 167 | void writeJSON(GeneratorOptions options = GeneratorOptions.init, String, Output)(const ref JSONToken!String token, ref Output output) 168 | if (isOutputRange!(Output, char)) 169 | { 170 | final switch (token.kind) with (JSONTokenKind) 171 | { 172 | case none: assert(false); 173 | case error: output.put("_error_"); break; 174 | case null_: output.put("null"); break; 175 | case boolean: output.put(token.boolean ? "true" : "false"); break; 176 | case number: output.writeNumber!options(token.number); break; 177 | case string: output.put('"'); output.escapeString!(options & GeneratorOptions.escapeUnicode)(token.string); output.put('"'); break; 178 | case objectStart: output.put('{'); break; 179 | case objectEnd: output.put('}'); break; 180 | case arrayStart: output.put('['); break; 181 | case arrayEnd: output.put(']'); break; 182 | case colon: output.put(':'); break; 183 | case comma: output.put(','); break; 184 | } 185 | } 186 | 187 | /** Convenience function for creating a `JSONOutputRange` instance using IFTI. 188 | */ 189 | JSONOutputRange!(R, options) jsonOutputRange(GeneratorOptions options = GeneratorOptions.init, R)(R output) 190 | if (isOutputRange!(R, char)) 191 | { 192 | return JSONOutputRange!(R, options)(output); 193 | } 194 | 195 | /** Output range that takes JSON primitives and outputs to a character output 196 | range. 197 | 198 | This range provides the underlying functinality for `writeJSON` and 199 | `toJSON` and is well suited as a target for serialization frameworks. 200 | 201 | Note that pretty-printing (`GeneratorOptions.compact` not set) is currently 202 | only supported for primitives of type `JSONParserNode`. 203 | */ 204 | struct JSONOutputRange(R, GeneratorOptions options = GeneratorOptions.init) 205 | if (isOutputRange!(R, char)) 206 | { 207 | private { 208 | R m_output; 209 | size_t m_nesting = 0; 210 | bool m_first = false; 211 | bool m_isObjectField = false; 212 | } 213 | 214 | /** Constructs the range for a given character output range. 215 | */ 216 | this(R output) 217 | { 218 | m_output = output; 219 | } 220 | 221 | /** Writes a single JSON primitive to the destination character range. 222 | */ 223 | void put(String)(JSONParserNode!String node) 224 | { 225 | enum pretty_print = (options & GeneratorOptions.compact) == 0; 226 | 227 | final switch (node.kind) with (JSONParserNodeKind) { 228 | case none: assert(false); 229 | case key: 230 | if (m_nesting > 0 && !m_first) m_output.put(','); 231 | else m_first = false; 232 | m_isObjectField = true; 233 | static if (pretty_print) indent(); 234 | m_output.put('"'); 235 | m_output.escapeString!(options & GeneratorOptions.escapeUnicode)(node.key); 236 | m_output.put(pretty_print ? `": ` : `":`); 237 | break; 238 | case literal: 239 | preValue(); 240 | node.literal.writeJSON!options(m_output); 241 | break; 242 | case objectStart: 243 | preValue(); 244 | m_output.put('{'); 245 | m_nesting++; 246 | m_first = true; 247 | break; 248 | case objectEnd: 249 | m_nesting--; 250 | static if (pretty_print) 251 | { 252 | if (!m_first) indent(); 253 | } 254 | m_first = false; 255 | m_output.put('}'); 256 | break; 257 | case arrayStart: 258 | preValue(); 259 | m_output.put('['); 260 | m_nesting++; 261 | m_first = true; 262 | m_isObjectField = false; 263 | break; 264 | case arrayEnd: 265 | m_nesting--; 266 | static if (pretty_print) 267 | { 268 | if (!m_first) indent(); 269 | } 270 | m_first = false; 271 | m_output.put(']'); 272 | break; 273 | } 274 | } 275 | /// ditto 276 | void put(String)(JSONToken!String token) 277 | { 278 | final switch (token.kind) with (JSONToken.Kind) { 279 | case none: assert(false); 280 | case error: m_output.put("_error_"); break; 281 | case null_: put(null); break; 282 | case boolean: put(token.boolean); break; 283 | case number: put(token.number); break; 284 | case string: put(token.string); break; 285 | case objectStart: m_output.put('{'); break; 286 | case objectEnd: m_output.put('}'); break; 287 | case arrayStart: m_output.put('['); break; 288 | case arrayEnd: m_output.put(']'); break; 289 | case colon: m_output.put(':'); break; 290 | case comma: m_output.put(','); break; 291 | } 292 | } 293 | /// ditto 294 | void put(typeof(null)) { m_output.put("null"); } 295 | /// ditto 296 | void put(bool value) { m_output.put(value ? "true" : "false"); } 297 | /// ditto 298 | void put(long value) { m_output.writeNumber(value); } 299 | /// ditto 300 | void put(BigInt value) { m_output.writeNumber(value); } 301 | /// ditto 302 | void put(double value) { m_output.writeNumber!options(value); } 303 | /// ditto 304 | void put(String)(JSONString!String value) 305 | { 306 | auto s = value.anyValue; 307 | if (s[0]) put(s[1]); // decoded string 308 | else m_output.put(s[1]); // raw string literal 309 | } 310 | /// ditto 311 | void put(string value) 312 | { 313 | m_output.put('"'); 314 | m_output.escapeString!(options & GeneratorOptions.escapeUnicode)(value); 315 | m_output.put('"'); 316 | } 317 | 318 | private void indent() 319 | { 320 | m_output.put('\n'); 321 | foreach (tab; 0 .. m_nesting) m_output.put('\t'); 322 | } 323 | 324 | private void preValue() 325 | { 326 | if (!m_isObjectField) 327 | { 328 | if (m_nesting > 0 && !m_first) m_output.put(','); 329 | else m_first = false; 330 | static if (!(options & GeneratorOptions.compact)) 331 | { 332 | if (m_nesting > 0) indent(); 333 | } 334 | } 335 | else m_isObjectField = false; 336 | } 337 | } 338 | 339 | @safe unittest { 340 | auto app = appender!(char[]); 341 | auto dst = jsonOutputRange(app); 342 | dst.put(true); 343 | dst.put(1234); 344 | dst.put("hello"); 345 | assert(app.data == "true1234\"hello\""); 346 | } 347 | 348 | @safe unittest { 349 | auto app = appender!(char[]); 350 | auto dst = jsonOutputRange(app); 351 | foreach (n; parseJSONStream(`{"foo":42, "bar":true, "baz": [null, false]}`)) 352 | dst.put(n); 353 | assert(app.data == "{\n\t\"foo\": 42,\n\t\"bar\": true,\n\t\"baz\": [\n\t\tnull,\n\t\tfalse\n\t]\n}"); 354 | } 355 | 356 | 357 | /** 358 | * Flags for configuring the JSON generator. 359 | * 360 | * These flags can be combined using a bitwise or operation. 361 | */ 362 | enum GeneratorOptions { 363 | /// Default value - enable none of the supported options 364 | init = 0, 365 | 366 | /// Avoid outputting whitespace to get a compact string representation 367 | compact = 1<<0, 368 | 369 | /// Output special float values as 'NaN' or 'Infinity' instead of 'null' 370 | specialFloatLiterals = 1<<1, 371 | 372 | /// Output all non-ASCII characters as unicode escape sequences 373 | escapeUnicode = 1<<2, 374 | } 375 | 376 | 377 | @safe private void writeAsStringImpl(GeneratorOptions options, Output)(JSONValue value, ref Output output, size_t nesting_level = 0) 378 | if (isOutputRange!(Output, char)) 379 | { 380 | import taggedalgebraic : get; 381 | 382 | enum pretty_print = (options & GeneratorOptions.compact) == 0; 383 | 384 | void indent(size_t depth) 385 | { 386 | output.put('\n'); 387 | foreach (tab; 0 .. depth) output.put('\t'); 388 | } 389 | 390 | final switch (value.kind) { 391 | case JSONValue.Kind.null_: output.put("null"); break; 392 | case JSONValue.Kind.boolean: output.put(value == true ? "true" : "false"); break; 393 | case JSONValue.Kind.double_: output.writeNumber!options(cast(double)value); break; 394 | case JSONValue.Kind.integer: output.writeNumber(cast(long)value); break; 395 | case JSONValue.Kind.bigInt: () @trusted { 396 | auto val = cast(BigInt*)value; 397 | if (val is null) throw new Exception("Null BigInt value"); 398 | output.writeNumber(*val); 399 | }(); break; 400 | case JSONValue.Kind.string: output.put('"'); output.escapeString!(options & GeneratorOptions.escapeUnicode)(get!string(value)); output.put('"'); break; 401 | case JSONValue.Kind.object: 402 | output.put('{'); 403 | bool first = true; 404 | foreach (string k, ref e; get!(JSONValue[string])(value)) 405 | { 406 | if (!first) output.put(','); 407 | else first = false; 408 | static if (pretty_print) indent(nesting_level+1); 409 | output.put('\"'); 410 | output.escapeString!(options & GeneratorOptions.escapeUnicode)(k); 411 | output.put(pretty_print ? `": ` : `":`); 412 | e.writeAsStringImpl!options(output, nesting_level+1); 413 | } 414 | static if (pretty_print) 415 | { 416 | if (!first) indent(nesting_level); 417 | } 418 | output.put('}'); 419 | break; 420 | case JSONValue.Kind.array: 421 | output.put('['); 422 | foreach (i, ref e; get!(JSONValue[])(value)) 423 | { 424 | if (i > 0) output.put(','); 425 | static if (pretty_print) indent(nesting_level+1); 426 | e.writeAsStringImpl!options(output, nesting_level+1); 427 | } 428 | static if (pretty_print) 429 | { 430 | if (get!(JSONValue[])(value).length > 0) indent(nesting_level); 431 | } 432 | output.put(']'); 433 | break; 434 | } 435 | } 436 | 437 | private void writeNumber(GeneratorOptions options, R)(ref R dst, JSONNumber num) @trusted 438 | { 439 | import std.format; 440 | import std.math; 441 | 442 | final switch (num.type) 443 | { 444 | case JSONNumber.Type.double_: dst.writeNumber!options(num.doubleValue); break; 445 | case JSONNumber.Type.long_: dst.writeNumber(num.longValue); break; 446 | case JSONNumber.Type.bigInt: dst.writeNumber(num.bigIntValue); break; 447 | } 448 | } 449 | 450 | private void writeNumber(GeneratorOptions options, R)(ref R dst, double num) @trusted 451 | { 452 | import std.format; 453 | import std.math; 454 | 455 | static if (options & GeneratorOptions.specialFloatLiterals) 456 | { 457 | if (isNaN(num)) dst.put("NaN"); 458 | else if (num == +double.infinity) dst.put("Infinity"); 459 | else if (num == -double.infinity) dst.put("-Infinity"); 460 | else dst.formattedWrite("%.16g", num); 461 | } 462 | else 463 | { 464 | if (isNaN(num) || num == -double.infinity || num == double.infinity) 465 | dst.put("null"); 466 | else dst.formattedWrite("%.16g", num); 467 | } 468 | } 469 | 470 | private void writeNumber(R)(ref R dst, long num) @trusted 471 | { 472 | import std.format; 473 | dst.formattedWrite("%d", num); 474 | } 475 | 476 | private void writeNumber(R)(ref R dst, BigInt num) @trusted 477 | { 478 | () @trusted { num.toString(str => dst.put(str), null); } (); 479 | } 480 | 481 | @safe unittest 482 | { 483 | import std.math; 484 | import std.string; 485 | 486 | auto num = toJSONValue("-67.199307"); 487 | auto exp = -67.199307; 488 | assert(num.get!double.approxEqual(exp)); 489 | 490 | auto snum = appender!string; 491 | snum.writeNumber!(GeneratorOptions.init)(JSONNumber(num.get!double)); 492 | auto pnum = toJSONValue(snum.data); 493 | assert(pnum.get!double.approxEqual(num.get!double)); 494 | } 495 | 496 | @safe unittest // special float values 497 | { 498 | static void test(GeneratorOptions options = GeneratorOptions.init)(double val, string expected) 499 | { 500 | auto dst = appender!string; 501 | dst.writeNumber!options(val); 502 | assert(dst.data == expected); 503 | } 504 | 505 | test(double.nan, "null"); 506 | test(double.infinity, "null"); 507 | test(-double.infinity, "null"); 508 | test!(GeneratorOptions.specialFloatLiterals)(double.nan, "NaN"); 509 | test!(GeneratorOptions.specialFloatLiterals)(double.infinity, "Infinity"); 510 | test!(GeneratorOptions.specialFloatLiterals)(-double.infinity, "-Infinity"); 511 | } 512 | 513 | private void escapeString(bool use_surrogates = false, R)(ref R dst, string s) 514 | { 515 | import std.format; 516 | import std.utf : decode; 517 | 518 | for (size_t pos = 0; pos < s.length; pos++) 519 | { 520 | immutable ch = s[pos]; 521 | 522 | switch (ch) 523 | { 524 | case '\\': dst.put(`\\`); break; 525 | case '\b': dst.put(`\b`); break; 526 | case '\f': dst.put(`\f`); break; 527 | case '\r': dst.put(`\r`); break; 528 | case '\n': dst.put(`\n`); break; 529 | case '\t': dst.put(`\t`); break; 530 | case '\"': dst.put(`\"`); break; 531 | default: 532 | static if (use_surrogates) 533 | { 534 | // output non-control char ASCII characters directly 535 | // note that 0x7F is the DEL control charactor 536 | if (ch >= 0x20 && ch < 0x7F) 537 | { 538 | dst.put(ch); 539 | break; 540 | } 541 | 542 | dchar cp = decode(s, pos); 543 | pos--; // account for the next loop increment 544 | 545 | // encode as one or two UTF-16 code points 546 | if (cp < 0x10000) 547 | { // in BMP -> 1 CP 548 | formattedWrite(dst, "\\u%04X", cp); 549 | } 550 | else 551 | { // not in BMP -> surrogate pair 552 | int first, last; 553 | cp -= 0x10000; 554 | first = 0xD800 | ((cp & 0xffc00) >> 10); 555 | last = 0xDC00 | (cp & 0x003ff); 556 | formattedWrite(dst, "\\u%04X\\u%04X", first, last); 557 | } 558 | } 559 | else 560 | { 561 | if (ch < 0x20 && ch != 0x7F) formattedWrite(dst, "\\u%04X", ch); 562 | else dst.put(ch); 563 | } 564 | break; 565 | } 566 | } 567 | } 568 | 569 | @safe unittest 570 | { 571 | static void test(bool surrog)(string str, string expected) 572 | { 573 | auto res = appender!string; 574 | res.escapeString!surrog(str); 575 | assert(res.data == expected, res.data); 576 | } 577 | 578 | test!false("hello", "hello"); 579 | test!false("hällo", "hällo"); 580 | test!false("a\U00010000b", "a\U00010000b"); 581 | test!false("a\u1234b", "a\u1234b"); 582 | test!false("\r\n\b\f\t\\\"", `\r\n\b\f\t\\\"`); 583 | test!true("hello", "hello"); 584 | test!true("hällo", `h\u00E4llo`); 585 | test!true("a\U00010000b", `a\uD800\uDC00b`); 586 | test!true("a\u1234b", `a\u1234b`); 587 | test!true("\r\n\b\f\t\\\"", `\r\n\b\f\t\\\"`); 588 | } 589 | -------------------------------------------------------------------------------- /source/stdx/data/json/parser.d: -------------------------------------------------------------------------------- 1 | /** 2 | * Provides various means for parsing JSON documents. 3 | * 4 | * This module contains two different parser implementations. The first 5 | * implementation returns a single JSON document in the form of a 6 | * $(D JSONValue), while the second implementation returns a stream 7 | * of nodes. The stream based parser is particularly useful for 8 | * deserializing with few allocations or for processing large documents. 9 | * 10 | * Copyright: Copyright 2012 - 2015, Sönke Ludwig. 11 | * License: $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0). 12 | * Authors: Sönke Ludwig 13 | * Source: $(PHOBOSSRC std/data/json/parser.d) 14 | */ 15 | module stdx.data.json.parser; 16 | 17 | /// 18 | unittest 19 | { 20 | import std.algorithm : equal, map; 21 | 22 | // Parse a JSON string to a single value 23 | JSONValue value = toJSONValue(`{"name": "D", "kind": "language"}`); 24 | 25 | // Parse a JSON string to a node stream 26 | auto nodes = parseJSONStream(`{"name": "D", "kind": "language"}`); 27 | with (JSONParserNodeKind) { 28 | assert(nodes.map!(n => n.kind).equal( 29 | [objectStart, key, literal, key, literal, objectEnd])); 30 | } 31 | 32 | // Parse a list of tokens instead of a string 33 | auto tokens = lexJSON(`{"name": "D", "kind": "language"}`); 34 | JSONValue value2 = toJSONValue(tokens); 35 | assert(value == value2); 36 | } 37 | 38 | import std.array : appender; 39 | import std.range : ElementType, isInputRange; 40 | import std.traits : isIntegral, isSomeChar; 41 | 42 | import stdx.data.json.lexer; 43 | import stdx.data.json.value; 44 | 45 | /// The default amount of nesting in the input allowed by `toJSONValue` and `parseJSONValue`. 46 | enum defaultMaxDepth = 512; 47 | 48 | /** 49 | * Parses a JSON string or token range and returns the result as a 50 | * `JSONValue`. 51 | * 52 | * The input string must be a valid JSON document. In particular, it must not 53 | * contain any additional text other than whitespace after the end of the 54 | * JSON document. 55 | * 56 | * See_also: `parseJSONValue` 57 | */ 58 | JSONValue toJSONValue(LexOptions options = LexOptions.init, Input)(Input input, string filename = "", int maxDepth = defaultMaxDepth) 59 | if (isInputRange!Input && (isSomeChar!(ElementType!Input) || isIntegral!(ElementType!Input))) 60 | { 61 | auto tokens = lexJSON!options(input, filename); 62 | return toJSONValue(tokens, maxDepth); 63 | } 64 | /// ditto 65 | JSONValue toJSONValue(Input)(Input tokens, int maxDepth = defaultMaxDepth) 66 | if (isJSONTokenInputRange!Input) 67 | { 68 | import stdx.data.json.foundation; 69 | auto ret = parseJSONValue(tokens, maxDepth); 70 | enforceJson(tokens.empty, "Unexpected characters following JSON", tokens.location); 71 | return ret; 72 | } 73 | 74 | /// 75 | @safe unittest 76 | { 77 | // parse a simple number 78 | JSONValue a = toJSONValue(`1.0`); 79 | assert(a == 1.0); 80 | 81 | // parse an object 82 | JSONValue b = toJSONValue(`{"a": true, "b": "test"}`); 83 | auto bdoc = b.get!(JSONValue[string]); 84 | assert(bdoc.length == 2); 85 | assert(bdoc["a"] == true); 86 | assert(bdoc["b"] == "test"); 87 | 88 | // parse an array 89 | JSONValue c = toJSONValue(`[1, 2, null]`); 90 | auto cdoc = c.get!(JSONValue[]); 91 | assert(cdoc.length == 3); 92 | assert(cdoc[0] == 1.0); 93 | assert(cdoc[1] == 2.0); 94 | assert(cdoc[2] == null); 95 | 96 | import std.conv; 97 | JSONValue jv = toJSONValue(`{ "a": 1234 }`); 98 | assert(jv["a"].to!int == 1234); 99 | } 100 | 101 | unittest { // issue #22 102 | import std.conv; 103 | JSONValue jv = toJSONValue(`{ "a": 1234 }`); 104 | assert(jv["a"].to!int == 1234); 105 | } 106 | 107 | /*unittest 108 | { 109 | import std.bigint; 110 | auto v = toJSONValue!(LexOptions.useBigInt)(`{"big": 12345678901234567890}`); 111 | assert(v["big"].value == BigInt("12345678901234567890")); 112 | }*/ 113 | 114 | @safe unittest 115 | { 116 | import std.exception; 117 | assertNotThrown(toJSONValue("{} \t\r\n")); 118 | assertThrown(toJSONValue(`{} {}`)); 119 | } 120 | 121 | 122 | /** 123 | * Consumes a single JSON value from the input range and returns the result as a 124 | * `JSONValue`. 125 | * 126 | * The input string must start with a valid JSON document. Any characters 127 | * occurring after this document will be left in the input range. Use 128 | * `toJSONValue` instead if you wish to perform a normal string to `JSONValue` 129 | * conversion. 130 | * 131 | * See_also: `toJSONValue` 132 | */ 133 | JSONValue parseJSONValue(LexOptions options = LexOptions.init, Input)(ref Input input, string filename = "", int maxDepth = defaultMaxDepth) 134 | if (isInputRange!Input && (isSomeChar!(ElementType!Input) || isIntegral!(ElementType!Input))) 135 | { 136 | import stdx.data.json.foundation; 137 | 138 | auto tokens = lexJSON!options(input, filename); 139 | auto ret = parseJSONValue(tokens, maxDepth); 140 | input = tokens.input; 141 | return ret; 142 | } 143 | 144 | /// Parse an object 145 | @safe unittest 146 | { 147 | // parse an object 148 | string str = `{"a": true, "b": "test"}`; 149 | JSONValue v = parseJSONValue(str); 150 | assert(!str.length); // the input has been consumed 151 | 152 | auto obj = v.get!(JSONValue[string]); 153 | assert(obj.length == 2); 154 | assert(obj["a"] == true); 155 | assert(obj["b"] == "test"); 156 | } 157 | 158 | /// Parse multiple consecutive values 159 | @safe unittest 160 | { 161 | string str = `1.0 2.0`; 162 | JSONValue v1 = parseJSONValue(str); 163 | assert(v1 == 1.0); 164 | assert(str == `2.0`); 165 | JSONValue v2 = parseJSONValue(str); 166 | assert(v2 == 2.0); 167 | assert(str == ``); 168 | } 169 | 170 | 171 | /** 172 | * Parses a stream of JSON tokens and returns the result as a $(D JSONValue). 173 | * 174 | * All tokens belonging to the document will be consumed from the input range. 175 | * Any tokens after the end of the first JSON document will be left in the 176 | * input token range for possible later consumption. 177 | */ 178 | @safe JSONValue parseJSONValue(Input)(ref Input tokens, int maxDepth = defaultMaxDepth) 179 | if (isJSONTokenInputRange!Input) 180 | { 181 | import std.array; 182 | import stdx.data.json.foundation; 183 | 184 | enforceJson(maxDepth > 0, "Too much nesting", tokens.location); 185 | 186 | enforceJson(!tokens.empty, "Missing JSON value before EOF", tokens.location); 187 | 188 | JSONValue ret; 189 | 190 | final switch (tokens.front.kind) with (JSONTokenKind) 191 | { 192 | case none: assert(false); 193 | case error: enforceJson(false, "Invalid token encountered", tokens.front.location); assert(false); 194 | case null_: ret = JSONValue(null); break; 195 | case boolean: ret = JSONValue(tokens.front.boolean); break; 196 | case number: 197 | final switch (tokens.front.number.type) 198 | { 199 | case JSONNumber.Type.double_: ret = tokens.front.number.doubleValue; break; 200 | case JSONNumber.Type.long_: ret = tokens.front.number.longValue; break; 201 | case JSONNumber.Type.bigInt: () @trusted { ret = WrappedBigInt(tokens.front.number.bigIntValue); } (); break; 202 | } 203 | break; 204 | case string: ret = JSONValue(tokens.front.string); break; 205 | case objectStart: 206 | auto loc = tokens.front.location; 207 | bool first = true; 208 | JSONValue[.string] obj; 209 | tokens.popFront(); 210 | while (true) 211 | { 212 | enforceJson(!tokens.empty, "Missing closing '}'", loc); 213 | if (tokens.front.kind == objectEnd) break; 214 | 215 | if (!first) 216 | { 217 | enforceJson(tokens.front.kind == comma, "Expected ',' or '}'", tokens.front.location); 218 | tokens.popFront(); 219 | enforceJson(!tokens.empty, "Expected field name", tokens.location); 220 | } 221 | else first = false; 222 | 223 | enforceJson(tokens.front.kind == string, "Expected field name string", tokens.front.location); 224 | auto key = tokens.front.string; 225 | tokens.popFront(); 226 | enforceJson(!tokens.empty && tokens.front.kind == colon, "Expected ':'", 227 | tokens.empty ? tokens.location : tokens.front.location); 228 | tokens.popFront(); 229 | obj[key] = parseJSONValue(tokens, maxDepth - 1); 230 | } 231 | ret = JSONValue(obj, loc); 232 | break; 233 | case arrayStart: 234 | auto loc = tokens.front.location; 235 | bool first = true; 236 | auto array = appender!(JSONValue[]); 237 | tokens.popFront(); 238 | while (true) 239 | { 240 | enforceJson(!tokens.empty, "Missing closing ']'", loc); 241 | if (tokens.front.kind == arrayEnd) break; 242 | 243 | if (!first) 244 | { 245 | enforceJson(tokens.front.kind == comma, "Expected ',' or ']'", tokens.front.location); 246 | tokens.popFront(); 247 | } 248 | else first = false; 249 | 250 | () @trusted { array ~= parseJSONValue(tokens, maxDepth - 1); }(); 251 | } 252 | ret = JSONValue(array.data, loc); 253 | break; 254 | case objectEnd, arrayEnd, colon, comma: 255 | enforceJson(false, "Expected JSON value", tokens.front.location); 256 | assert(false); 257 | } 258 | 259 | tokens.popFront(); 260 | return ret; 261 | } 262 | 263 | /// 264 | @safe unittest 265 | { 266 | // lex 267 | auto tokens = lexJSON(`[1, 2, 3]`); 268 | 269 | // parse 270 | auto doc = parseJSONValue(tokens); 271 | 272 | auto arr = doc.get!(JSONValue[]); 273 | assert(arr.length == 3); 274 | assert(arr[0] == 1.0); 275 | assert(arr[1] == 2.0); 276 | assert(arr[2] == 3.0); 277 | } 278 | 279 | @safe unittest 280 | { 281 | import std.exception; 282 | 283 | assertThrown(toJSONValue(`]`)); 284 | assertThrown(toJSONValue(`}`)); 285 | assertThrown(toJSONValue(`,`)); 286 | assertThrown(toJSONValue(`:`)); 287 | assertThrown(toJSONValue(`{`)); 288 | assertThrown(toJSONValue(`[`)); 289 | assertThrown(toJSONValue(`[1,]`)); 290 | assertThrown(toJSONValue(`[1,,]`)); 291 | assertThrown(toJSONValue(`[1,`)); 292 | assertThrown(toJSONValue(`[1 2]`)); 293 | assertThrown(toJSONValue(`{1: 1}`)); 294 | assertThrown(toJSONValue(`{"a": 1,}`)); 295 | assertThrown(toJSONValue(`{"a" 1}`)); 296 | assertThrown(toJSONValue(`{"a": 1 "b": 2}`)); 297 | } 298 | 299 | @safe unittest 300 | { 301 | import std.exception; 302 | 303 | // Test depth/nesting limitation 304 | assertNotThrown(toJSONValue(`[]`, "", 1)); 305 | // values inside objects/arrays count as a level of nesting 306 | assertThrown(toJSONValue(`[1, 2, 3]`, "", 1)); 307 | assertNotThrown(toJSONValue(`[1, 2, 3]`, "", 2)); 308 | assertThrown(toJSONValue(`[[]]`, "", 1)); 309 | assertNotThrown(toJSONValue(`{}`, "", 1)); 310 | assertThrown(toJSONValue(`{"a": {}}`, "", 1)); 311 | } 312 | 313 | /** 314 | * Parses a JSON document using a lazy parser node range. 315 | * 316 | * This mode parsing mode is similar to a streaming XML (StAX) parser. It can 317 | * be used to parse JSON documents of unlimited size. The memory consumption 318 | * grows linearly with the nesting level (about 4 bytes per level), but is 319 | * independent of the number of values in the JSON document. 320 | * 321 | * The resulting range of nodes is guaranteed to be ordered according to the 322 | * following grammar, where uppercase terminals correspond to the node kind 323 | * (See $(D JSONParserNodeKind)). 324 | * 325 | * $(UL 326 | * $(LI list → value*) 327 | * $(LI value → LITERAL | array | object) 328 | * $(LI array → ARRAYSTART (value)* ARRAYEND) 329 | * $(LI object → OBJECTSTART (KEY value)* OBJECTEND) 330 | * ) 331 | */ 332 | JSONParserRange!(JSONLexerRange!(Input, options, String)) 333 | parseJSONStream(LexOptions options = LexOptions.init, String = string, Input) 334 | (Input input, string filename = null) 335 | if (isInputRange!Input && (isSomeChar!(ElementType!Input) || isIntegral!(ElementType!Input))) 336 | { 337 | return parseJSONStream(lexJSON!(options, String)(input, filename)); 338 | } 339 | /// ditto 340 | JSONParserRange!Input parseJSONStream(Input)(Input tokens) 341 | if (isJSONTokenInputRange!Input) 342 | { 343 | return JSONParserRange!Input(tokens); 344 | } 345 | 346 | /// 347 | @safe unittest 348 | { 349 | import std.algorithm; 350 | 351 | auto rng1 = parseJSONStream(`{ "a": 1, "b": [null] }`); 352 | with (JSONParserNodeKind) 353 | { 354 | assert(rng1.map!(n => n.kind).equal( 355 | [objectStart, key, literal, key, arrayStart, literal, arrayEnd, 356 | objectEnd])); 357 | } 358 | 359 | auto rng2 = parseJSONStream(`1 {"a": 2} null`); 360 | with (JSONParserNodeKind) 361 | { 362 | assert(rng2.map!(n => n.kind).equal( 363 | [literal, objectStart, key, literal, objectEnd, literal])); 364 | } 365 | } 366 | 367 | @safe unittest 368 | { 369 | auto rng = parseJSONStream(`{"a": 1, "b": [null, true], "c": {"d": {}}}`); 370 | with (JSONParserNodeKind) 371 | { 372 | rng.popFront(); 373 | assert(rng.front.kind == key && rng.front.key == "a"); rng.popFront(); 374 | assert(rng.front.kind == literal && rng.front.literal.number == 1.0); rng.popFront(); 375 | assert(rng.front.kind == key && rng.front.key == "b"); rng.popFront(); 376 | assert(rng.front.kind == arrayStart); rng.popFront(); 377 | assert(rng.front.kind == literal && rng.front.literal.kind == JSONTokenKind.null_); rng.popFront(); 378 | assert(rng.front.kind == literal && rng.front.literal.boolean == true); rng.popFront(); 379 | assert(rng.front.kind == arrayEnd); rng.popFront(); 380 | assert(rng.front.kind == key && rng.front.key == "c"); rng.popFront(); 381 | assert(rng.front.kind == objectStart); rng.popFront(); 382 | assert(rng.front.kind == key && rng.front.key == "d"); rng.popFront(); 383 | assert(rng.front.kind == objectStart); rng.popFront(); 384 | assert(rng.front.kind == objectEnd); rng.popFront(); 385 | assert(rng.front.kind == objectEnd); rng.popFront(); 386 | assert(rng.front.kind == objectEnd); rng.popFront(); 387 | assert(rng.empty); 388 | } 389 | } 390 | 391 | @safe unittest 392 | { 393 | auto rng = parseJSONStream(`[]`); 394 | with (JSONParserNodeKind) 395 | { 396 | import std.algorithm; 397 | assert(rng.map!(n => n.kind).equal([arrayStart, arrayEnd])); 398 | } 399 | } 400 | 401 | @safe unittest 402 | { 403 | import std.array; 404 | import std.exception; 405 | 406 | assertThrown(parseJSONStream(`]`).array); 407 | assertThrown(parseJSONStream(`}`).array); 408 | assertThrown(parseJSONStream(`,`).array); 409 | assertThrown(parseJSONStream(`:`).array); 410 | assertThrown(parseJSONStream(`{`).array); 411 | assertThrown(parseJSONStream(`[`).array); 412 | assertThrown(parseJSONStream(`[1,]`).array); 413 | assertThrown(parseJSONStream(`[1,,]`).array); 414 | assertThrown(parseJSONStream(`[1,`).array); 415 | assertThrown(parseJSONStream(`[1 2]`).array); 416 | assertThrown(parseJSONStream(`{1: 1}`).array); 417 | assertThrown(parseJSONStream(`{"a": 1,}`).array); 418 | assertThrown(parseJSONStream(`{"a" 1}`).array); 419 | assertThrown(parseJSONStream(`{"a": 1 "b": 2}`).array); 420 | assertThrown(parseJSONStream(`{"a": 1, "b": [null, true], "c": {"d": {}}}}`).array); 421 | } 422 | 423 | // Not possible to test anymore with the new String customization scheme 424 | /*@safe unittest { // test for @nogc interface 425 | static struct MyAppender 426 | { 427 | @nogc: 428 | void put(string s) { } 429 | void put(dchar ch) {} 430 | void put(char ch) {} 431 | @property string data() { return null; } 432 | } 433 | static MyAppender createAppender() @nogc { return MyAppender.init; } 434 | 435 | static struct EmptyStream 436 | { 437 | @nogc: 438 | @property bool empty() { return true; } 439 | @property dchar front() { return ' '; } 440 | void popFront() { assert(false); } 441 | @property EmptyStream save() { return this; } 442 | } 443 | 444 | void test(T)() 445 | { 446 | T t; 447 | auto str = parseJSONStream!(LexOptions.noThrow, createAppender)(t); 448 | while (!str.empty) { 449 | auto f = str.front; 450 | str.popFront(); 451 | } 452 | } 453 | // just instantiate, don't run 454 | auto t1 = &test!string; 455 | auto t2 = &test!wstring; 456 | auto t3 = &test!dstring; 457 | auto t4 = &test!EmptyStream; 458 | }*/ 459 | 460 | 461 | /** 462 | * Lazy input range of JSON parser nodes. 463 | * 464 | * See $(D parseJSONStream) for more information. 465 | */ 466 | struct JSONParserRange(Input) 467 | if (isJSONTokenInputRange!Input) 468 | { 469 | import stdx.data.json.foundation; 470 | 471 | alias String = typeof(Input.front).String; 472 | 473 | private { 474 | Input _input; 475 | JSONTokenKind[] _containerStack; 476 | size_t _containerStackFill = 0; 477 | JSONParserNodeKind _prevKind; 478 | JSONParserNode!String _node; 479 | } 480 | 481 | /** 482 | * Constructs a new parser range. 483 | */ 484 | this(Input input) 485 | { 486 | _input = input; 487 | } 488 | 489 | /** 490 | * Determines of the range has been exhausted. 491 | */ 492 | @property bool empty() { return _containerStackFill == 0 && _input.empty && _node.kind == JSONParserNodeKind.none; } 493 | 494 | /** 495 | * Returns the current node from the stream. 496 | */ 497 | @property ref const(JSONParserNode!String) front() 498 | { 499 | ensureFrontValid(); 500 | return _node; 501 | } 502 | 503 | /** 504 | * Skips to the next node in the stream. 505 | */ 506 | void popFront() 507 | { 508 | assert(!empty); 509 | ensureFrontValid(); 510 | _prevKind = _node.kind; 511 | _node.kind = JSONParserNodeKind.none; 512 | } 513 | 514 | private void ensureFrontValid() 515 | { 516 | if (_node.kind == JSONParserNodeKind.none) 517 | { 518 | readNext(); 519 | assert(_node.kind != JSONParserNodeKind.none); 520 | } 521 | } 522 | 523 | private void readNext() 524 | { 525 | if (_containerStackFill) 526 | { 527 | if (_containerStack[_containerStackFill-1] == JSONTokenKind.objectStart) 528 | readNextInObject(); 529 | else readNextInArray(); 530 | } 531 | else readNextValue(); 532 | } 533 | 534 | private void readNextInObject() @trusted 535 | { 536 | enforceJson(!_input.empty, "Missing closing '}'", _input.location); 537 | switch (_prevKind) 538 | { 539 | default: assert(false); 540 | case JSONParserNodeKind.objectStart: 541 | if (_input.front.kind == JSONTokenKind.objectEnd) 542 | { 543 | _node.kind = JSONParserNodeKind.objectEnd; 544 | _containerStackFill--; 545 | } 546 | else 547 | { 548 | enforceJson(_input.front.kind == JSONTokenKind.string, 549 | "Expected field name", _input.front.location); 550 | _node.key = _input.front.string; 551 | } 552 | _input.popFront(); 553 | break; 554 | case JSONParserNodeKind.key: 555 | enforceJson(_input.front.kind == JSONTokenKind.colon, 556 | "Expected ':'", _input.front.location); 557 | _input.popFront(); 558 | readNextValue(); 559 | break; 560 | case JSONParserNodeKind.literal, JSONParserNodeKind.objectEnd, JSONParserNodeKind.arrayEnd: 561 | if (_input.front.kind == JSONTokenKind.objectEnd) 562 | { 563 | _node.kind = JSONParserNodeKind.objectEnd; 564 | _containerStackFill--; 565 | } 566 | else 567 | { 568 | enforceJson(_input.front.kind == JSONTokenKind.comma, 569 | "Expected ',' or '}'", _input.front.location); 570 | _input.popFront(); 571 | enforceJson(!_input.empty && _input.front.kind == JSONTokenKind.string, 572 | "Expected field name", _input.front.location); 573 | _node.key = _input.front.string; 574 | } 575 | _input.popFront(); 576 | break; 577 | } 578 | } 579 | 580 | private void readNextInArray() 581 | { 582 | enforceJson(!_input.empty, "Missing closing ']'", _input.location); 583 | switch (_prevKind) 584 | { 585 | default: assert(false); 586 | case JSONParserNodeKind.arrayStart: 587 | if (_input.front.kind == JSONTokenKind.arrayEnd) 588 | { 589 | _node.kind = JSONParserNodeKind.arrayEnd; 590 | _containerStackFill--; 591 | _input.popFront(); 592 | } 593 | else 594 | { 595 | readNextValue(); 596 | } 597 | break; 598 | case JSONParserNodeKind.literal, JSONParserNodeKind.objectEnd, JSONParserNodeKind.arrayEnd: 599 | if (_input.front.kind == JSONTokenKind.arrayEnd) 600 | { 601 | _node.kind = JSONParserNodeKind.arrayEnd; 602 | _containerStackFill--; 603 | _input.popFront(); 604 | } 605 | else 606 | { 607 | enforceJson(_input.front.kind == JSONTokenKind.comma, 608 | "Expected ',' or ']'", _input.front.location); 609 | _input.popFront(); 610 | enforceJson(!_input.empty, "Missing closing ']'", _input.location); 611 | readNextValue(); 612 | } 613 | break; 614 | } 615 | } 616 | 617 | private void readNextValue() 618 | { 619 | switch (_input.front.kind) 620 | { 621 | default: 622 | throw new JSONException("Expected JSON value", _input.location); 623 | case JSONTokenKind.none: assert(false); 624 | case JSONTokenKind.null_, JSONTokenKind.boolean, 625 | JSONTokenKind.number, JSONTokenKind.string: 626 | _node.literal = _input.front; 627 | _input.popFront(); 628 | break; 629 | case JSONTokenKind.objectStart: 630 | _node.kind = JSONParserNodeKind.objectStart; 631 | pushContainer(JSONTokenKind.objectStart); 632 | _input.popFront(); 633 | break; 634 | case JSONTokenKind.arrayStart: 635 | _node.kind = JSONParserNodeKind.arrayStart; 636 | pushContainer(JSONTokenKind.arrayStart); 637 | _input.popFront(); 638 | break; 639 | } 640 | } 641 | 642 | private void pushContainer(JSONTokenKind kind) 643 | { 644 | import std.algorithm/*.comparison*/ : max; 645 | if (_containerStackFill >= _containerStack.length) 646 | _containerStack.length = max(32, _containerStack.length*3/2); 647 | _containerStack[_containerStackFill++] = kind; 648 | } 649 | } 650 | 651 | 652 | /** 653 | * Represents a single node of a JSON parse tree. 654 | * 655 | * See $(D parseJSONStream) and $(D JSONParserRange) more information. 656 | */ 657 | struct JSONParserNode(String) 658 | { 659 | @safe: 660 | import std.algorithm/*.comparison*/ : among; 661 | import stdx.data.json.foundation : Location; 662 | 663 | private alias Kind = JSONParserNodeKind; // compatibility alias 664 | 665 | private 666 | { 667 | Kind _kind = Kind.none; 668 | union 669 | { 670 | String _key; 671 | JSONToken!String _literal; 672 | } 673 | } 674 | 675 | /** 676 | * The kind of this node. 677 | */ 678 | @property Kind kind() const nothrow { return _kind; } 679 | /// ditto 680 | @property Kind kind(Kind value) nothrow 681 | in { assert(!value.among(Kind.key, Kind.literal)); } 682 | do { return _kind = value; } 683 | 684 | /** 685 | * The key identifier for $(D Kind.key) nodes. 686 | * 687 | * Setting the key will automatically switch the node kind. 688 | */ 689 | @property String key() const @trusted nothrow 690 | { 691 | assert(_kind == Kind.key); 692 | return _key; 693 | } 694 | /// ditto 695 | @property String key(String value) nothrow 696 | { 697 | _kind = Kind.key; 698 | return () @trusted { return _key = value; } (); 699 | } 700 | 701 | /** 702 | * The literal token for $(D Kind.literal) nodes. 703 | * 704 | * Setting the literal will automatically switch the node kind. 705 | */ 706 | @property ref inout(JSONToken!String) literal() inout @trusted nothrow 707 | { 708 | assert(_kind == Kind.literal); 709 | return _literal; 710 | } 711 | /// ditto 712 | @property ref JSONToken!String literal(JSONToken!String literal) return nothrow 713 | { 714 | _kind = Kind.literal; 715 | return *() @trusted { return &(_literal = literal); } (); 716 | } 717 | 718 | @property Location location() 719 | const @trusted nothrow { 720 | if (_kind == Kind.literal) return _literal.location; 721 | return Location.init; 722 | } 723 | 724 | /** 725 | * Enables equality comparisons. 726 | * 727 | * Note that the location is considered part of the token and thus is 728 | * included in the comparison. 729 | */ 730 | bool opEquals(const ref JSONParserNode other) 731 | const nothrow 732 | { 733 | if (this.kind != other.kind) return false; 734 | 735 | switch (this.kind) 736 | { 737 | default: return true; 738 | case Kind.literal: return this.literal == other.literal; 739 | case Kind.key: return this.key == other.key; 740 | } 741 | } 742 | /// ditto 743 | bool opEquals(JSONParserNode other) const nothrow { return opEquals(other); } 744 | 745 | unittest 746 | { 747 | JSONToken!string t1, t2, t3; 748 | t1.string = "test"; 749 | t2.string = "test".idup; 750 | t3.string = "other"; 751 | 752 | JSONParserNode!string n1, n2; 753 | n2.literal = t1; assert(n1 != n2); 754 | n1.literal = t1; assert(n1 == n2); 755 | n1.literal = t3; assert(n1 != n2); 756 | n1.literal = t2; assert(n1 == n2); 757 | n1.kind = Kind.objectStart; assert(n1 != n2); 758 | n1.key = "test"; assert(n1 != n2); 759 | n2.key = "other"; assert(n1 != n2); 760 | n2.key = "test".idup; assert(n1 == n2); 761 | } 762 | 763 | /** 764 | * Enables usage of $(D JSONToken) as an associative array key. 765 | */ 766 | size_t toHash() const nothrow @trusted 767 | { 768 | hash_t ret = 723125331 + cast(int)_kind * 3962627; 769 | 770 | switch (_kind) 771 | { 772 | default: return ret; 773 | case Kind.literal: return ret + _literal.toHash(); 774 | case Kind.key: return ret + typeid(.string).getHash(&_key); 775 | } 776 | } 777 | 778 | /** 779 | * Converts the node to a string representation. 780 | * 781 | * Note that this representation is NOT the JSON representation, but rather 782 | * a representation suitable for printing out a node. 783 | */ 784 | string toString() const 785 | { 786 | import std.string; 787 | switch (this.kind) 788 | { 789 | default: return format("%s", this.kind); 790 | case Kind.key: return format("[key \"%s\"]", this.key); 791 | case Kind.literal: return literal.toString(); 792 | } 793 | } 794 | } 795 | 796 | 797 | /** 798 | * Identifies the kind of a parser node. 799 | */ 800 | enum JSONParserNodeKind 801 | { 802 | none, /// Used internally, never occurs in a node stream 803 | key, /// An object key 804 | literal, /// A literal value ($(D null), $(D boolean), $(D number) or $(D string)) 805 | objectStart, /// The start of an object value 806 | objectEnd, /// The end of an object value 807 | arrayStart, /// The start of an array value 808 | arrayEnd, /// The end of an array value 809 | } 810 | 811 | 812 | /// Tests if a given type is an input range of $(D JSONToken). 813 | enum isJSONTokenInputRange(R) = isInputRange!R && is(typeof(R.init.front) : JSONToken!String, String); 814 | 815 | static assert(isJSONTokenInputRange!(JSONLexerRange!string)); 816 | 817 | /// Tests if a given type is an input range of $(D JSONParserNode). 818 | enum isJSONParserNodeInputRange(R) = isInputRange!R && is(typeof(R.init.front) : JSONParserNode!String, String); 819 | 820 | static assert(isJSONParserNodeInputRange!(JSONParserRange!(JSONLexerRange!string))); 821 | 822 | // Workaround for https://issues.dlang.org/show_bug.cgi?id=14425 823 | private alias Workaround_14425 = JSONParserRange!(JSONLexerRange!string); 824 | 825 | 826 | /** 827 | * Skips a single JSON value in a parser stream. 828 | * 829 | * The value pointed to by `nodes.front` will be skipped. All JSON types will 830 | * be skipped, which means in particular that arrays and objects will be 831 | * skipped recursively. 832 | * 833 | * Params: 834 | * nodes = An input range of JSON parser nodes 835 | */ 836 | void skipValue(R)(ref R nodes) if (isJSONParserNodeInputRange!R) 837 | { 838 | import stdx.data.json.foundation; 839 | enforceJson(!nodes.empty, "Unexpected end of input", nodes.front.literal.location); 840 | 841 | auto k = nodes.front.kind; 842 | nodes.popFront(); 843 | 844 | with (JSONParserNodeKind) { 845 | if (k != arrayStart && k != objectStart) return; 846 | 847 | int depth = 1; 848 | while (!nodes.empty) { 849 | k = nodes.front.kind; 850 | nodes.popFront(); 851 | if (k == arrayStart || k == objectStart) depth++; 852 | else if (k == arrayEnd || k == objectEnd) { 853 | if (--depth == 0) break; 854 | } 855 | } 856 | } 857 | } 858 | 859 | /// 860 | @safe unittest 861 | { 862 | auto j = parseJSONStream(q{ 863 | [ 864 | [1, 2, 3], 865 | "foo" 866 | ] 867 | }); 868 | 869 | assert(j.front.kind == JSONParserNodeKind.arrayStart); 870 | j.popFront(); 871 | 872 | // skips the whole [1, 2, 3] array 873 | j.skipValue(); 874 | 875 | string value = j.readString; 876 | assert(value == "foo"); 877 | 878 | assert(j.front.kind == JSONParserNodeKind.arrayEnd); 879 | j.popFront(); 880 | 881 | assert(j.empty); 882 | } 883 | 884 | 885 | /** 886 | * Skips all entries in an object until a certain key is reached. 887 | * 888 | * The node range must either point to the start of an object 889 | * (`JSONParserNodeKind.objectStart`), or to a key within an object 890 | * (`JSONParserNodeKind.key`). 891 | * 892 | * Params: 893 | * nodes = An input range of JSON parser nodes 894 | * key = Name of the key to find 895 | * 896 | * Returns: 897 | * `true` is returned if and only if the specified key has been found. 898 | * 899 | * Params: 900 | * nodes = An input range of JSON parser nodes 901 | */ 902 | bool skipToKey(R)(ref R nodes, string key) if (isJSONParserNodeInputRange!R) 903 | { 904 | import std.algorithm/*.comparison*/ : among; 905 | import stdx.data.json.foundation; 906 | 907 | enforceJson(!nodes.empty, "Unexpected end of input", Location.init); 908 | enforceJson(nodes.front.kind.among!(JSONParserNodeKind.objectStart, JSONParserNodeKind.key) > 0, 909 | "Expected object or object key", nodes.front.location); 910 | 911 | if (nodes.front.kind == JSONParserNodeKind.objectStart) 912 | nodes.popFront(); 913 | 914 | while (true) { 915 | auto k = nodes.front.kind; 916 | if (k == JSONParserNodeKind.objectEnd) { 917 | nodes.popFront(); 918 | return false; 919 | } 920 | 921 | assert(k == JSONParserNodeKind.key); 922 | if (nodes.front.key == key) { 923 | nodes.popFront(); 924 | return true; 925 | } 926 | 927 | nodes.popFront(); 928 | 929 | nodes.skipValue(); 930 | } 931 | } 932 | 933 | /// 934 | @safe unittest 935 | { 936 | auto j = parseJSONStream(q{ 937 | { 938 | "foo": 2, 939 | "bar": 3, 940 | "baz": false, 941 | "qux": "str" 942 | } 943 | }); 944 | 945 | j.skipToKey("bar"); 946 | double v1 = j.readDouble; 947 | assert(v1 == 3); 948 | 949 | j.skipToKey("qux"); 950 | string v2 = j.readString; 951 | assert(v2 == "str"); 952 | 953 | assert(j.front.kind == JSONParserNodeKind.objectEnd); 954 | j.popFront(); 955 | 956 | assert(j.empty); 957 | } 958 | 959 | 960 | /** 961 | * Reads an array and issues a callback for each entry. 962 | * 963 | * Params: 964 | * nodes = An input range of JSON parser nodes 965 | * del = The callback to invoke for each array entry 966 | */ 967 | void readArray(R)(ref R nodes, scope void delegate() @safe del) if (isJSONParserNodeInputRange!R) 968 | { 969 | import stdx.data.json.foundation; 970 | enforceJson(!nodes.empty, "Unexpected end of input", Location.init); 971 | enforceJson(nodes.front.kind == JSONParserNodeKind.arrayStart, 972 | "Expected array", nodes.front.location); 973 | nodes.popFront(); 974 | 975 | while (true) { 976 | auto k = nodes.front.kind; 977 | if (k == JSONParserNodeKind.arrayEnd) { 978 | nodes.popFront(); 979 | return; 980 | } 981 | del(); 982 | } 983 | } 984 | 985 | /// 986 | @safe unittest 987 | { 988 | auto j = parseJSONStream(q{ 989 | [ 990 | "foo", 991 | "bar" 992 | ] 993 | }); 994 | 995 | size_t i = 0; 996 | j.readArray({ 997 | auto value = j.readString(); 998 | switch (i++) { 999 | default: assert(false); 1000 | case 0: assert(value == "foo"); break; 1001 | case 1: assert(value == "bar"); break; 1002 | } 1003 | }); 1004 | 1005 | assert(j.empty); 1006 | } 1007 | 1008 | /** Reads an array and returns a lazy range of parser node ranges. 1009 | * 1010 | * The given parser node range must point to a node of kind 1011 | * `JSONParserNodeKind.arrayStart`. Each of the returned sub ranges 1012 | * corresponds to the contents of a single array entry. 1013 | * 1014 | * Params: 1015 | * nodes = An input range of JSON parser nodes 1016 | * 1017 | * Throws: 1018 | * A `JSONException` is thrown if the input range does not point to the 1019 | * start of an array. 1020 | */ 1021 | auto readArray(R)(ref R nodes) @system if (isJSONParserNodeInputRange!R) 1022 | { 1023 | static struct VR { 1024 | R* nodes; 1025 | size_t depth = 0; 1026 | 1027 | @disable this(this); 1028 | 1029 | @property bool empty() { return !nodes || nodes.empty; } 1030 | 1031 | @property ref const(typeof(nodes.front)) front() { return nodes.front; } 1032 | 1033 | void popFront() 1034 | { 1035 | switch (nodes.front.kind) with (JSONParserNodeKind) 1036 | { 1037 | default: break; 1038 | case objectStart, arrayStart: depth++; break; 1039 | case objectEnd, arrayEnd: depth--; break; 1040 | } 1041 | 1042 | nodes.popFront(); 1043 | 1044 | if (depth == 0) nodes = null; 1045 | } 1046 | } 1047 | 1048 | static struct ARR { 1049 | R* nodes; 1050 | VR value; 1051 | 1052 | @property bool empty() { return !nodes || nodes.empty; } 1053 | 1054 | @property ref inout(VR) front() inout { return value; } 1055 | 1056 | void popFront() 1057 | { 1058 | while (!value.empty) value.popFront(); 1059 | if (nodes.front.kind == JSONParserNodeKind.arrayEnd) { 1060 | nodes.popFront(); 1061 | nodes = null; 1062 | } else { 1063 | value = VR(nodes); 1064 | } 1065 | } 1066 | } 1067 | 1068 | import stdx.data.json.foundation; 1069 | 1070 | enforceJson(!nodes.empty, "Unexpected end of input", Location.init); 1071 | enforceJson(nodes.front.kind == JSONParserNodeKind.arrayStart, 1072 | "Expected array", nodes.front.location); 1073 | nodes.popFront(); 1074 | 1075 | ARR ret; 1076 | 1077 | if (nodes.front.kind != JSONParserNodeKind.arrayEnd) { 1078 | ret.nodes = &nodes; 1079 | ret.value = VR(&nodes); 1080 | } else nodes.popFront(); 1081 | 1082 | return ret; 1083 | } 1084 | 1085 | /// 1086 | unittest { 1087 | auto j = parseJSONStream(q{ 1088 | [ 1089 | "foo", 1090 | "bar" 1091 | ] 1092 | }); 1093 | 1094 | size_t i = 0; 1095 | foreach (ref entry; j.readArray) { 1096 | auto value = entry.readString; 1097 | assert(entry.empty); 1098 | switch (i++) { 1099 | default: assert(false); 1100 | case 0: assert(value == "foo"); break; 1101 | case 1: assert(value == "bar"); break; 1102 | } 1103 | } 1104 | assert(i == 2); 1105 | } 1106 | 1107 | 1108 | /** 1109 | * Reads an object and issues a callback for each field. 1110 | * 1111 | * Params: 1112 | * nodes = An input range of JSON parser nodes 1113 | * del = The callback to invoke for each object field 1114 | */ 1115 | void readObject(R)(ref R nodes, scope void delegate(string key) @safe del) if (isJSONParserNodeInputRange!R) 1116 | { 1117 | import stdx.data.json.foundation; 1118 | enforceJson(!nodes.empty, "Unexpected end of input", Location.init); 1119 | enforceJson(nodes.front.kind == JSONParserNodeKind.objectStart, 1120 | "Expected object", nodes.front.literal.location); 1121 | nodes.popFront(); 1122 | 1123 | while (true) { 1124 | auto k = nodes.front.kind; 1125 | if (k == JSONParserNodeKind.objectEnd) { 1126 | nodes.popFront(); 1127 | return; 1128 | } 1129 | auto key = nodes.front.key; 1130 | nodes.popFront(); 1131 | del(key); 1132 | } 1133 | } 1134 | 1135 | /// 1136 | @safe unittest 1137 | { 1138 | auto j = parseJSONStream(q{ 1139 | { 1140 | "foo": 1, 1141 | "bar": 2 1142 | } 1143 | }); 1144 | 1145 | j.readObject((key) { 1146 | auto value = j.readDouble; 1147 | switch (key) { 1148 | default: assert(false); 1149 | case "foo": assert(value == 1); break; 1150 | case "bar": assert(value == 2); break; 1151 | } 1152 | }); 1153 | 1154 | assert(j.empty); 1155 | } 1156 | 1157 | 1158 | /** 1159 | * Reads a single double value. 1160 | * 1161 | * Params: 1162 | * nodes = An input range of JSON parser nodes 1163 | * 1164 | * Throws: Throws a `JSONException` is the node range is empty or `nodes.front` is not a number. 1165 | */ 1166 | double readDouble(R)(ref R nodes) if (isJSONParserNodeInputRange!R) 1167 | { 1168 | import stdx.data.json.foundation; 1169 | enforceJson(!nodes.empty, "Unexpected end of input", Location.init); 1170 | enforceJson(nodes.front.kind == JSONParserNodeKind.literal 1171 | && nodes.front.literal.kind == JSONTokenKind.number, 1172 | "Expected numeric value", nodes.front.literal.location); 1173 | double ret = nodes.front.literal.number; 1174 | nodes.popFront(); 1175 | return ret; 1176 | } 1177 | 1178 | /// 1179 | @safe unittest 1180 | { 1181 | auto j = parseJSONStream(`1.0`); 1182 | double value = j.readDouble; 1183 | assert(value == 1.0); 1184 | assert(j.empty); 1185 | } 1186 | 1187 | 1188 | /** 1189 | * Reads a single double value. 1190 | * 1191 | * Params: 1192 | * nodes = An input range of JSON parser nodes 1193 | * 1194 | * Throws: Throws a `JSONException` is the node range is empty or `nodes.front` is not a string. 1195 | */ 1196 | string readString(R)(ref R nodes) if (isJSONParserNodeInputRange!R) 1197 | { 1198 | import stdx.data.json.foundation; 1199 | enforceJson(!nodes.empty, "Unexpected end of input", Location.init); 1200 | enforceJson(nodes.front.kind == JSONParserNodeKind.literal 1201 | && nodes.front.literal.kind == JSONTokenKind.string, 1202 | "Expected string value", nodes.front.literal.location); 1203 | string ret = nodes.front.literal.string; 1204 | nodes.popFront(); 1205 | return ret; 1206 | } 1207 | 1208 | /// 1209 | @safe unittest 1210 | { 1211 | auto j = parseJSONStream(`"foo"`); 1212 | string value = j.readString; 1213 | assert(value == "foo"); 1214 | assert(j.empty); 1215 | } 1216 | 1217 | 1218 | /** 1219 | * Reads a single double value. 1220 | * 1221 | * Params: 1222 | * nodes = An input range of JSON parser nodes 1223 | * 1224 | * Throws: Throws a `JSONException` is the node range is empty or `nodes.front` is not a boolean. 1225 | */ 1226 | bool readBool(R)(ref R nodes) if (isJSONParserNodeInputRange!R) 1227 | { 1228 | import stdx.data.json.foundation; 1229 | enforceJson(!nodes.empty, "Unexpected end of input", Location.init); 1230 | enforceJson(nodes.front.kind == JSONParserNodeKind.literal 1231 | && nodes.front.literal.kind == JSONTokenKind.boolean, 1232 | "Expected boolean value", nodes.front.literal.location); 1233 | bool ret = nodes.front.literal.boolean; 1234 | nodes.popFront(); 1235 | return ret; 1236 | } 1237 | 1238 | /// 1239 | @safe unittest 1240 | { 1241 | auto j = parseJSONStream(`true`); 1242 | bool value = j.readBool; 1243 | assert(value == true); 1244 | assert(j.empty); 1245 | } 1246 | -------------------------------------------------------------------------------- /source/stdx/data/json/package.d: -------------------------------------------------------------------------------- 1 | /** 2 | * Package import for the whole std.data.json package. 3 | * 4 | * Synopsis: 5 | * --- 6 | * // Parse a JSON string 7 | * JSONValue value = toJSONValue(`{"name": "D", "kind": "language"}`); 8 | * assert(value["name"] == "D"); 9 | * assert(value["kind"] == "language"); 10 | * 11 | * // Convert a value to a formatted JSON string 12 | * assert(value.toJSON() == 13 | * `{ 14 | * "name": "D", 15 | * "kind": "language" 16 | * }`); 17 | * 18 | * // Convert a value back to a JSON string 19 | * assert(value.toJSON(GeneratorOptions.compact) == `{"name":"D","kind":"language"}`); 20 | * 21 | * // Lex a JSON string into a lazy range of tokens 22 | * auto tokens = lexJSON(`{"name": "D", "kind": "language"}`); 23 | * with (JSONToken.Kind) { 24 | * assert(tokens.map!(t => t.kind).equal( 25 | * [objectStart, string, colon, string, comma, 26 | * string, colon, string, objectEnd])); 27 | * } 28 | * 29 | * // Parse the tokens to a value 30 | * JSONValue value2 = toJSONValue(tokens); 31 | * assert(value2 == value); 32 | * 33 | * // Parse the tokens to a JSON node stream 34 | * auto nodes = parseJSONStream(tokens); 35 | * with (JSONParserNode.Kind) { 36 | * assert(nodes.map!(n => n.kind).equal( 37 | * [objectStart, key, literal, key, literal, objectEnd])); 38 | * } 39 | * --- 40 | * 41 | * Copyright: Copyright 2012 - 2015, Sönke Ludwig. 42 | * License: $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0). 43 | * Authors: Sönke Ludwig 44 | * Source: $(PHOBOSSRC std/data/json/package.d) 45 | */ 46 | module stdx.data.json; 47 | 48 | public import stdx.data.json.foundation; 49 | public import stdx.data.json.generator; 50 | public import stdx.data.json.lexer; 51 | public import stdx.data.json.parser; 52 | public import stdx.data.json.value; 53 | 54 | 55 | version (unittest) 56 | { 57 | private immutable testString1 =`{ 58 | "cols": [ 59 | "name", 60 | "num", 61 | "email", 62 | "text" 63 | ], 64 | "data": [ 65 | [ 66 | "Patrick Kirby", 67 | "-3.4403043480471", 68 | "luctus.sit.amet@enim.edu", 69 | "mauris a nunc. In at pede. Cras vulputate velit eu sem. Pellentesque ut ipsum ac mi eleifend egestas. Sed pharetra, felis eget varius ultrices, mauris ipsum porta elit, a feugiat tellus lorem eu metus. In lorem. Donec elementum, lorem ut aliquam iaculis, lacus pede sagittis augue, eu tempor erat neque non quam. Pellentesque habitant" 70 | ], 71 | [ 72 | "Abdul Weaver", 73 | "1.1076661798338", 74 | "lorem.eget@placeratCras.com", 75 | "odio tristique pharetra. Quisque ac libero nec ligula consectetuer rhoncus. Nullam velit dui, semper et, lacinia vitae, sodales at, velit. Pellentesque ultricies dignissim lacus. Aliquam rutrum lorem ac risus. Morbi metus. Vivamus euismod urna. Nullam lobortis quam a felis ullamcorper viverra. Maecenas iaculis aliquet diam. Sed diam lorem, auctor quis, tristique ac, eleifend vitae, erat. Vivamus nisi. Mauris nulla. Integer urna. Vivamus molestie dapibus ligula. Aliquam erat volutpat. Nulla dignissim. Maecenas ornare egestas ligula. Nullam feugiat placerat velit. Quisque varius. Nam porttitor scelerisque neque. Nullam nisl. Maecenas malesuada fringilla" 76 | ], 77 | [ 78 | "Reese Calderon", 79 | "2.9110321408694", 80 | "Nam.ligula@risus.org", 81 | "magnis dis parturient montes, nascetur ridiculus mus. Donec dignissim magna a tortor. Nunc commodo" 82 | ], 83 | [ 84 | "Philip Stanley", 85 | "1.5177049910759", 86 | "adipiscing.fringilla.porttitor@vel.net", 87 | "dui, in sodales elit erat vitae risus. Duis a mi fringilla mi lacinia mattis. Integer eu lacus. Quisque imperdiet, erat nonummy ultricies ornare, elit elit fermentum risus, at fringilla" 88 | ], 89 | [ 90 | "Blaze Hester", 91 | "-1.5274821664568", 92 | "est.tempor@turpisAliquamadipiscing.org", 93 | "egestas. Aliquam nec enim. Nunc ut erat. Sed nunc est, mollis non, cursus non, egestas a, dui. Cras pellentesque. Sed dictum. Proin eget odio. Aliquam vulputate ullamcorper magna. Sed eu eros. Nam" 94 | ], 95 | [ 96 | "Kyle Hammond", 97 | "0.2603162084147", 98 | "ridiculus.mus.Proin@at.net", 99 | "elementum, dui quis accumsan convallis, ante lectus convallis est, vitae sodales nisi magna sed dui. Fusce aliquam, enim nec tempus scelerisque, lorem ipsum sodales purus, in molestie tortor nibh sit amet orci. Ut sagittis lobortis mauris. Suspendisse aliquet molestie tellus." 100 | ], 101 | [ 102 | "Brennan Petty", 103 | "-3.4768128125142", 104 | "Sed.et.libero@consectetuer.org", 105 | "libero nec ligula consectetuer rhoncus. Nullam velit dui, semper et, lacinia vitae, sodales at, velit. Pellentesque ultricies dignissim lacus. Aliquam rutrum lorem ac risus. Morbi metus. Vivamus euismod urna. Nullam lobortis quam a felis ullamcorper viverra. Maecenas iaculis aliquet diam. Sed diam lorem, auctor quis, tristique ac, eleifend vitae, erat. Vivamus nisi. Mauris nulla. Integer urna. Vivamus molestie dapibus ligula. Aliquam erat volutpat." 106 | ], 107 | [ 108 | "Amal Stevenson", 109 | "0.85198412868279", 110 | "sed.hendrerit@nunc.com", 111 | "congue a, aliquet vel, vulputate eu, odio. Phasellus at augue id ante dictum cursus. Nunc mauris elit, dictum eu, eleifend nec, malesuada ut, sem. Nulla interdum. Curabitur dictum. Phasellus in felis. Nulla tempor augue ac ipsum. Phasellus vitae mauris sit amet lorem" 112 | ], 113 | [ 114 | "Kibo Levy", 115 | "-1.4784283166374", 116 | "convallis@sedpede.com", 117 | "ultrices. Duis volutpat nunc sit amet metus. Aliquam erat volutpat. Nulla facilisis. Suspendisse commodo tincidunt nibh. Phasellus nulla. Integer vulputate, risus a ultricies adipiscing, enim mi tempor lorem, eget mollis lectus pede et risus. Quisque libero lacus, varius et, euismod et, commodo at, libero. Morbi accumsan laoreet ipsum. Curabitur consequat, lectus sit amet luctus vulputate, nisi sem semper erat, in consectetuer ipsum nunc id enim. Curabitur massa. Vestibulum accumsan neque et nunc. Quisque ornare tortor at risus." 118 | ], 119 | [ 120 | "Giacomo Livingston", 121 | "0.54232248361117", 122 | "Nullam.vitae.diam@interdumCurabitur.net", 123 | "egestas. Aliquam nec enim. Nunc ut erat. Sed nunc est, mollis non, cursus non, egestas a, dui. Cras pellentesque. Sed dictum. Proin eget odio. Aliquam vulputate ullamcorper magna. Sed eu eros. Nam consequat dolor vitae dolor. Donec fringilla. Donec feugiat metus sit amet ante. Vivamus non lorem vitae odio sagittis semper. Nam tempor diam dictum sapien. Aenean massa. Integer vitae nibh. Donec est mauris, rhoncus id, mollis nec, cursus a, enim. Suspendisse aliquet, sem ut cursus luctus, ipsum leo elementum sem, vitae aliquam eros turpis non enim. Mauris quis turpis vitae purus gravida" 124 | ], 125 | [ 126 | "Ian Gray", 127 | "-0.18460451694887", 128 | "lacus.Quisque.purus@tincidunt.edu", 129 | "interdum ligula eu enim. Etiam imperdiet dictum magna. Ut tincidunt orci quis lectus. Nullam suscipit, est ac facilisis facilisis, magna tellus faucibus leo, in lobortis tellus justo sit amet nulla. Donec non justo. Proin non massa non ante bibendum ullamcorper. Duis cursus, diam at pretium aliquet, metus urna convallis erat, eget tincidunt dui augue eu tellus. Phasellus elit pede, malesuada vel, venenatis vel, faucibus id, libero. Donec consectetuer mauris id sapien. Cras dolor dolor, tempus non, lacinia at, iaculis quis, pede. Praesent eu dui. Cum sociis natoque penatibus et magnis dis" 130 | ], 131 | [ 132 | "Salvador Weaver", 133 | "-1.0306127882055", 134 | "eu.neque.pellentesque@pulvinar.org", 135 | "erat eget ipsum. Suspendisse sagittis. Nullam vitae diam. Proin dolor. Nulla semper tellus id nunc interdum feugiat. Sed nec metus facilisis lorem tristique aliquet. Phasellus fermentum convallis ligula. Donec luctus aliquet odio. Etiam ligula tortor, dictum eu, placerat eget, venenatis a, magna. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Etiam laoreet, libero et tristique pellentesque, tellus sem mollis dui, in" 136 | ], 137 | [ 138 | "Carson Arnold", 139 | "2.6606923019535", 140 | "Cum.sociis@sitametluctus.com", 141 | "scelerisque, lorem ipsum sodales purus, in molestie tortor nibh sit amet orci. Ut sagittis lobortis mauris. Suspendisse aliquet molestie tellus. Aenean egestas hendrerit neque. In ornare sagittis felis. Donec tempor, est ac mattis semper, dui lectus rutrum urna, nec luctus felis purus ac tellus. Suspendisse sed dolor. Fusce mi lorem, vehicula et, rutrum eu, ultrices sit amet, risus. Donec nibh enim, gravida sit amet, dapibus id, blandit at, nisi. Cum" 142 | ], 143 | [ 144 | "Kyle Harvey", 145 | "-3.988850143986", 146 | "Fusce.diam.nunc@sit.org", 147 | "ultricies ligula. Nullam" 148 | ], 149 | [ 150 | "Todd Gibbs", 151 | "0.58497373466655", 152 | "mattis.semper@maurisanunc.com", 153 | "erat. Vivamus nisi. Mauris nulla. Integer urna. Vivamus molestie" 154 | ], 155 | [ 156 | "Fitzgerald Norris", 157 | "0.49883579938617", 158 | "cursus@Inornare.com", 159 | "sed pede. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Proin vel arcu eu odio tristique pharetra. Quisque ac libero nec ligula consectetuer rhoncus. Nullam velit dui, semper et, lacinia vitae, sodales at, velit. Pellentesque ultricies dignissim lacus. Aliquam rutrum lorem ac risus. Morbi metus. Vivamus euismod urna. Nullam lobortis quam a felis ullamcorper viverra. Maecenas iaculis aliquet diam. Sed diam lorem, auctor quis, tristique ac, eleifend vitae, erat. Vivamus nisi. Mauris nulla. Integer urna. Vivamus" 160 | ], 161 | [ 162 | "Amery Hodge", 163 | "-1.7000203147919", 164 | "Ut@enim.net", 165 | "mauris ipsum porta elit, a feugiat tellus lorem eu metus. In lorem. Donec elementum, lorem ut aliquam iaculis, lacus pede sagittis augue, eu tempor erat neque non quam. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Aliquam fringilla cursus purus. Nullam scelerisque neque sed sem egestas blandit. Nam nulla magna, malesuada vel, convallis in," 166 | ], 167 | [ 168 | "Todd Kidd", 169 | "-1.6975579005113", 170 | "orci@rhoncus.net", 171 | "Proin non massa non ante bibendum ullamcorper. Duis cursus, diam at pretium aliquet, metus urna convallis erat, eget tincidunt dui augue eu tellus. Phasellus elit pede, malesuada vel, venenatis vel, faucibus id, libero. Donec consectetuer mauris id sapien. Cras dolor dolor, tempus non, lacinia at, iaculis quis, pede. Praesent eu dui. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Aenean eget magna. Suspendisse tristique neque venenatis lacus. Etiam bibendum fermentum metus. Aenean sed pede nec ante blandit viverra. Donec tempus, lorem fringilla ornare placerat, orci lacus vestibulum lorem, sit amet" 172 | ], 173 | [ 174 | "Xenos Hopper", 175 | "-2.4634221009688", 176 | "auctor.ullamcorper.nisl@sollicitudin.com", 177 | "ultrices sit amet, risus. Donec nibh enim, gravida sit amet, dapibus id, blandit at, nisi. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Proin vel nisl. Quisque fringilla euismod enim. Etiam gravida molestie arcu. Sed eu nibh vulputate mauris sagittis placerat. Cras dictum ultricies ligula. Nullam enim. Sed nulla ante, iaculis nec, eleifend non, dapibus rutrum, justo. Praesent luctus. Curabitur egestas nunc sed libero. Proin" 178 | ], 179 | [ 180 | "Hiram Larson", 181 | "-2.3861602122822", 182 | "sit.amet@purus.co.uk", 183 | "non dui nec urna suscipit nonummy. Fusce fermentum fermentum arcu. Vestibulum ante ipsum primis in faucibus orci luctus et" 184 | ], 185 | [ 186 | "Timon Grimes", 187 | "-1.6317122102384", 188 | "at.risus.Nunc@vitae.org", 189 | "turpis nec mauris blandit mattis. Cras eget nisi dictum augue malesuada malesuada. Integer id magna et ipsum cursus vestibulum. Mauris magna. Duis dignissim tempor arcu. Vestibulum ut eros non enim commodo hendrerit. Donec porttitor tellus non magna. Nam ligula elit, pretium et, rutrum non, hendrerit id, ante. Nunc mauris sapien, cursus in, hendrerit consectetuer, cursus et, magna. Praesent interdum ligula eu enim. Etiam imperdiet dictum magna. Ut tincidunt orci quis lectus. Nullam suscipit, est ac facilisis facilisis, magna tellus faucibus leo, in lobortis tellus justo sit" 190 | ], 191 | [ 192 | "Palmer Bell", 193 | "0.38641549228268", 194 | "interdum.enim.non@semperegestas.net", 195 | "ultricies ornare, elit elit fermentum risus, at fringilla purus mauris a nunc. In at pede. Cras vulputate velit eu sem. Pellentesque ut ipsum ac mi eleifend egestas. Sed pharetra, felis eget varius ultrices, mauris ipsum porta elit, a feugiat tellus lorem eu metus. In lorem. Donec elementum, lorem ut aliquam iaculis, lacus pede sagittis augue, eu tempor erat neque non quam. Pellentesque habitant morbi tristique senectus et netus et malesuada" 196 | ], 197 | [ 198 | "Keith Hubbard", 199 | "1.1787214399064", 200 | "posuere.at@sitametrisus.edu", 201 | "Praesent eu dui. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Aenean eget magna. Suspendisse tristique neque venenatis lacus. Etiam bibendum fermentum metus. Aenean sed pede nec ante blandit viverra. Donec tempus, lorem fringilla ornare placerat, orci lacus vestibulum lorem, sit amet ultricies sem" 202 | ], 203 | [ 204 | "Lars Green", 205 | "-1.3692547338355", 206 | "diam.Proin@magnisdisparturient.org", 207 | "rhoncus. Donec est. Nunc ullamcorper, velit" 208 | ], 209 | [ 210 | "Jonas Huff", 211 | "-0.92361474641041", 212 | "ipsum.Curabitur@Morbinonsapien.org", 213 | "consectetuer adipiscing elit. Etiam laoreet, libero et tristique pellentesque, tellus sem mollis dui, in sodales elit erat vitae risus. Duis a mi fringilla mi lacinia mattis. Integer eu lacus. Quisque imperdiet, erat nonummy ultricies ornare, elit elit fermentum risus, at fringilla purus mauris a nunc. In at pede. Cras vulputate velit eu sem. Pellentesque ut ipsum ac mi eleifend egestas. Sed pharetra, felis eget varius ultrices, mauris ipsum porta elit, a feugiat" 214 | ], 215 | [ 216 | "Cullen Gaines", 217 | "0.14272912346868", 218 | "sociis.natoque.penatibus@dolor.ca", 219 | "est tempor bibendum. Donec felis orci, adipiscing non, luctus sit amet, faucibus ut, nulla. Cras eu tellus eu augue porttitor interdum. Sed auctor odio a purus. Duis elementum, dui quis accumsan convallis, ante lectus convallis est, vitae sodales nisi magna sed dui. Fusce aliquam, enim nec tempus scelerisque, lorem ipsum sodales purus, in molestie tortor nibh sit amet orci. Ut sagittis lobortis mauris. Suspendisse aliquet" 220 | ], 221 | [ 222 | "Tarik Bauer", 223 | "-4.4274628384424", 224 | "nisi.Cum.sociis@dictumeleifendnunc.ca", 225 | "hendrerit. Donec porttitor tellus non magna. Nam ligula elit, pretium et, rutrum non, hendrerit id, ante. Nunc mauris sapien, cursus in, hendrerit consectetuer, cursus et, magna. Praesent interdum ligula eu enim. Etiam imperdiet dictum magna. Ut tincidunt orci quis lectus. Nullam suscipit, est ac facilisis facilisis, magna tellus faucibus leo, in lobortis tellus justo sit amet nulla. Donec non justo. Proin" 226 | ], 227 | [ 228 | "Driscoll Robinson", 229 | "1.6859334536819", 230 | "Etiam.laoreet.libero@etmalesuada.net", 231 | "sit amet, dapibus id, blandit at, nisi. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Proin vel nisl. Quisque fringilla euismod enim. Etiam gravida molestie arcu. Sed eu nibh vulputate mauris sagittis placerat. Cras dictum ultricies ligula. Nullam enim. Sed nulla ante, iaculis nec, eleifend non, dapibus rutrum, justo. Praesent luctus. Curabitur egestas nunc sed libero. Proin sed turpis nec mauris blandit mattis. Cras eget nisi dictum augue malesuada malesuada. Integer id magna et ipsum cursus vestibulum." 232 | ], 233 | [ 234 | "Lucas Decker", 235 | "1.4165992895811", 236 | "sed.dolor@nequesedsem.edu", 237 | "in faucibus orci luctus et ultrices posuere cubilia Curae; Phasellus ornare. Fusce mollis. Duis sit amet diam eu dolor egestas rhoncus. Proin nisl sem, consequat nec, mollis vitae, posuere at, velit. Cras lorem lorem, luctus ut, pellentesque eget, dictum placerat, augue. Sed molestie. Sed id risus" 238 | ], 239 | [ 240 | "Duncan Evans", 241 | "2.7341570955724", 242 | "fames.ac.turpis@Morbiaccumsan.ca", 243 | "Curabitur vel lectus. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec dignissim magna a tortor. Nunc commodo auctor velit. Aliquam nisl. Nulla eu neque pellentesque massa lobortis ultrices. Vivamus rhoncus. Donec est. Nunc ullamcorper, velit in aliquet lobortis, nisi nibh" 244 | ], 245 | [ 246 | "Porter Moody", 247 | "-1.4324173162312", 248 | "non.hendrerit@litoratorquent.edu", 249 | "at arcu. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Donec tincidunt. Donec vitae erat vel" 250 | ], 251 | [ 252 | "Wade Preston", 253 | "0.32972666337599", 254 | "libero@molestieorcitincidunt.com", 255 | "nibh. Quisque nonummy ipsum non arcu. Vivamus sit amet risus. Donec egestas. Aliquam nec enim. Nunc ut erat. Sed nunc est, mollis non, cursus non, egestas a, dui. Cras pellentesque. Sed dictum. Proin eget odio. Aliquam vulputate ullamcorper magna. Sed eu eros. Nam consequat dolor vitae dolor. Donec fringilla. Donec feugiat metus sit amet ante. Vivamus non lorem vitae odio sagittis semper. Nam tempor diam" 256 | ], 257 | [ 258 | "Myles Chase", 259 | "-1.6442499964583", 260 | "mollis.non.cursus@tristiquesenectuset.ca", 261 | "molestie dapibus ligula. Aliquam erat volutpat. Nulla dignissim. Maecenas ornare egestas ligula. Nullam feugiat placerat velit. Quisque varius. Nam porttitor scelerisque neque. Nullam nisl. Maecenas malesuada fringilla est. Mauris eu turpis. Nulla aliquet. Proin velit. Sed malesuada augue ut lacus. Nulla tincidunt, neque vitae semper egestas, urna justo faucibus lectus, a sollicitudin orci sem eget massa. Suspendisse eleifend. Cras sed leo. Cras vehicula aliquet libero. Integer in magna. Phasellus dolor elit, pellentesque" 262 | ], 263 | [ 264 | "Mark Fitzgerald", 265 | "3.3596166320255", 266 | "sollicitudin@Donecfeugiatmetus.com", 267 | "natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Proin vel nisl. Quisque fringilla euismod enim. Etiam gravida molestie arcu." 268 | ], 269 | [ 270 | "Zachery Walsh", 271 | "3.5871951530794", 272 | "sit.amet.massa@telluseu.co.uk", 273 | "Sed et libero. Proin mi. Aliquam gravida mauris ut mi. Duis risus odio, auctor" 274 | ], 275 | [ 276 | "Tyrone Hogan", 277 | "0.65198505567554", 278 | "felis.eget.varius@velvulputateeu.edu", 279 | "eu" 280 | ], 281 | [ 282 | "Dante Gordon", 283 | "2.4654363408172", 284 | "sodales.Mauris.blandit@tellus.org", 285 | "vulputate dui, nec tempus mauris erat eget ipsum. Suspendisse sagittis. Nullam vitae diam. Proin dolor. Nulla semper tellus id nunc interdum feugiat. Sed nec metus facilisis lorem tristique aliquet. Phasellus fermentum convallis ligula. Donec luctus aliquet odio. Etiam ligula tortor, dictum eu, placerat eget, venenatis a, magna. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Etiam laoreet, libero et tristique pellentesque, tellus sem mollis dui, in sodales elit erat vitae risus. Duis a mi fringilla mi lacinia" 286 | ], 287 | [ 288 | "Aladdin Foster", 289 | "2.9542768691297", 290 | "ut.erat@Phasellusataugue.net", 291 | "Vivamus sit amet risus. Donec egestas. Aliquam nec enim. Nunc ut erat. Sed nunc est, mollis non, cursus non, egestas a, dui. Cras pellentesque. Sed dictum. Proin eget odio. Aliquam vulputate ullamcorper magna. Sed eu eros. Nam consequat dolor vitae dolor. Donec fringilla. Donec feugiat metus sit amet ante. Vivamus non lorem vitae odio sagittis semper. Nam tempor diam dictum sapien. Aenean massa. Integer vitae nibh. Donec est mauris, rhoncus id, mollis nec, cursus a, enim. Suspendisse aliquet, sem" 292 | ], 293 | [ 294 | "Silas Erickson", 295 | "0.17533130381752", 296 | "non.leo.Vivamus@ipsumnonarcu.edu", 297 | "magna. Praesent interdum ligula eu enim. Etiam imperdiet dictum magna. Ut tincidunt orci quis lectus. Nullam suscipit, est ac facilisis facilisis, magna tellus faucibus leo, in lobortis tellus justo sit amet nulla. Donec non justo. Proin non massa non ante bibendum ullamcorper. Duis cursus, diam at pretium aliquet, metus urna convallis erat, eget tincidunt dui augue eu tellus. Phasellus elit pede, malesuada vel, venenatis vel, faucibus id, libero. Donec consectetuer mauris" 298 | ], 299 | [ 300 | "Avram Walls", 301 | "2.4721166900916", 302 | "orci.tincidunt@tortorat.com", 303 | "nunc id enim. Curabitur massa. Vestibulum accumsan neque et nunc. Quisque ornare tortor at risus. Nunc ac sem ut dolor dapibus gravida. Aliquam tincidunt, nunc ac mattis ornare, lectus ante dictum mi, ac mattis velit justo nec ante. Maecenas mi felis, adipiscing fringilla, porttitor vulputate, posuere vulputate, lacus. Cras interdum. Nunc sollicitudin" 304 | ], 305 | [ 306 | "Porter Walter", 307 | "3.7058417984546", 308 | "placerat@quis.ca", 309 | "nulla. Donec non justo. Proin non massa non ante bibendum ullamcorper. Duis cursus, diam at pretium aliquet, metus urna convallis erat, eget tincidunt dui augue eu tellus. Phasellus elit pede, malesuada vel, venenatis vel, faucibus id, libero. Donec consectetuer mauris id sapien. Cras dolor dolor, tempus non, lacinia at, iaculis quis, pede. Praesent eu dui. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Aenean eget magna. Suspendisse tristique neque venenatis" 310 | ], 311 | [ 312 | "Honorato Glenn", 313 | "0.45774163592354", 314 | "Morbi.accumsan.laoreet@pharetraNamac.net", 315 | "Aliquam ornare, libero at auctor ullamcorper, nisl arcu iaculis enim, sit amet ornare lectus justo eu arcu. Morbi sit amet massa. Quisque porttitor eros nec tellus. Nunc" 316 | ], 317 | [ 318 | "Castor Rocha", 319 | "5.3057451914525", 320 | "eros.turpis@sapienimperdiet.ca", 321 | "Nulla semper tellus id nunc interdum feugiat. Sed nec metus facilisis lorem tristique aliquet. Phasellus fermentum convallis ligula. Donec luctus aliquet odio. Etiam ligula tortor, dictum eu, placerat eget, venenatis a, magna. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Etiam laoreet, libero et" 322 | ], 323 | [ 324 | "Wallace Cantrell", 325 | "1.5780227213515", 326 | "laoreet@Vestibulumanteipsum.edu", 327 | "mus. Donec dignissim magna a tortor. Nunc commodo auctor velit. Aliquam nisl. Nulla eu neque pellentesque massa lobortis ultrices. Vivamus rhoncus. Donec est. Nunc ullamcorper, velit in aliquet lobortis, nisi nibh lacinia orci, consectetuer euismod est arcu ac orci. Ut semper pretium neque. Morbi quis urna. Nunc quis arcu vel quam dignissim pharetra. Nam ac nulla. In tincidunt congue turpis. In condimentum. Donec at arcu. Vestibulum ante ipsum primis in faucibus" 328 | ], 329 | [ 330 | "Alan Lyons", 331 | "-1.3592972807138", 332 | "lorem.sit@Donectempuslorem.net", 333 | "In mi pede, nonummy ut, molestie in, tempus eu, ligula. Aenean euismod mauris eu elit. Nulla facilisi. Sed neque. Sed eget lacus. Mauris non dui nec urna suscipit nonummy. Fusce fermentum fermentum arcu. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Phasellus ornare. Fusce mollis." 334 | ], 335 | [ 336 | "Leroy Boyle", 337 | "4.6394981156692", 338 | "malesuada@diamlorem.org", 339 | "est." 340 | ], 341 | [ 342 | "Micah Guzman", 343 | "2.8703558761468", 344 | "augue@elitNullafacilisi.ca", 345 | "Donec tempus, lorem fringilla ornare placerat, orci lacus vestibulum lorem, sit amet ultricies sem magna nec quam. Curabitur vel lectus. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec dignissim magna a tortor. Nunc commodo auctor velit. Aliquam nisl. Nulla eu neque pellentesque massa lobortis ultrices. Vivamus rhoncus. Donec est. Nunc ullamcorper, velit in aliquet lobortis, nisi nibh lacinia orci, consectetuer euismod est arcu ac orci. Ut semper pretium neque. Morbi" 346 | ], 347 | [ 348 | "Callum Brooks", 349 | "-0.96170658390365", 350 | "eget.massa@inaliquet.edu", 351 | "a feugiat tellus lorem" 352 | ], 353 | [ 354 | "Ryder Weber", 355 | "1.6768444090056", 356 | "odio.Phasellus.at@nonmassa.ca", 357 | "semper cursus. Integer mollis. Integer tincidunt aliquam arcu. Aliquam ultrices iaculis odio. Nam interdum enim non nisi. Aenean eget metus. In nec orci. Donec nibh. Quisque nonummy ipsum non arcu. Vivamus sit amet risus. Donec egestas. Aliquam nec enim. Nunc ut erat. Sed nunc est," 358 | ], 359 | [ 360 | "Ulric Briggs", 361 | "-0.65281544443019", 362 | "arcu@sagittis.org", 363 | "Suspendisse non leo. Vivamus nibh dolor, nonummy ac, feugiat non, lobortis quis, pede. Suspendisse dui. Fusce diam nunc, ullamcorper eu, euismod ac, fermentum vel, mauris. Integer sem elit, pharetra ut, pharetra sed, hendrerit a, arcu. Sed et libero. Proin mi. Aliquam gravida mauris ut mi. Duis risus" 364 | ], 365 | [ 366 | "Zephania Wolfe", 367 | "-1.0663470152788", 368 | "interdum.Nunc@eget.co.uk", 369 | "a ultricies adipiscing, enim mi tempor lorem, eget mollis lectus pede et risus. Quisque libero lacus, varius et, euismod et, commodo at, libero. Morbi accumsan laoreet ipsum. Curabitur consequat, lectus sit amet luctus vulputate, nisi sem" 370 | ], 371 | [ 372 | "Vernon Collins", 373 | "0.96171776003428", 374 | "arcu.Morbi.sit@quispede.edu", 375 | "vitae odio sagittis semper. Nam tempor diam dictum sapien. Aenean massa. Integer vitae nibh. Donec est mauris, rhoncus id, mollis nec, cursus a, enim. Suspendisse aliquet, sem ut cursus luctus, ipsum leo elementum sem, vitae aliquam eros turpis non enim. Mauris quis turpis vitae purus" 376 | ], 377 | [ 378 | "Slade Potter", 379 | "6.944424067028", 380 | "feugiat.metus@risus.edu", 381 | "arcu. Aliquam ultrices iaculis odio. Nam interdum enim non nisi. Aenean eget metus. In nec orci. Donec nibh. Quisque nonummy ipsum non arcu. Vivamus sit amet risus. Donec egestas. Aliquam nec enim. Nunc ut erat. Sed nunc est, mollis non, cursus non," 382 | ], 383 | [ 384 | "Scott Osborne", 385 | "-0.8219277954245", 386 | "tempor.diam@consectetueradipiscing.org", 387 | "imperdiet, erat nonummy ultricies ornare, elit elit fermentum risus, at fringilla purus mauris a nunc. In at pede. Cras vulputate velit eu sem. Pellentesque ut ipsum ac mi eleifend egestas. Sed pharetra, felis eget varius ultrices, mauris ipsum porta elit, a feugiat tellus lorem eu metus. In lorem. Donec elementum, lorem ut aliquam iaculis, lacus" 388 | ], 389 | [ 390 | "Dale Pena", 391 | "-2.6963522007683", 392 | "nulla.Integer@loremac.edu", 393 | "libero lacus, varius et, euismod et, commodo at, libero. Morbi accumsan laoreet ipsum. Curabitur consequat, lectus sit amet luctus vulputate, nisi sem semper erat, in consectetuer ipsum nunc id enim. Curabitur massa. Vestibulum accumsan neque et nunc. Quisque ornare tortor at risus. Nunc ac sem ut dolor dapibus gravida. Aliquam tincidunt, nunc ac mattis ornare, lectus ante dictum mi, ac mattis velit justo nec ante. Maecenas mi felis, adipiscing fringilla, porttitor vulputate, posuere vulputate, lacus. Cras interdum. Nunc sollicitudin commodo ipsum. Suspendisse non leo." 394 | ], 395 | [ 396 | "Drew Acevedo", 397 | "0.22425876566644", 398 | "Etiam.ligula.tortor@magnaPraesent.edu", 399 | "iaculis nec, eleifend non, dapibus rutrum, justo. Praesent luctus. Curabitur egestas nunc sed libero. Proin sed turpis nec mauris blandit mattis. Cras eget nisi dictum augue malesuada malesuada. Integer id magna et ipsum cursus vestibulum. Mauris magna. Duis dignissim tempor arcu. Vestibulum ut eros non enim commodo hendrerit. Donec porttitor tellus non magna." 400 | ], 401 | [ 402 | "Joseph Holloway", 403 | "3.2484894944076", 404 | "cubilia.Curae.Donec@arcuMorbisit.co.uk", 405 | "tempor erat neque non quam. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Aliquam fringilla cursus purus. Nullam scelerisque neque sed sem egestas blandit. Nam nulla magna, malesuada vel, convallis in, cursus et, eros. Proin ultrices. Duis volutpat nunc sit amet metus. Aliquam erat volutpat. Nulla facilisis. Suspendisse" 406 | ], 407 | [ 408 | "Hashim Hunt", 409 | "3.7611250844844", 410 | "gravida.Praesent.eu@euodiotristique.co.uk", 411 | "hendrerit neque. In ornare sagittis felis. Donec tempor, est ac mattis semper, dui lectus rutrum urna, nec luctus felis purus ac tellus. Suspendisse sed dolor. Fusce mi lorem, vehicula et, rutrum eu, ultrices sit amet, risus. Donec nibh enim, gravida sit amet, dapibus id, blandit at, nisi. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Proin vel nisl. Quisque fringilla euismod enim. Etiam gravida molestie arcu. Sed eu nibh vulputate mauris sagittis placerat. Cras dictum ultricies ligula. Nullam enim. Sed nulla ante, iaculis nec, eleifend non, dapibus rutrum, justo. Praesent luctus. Curabitur egestas nunc sed libero." 412 | ], 413 | [ 414 | "Elton Marsh", 415 | "1.1611870562918", 416 | "dolor.dolor@Aliquamgravida.org", 417 | "nibh lacinia orci, consectetuer euismod est arcu ac orci. Ut semper pretium neque. Morbi quis urna. Nunc quis arcu vel quam dignissim pharetra. Nam ac nulla. In tincidunt congue turpis. In condimentum. Donec at arcu. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices" 418 | ], 419 | [ 420 | "Demetrius Browning", 421 | "0.80158215866453", 422 | "nibh@iaculisnec.org", 423 | "et, eros. Proin ultrices. Duis volutpat nunc sit amet metus. Aliquam erat volutpat. Nulla facilisis. Suspendisse commodo tincidunt nibh. Phasellus nulla. Integer vulputate, risus a ultricies adipiscing, enim mi tempor lorem, eget mollis lectus pede et risus. Quisque libero lacus, varius et, euismod et, commodo at, libero. Morbi accumsan laoreet ipsum. Curabitur consequat, lectus sit amet luctus vulputate, nisi sem semper erat, in consectetuer ipsum nunc id enim. Curabitur massa. Vestibulum accumsan neque et nunc. Quisque ornare tortor at risus. Nunc" 424 | ], 425 | [ 426 | "Bevis Mcfarland", 427 | "0.30128351197158", 428 | "Nam@dui.org", 429 | "Etiam laoreet, libero et tristique pellentesque, tellus sem mollis dui, in sodales elit erat vitae risus. Duis a mi fringilla mi lacinia mattis. Integer eu lacus. Quisque imperdiet, erat nonummy ultricies ornare, elit elit fermentum risus, at fringilla purus mauris a nunc. In at pede. Cras vulputate velit eu sem. Pellentesque ut ipsum ac mi eleifend egestas. Sed pharetra, felis eget varius ultrices, mauris ipsum porta elit, a feugiat tellus lorem eu metus. In lorem. Donec elementum, lorem" 430 | ], 431 | [ 432 | "Branden Berry", 433 | "2.4066575933311", 434 | "ultricies.ornare@interdum.edu", 435 | "venenatis a, magna. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Etiam laoreet, libero et tristique pellentesque, tellus sem mollis dui, in sodales" 436 | ], 437 | [ 438 | "Josiah Quinn", 439 | "-2.4753853101846", 440 | "nunc.risus.varius@imperdietullamcorper.edu", 441 | "blandit congue. In scelerisque scelerisque dui. Suspendisse ac metus vitae velit egestas lacinia. Sed congue, elit sed consequat auctor, nunc nulla vulputate dui, nec tempus mauris erat eget ipsum. Suspendisse sagittis. Nullam vitae diam. Proin dolor." 442 | ], 443 | [ 444 | "Jerome Clark", 445 | "-0.68994637737092", 446 | "nec.cursus@condimentumeget.org", 447 | "gravida sit amet, dapibus id, blandit at, nisi. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Proin vel nisl. Quisque fringilla euismod enim. Etiam gravida molestie arcu. Sed eu nibh vulputate mauris sagittis placerat. Cras dictum ultricies ligula. Nullam enim. Sed nulla ante, iaculis nec, eleifend non, dapibus rutrum, justo. Praesent luctus. Curabitur egestas nunc sed libero. Proin sed turpis nec mauris blandit mattis. Cras eget nisi dictum augue malesuada malesuada. Integer id magna et ipsum cursus vestibulum. Mauris magna. Duis dignissim tempor arcu." 448 | ], 449 | [ 450 | "Hall Delaney", 451 | "4.982605394196", 452 | "aliquet.molestie@massa.net", 453 | "auctor odio a purus. Duis elementum, dui quis accumsan convallis, ante lectus convallis est, vitae sodales nisi magna sed dui. Fusce aliquam, enim nec tempus scelerisque, lorem ipsum sodales purus, in molestie tortor nibh sit amet orci. Ut sagittis lobortis mauris. Suspendisse aliquet molestie tellus. Aenean egestas hendrerit neque. In ornare sagittis felis. Donec tempor, est ac mattis semper, dui lectus rutrum urna, nec luctus felis purus ac tellus. Suspendisse sed dolor. Fusce" 454 | ], 455 | [ 456 | "Ishmael Jacobs", 457 | "-1.4192648005693", 458 | "consectetuer.adipiscing.elit@liberoInteger.org", 459 | "Donec at arcu. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Donec tincidunt. Donec vitae erat vel pede blandit congue. In scelerisque scelerisque dui. Suspendisse ac metus vitae velit egestas lacinia. Sed congue, elit sed consequat auctor, nunc nulla vulputate dui, nec tempus mauris erat eget ipsum. Suspendisse sagittis. Nullam vitae diam. Proin dolor. Nulla semper tellus id nunc interdum feugiat. Sed nec metus facilisis lorem tristique aliquet. Phasellus fermentum convallis ligula. Donec luctus aliquet odio. Etiam ligula tortor, dictum eu, placerat eget, venenatis a, magna." 460 | ], 461 | [ 462 | "Jordan Roth", 463 | "-1.5429754167248", 464 | "non.cursus@Etiambibendumfermentum.org", 465 | "faucibus. Morbi vehicula. Pellentesque tincidunt tempus risus. Donec egestas. Duis ac arcu. Nunc mauris. Morbi non sapien molestie orci tincidunt adipiscing. Mauris molestie pharetra" 466 | ], 467 | [ 468 | "Brady Collins", 469 | "-0.17245973265889", 470 | "tellus.imperdiet.non@dapibusrutrum.org", 471 | "Sed auctor odio a purus. Duis elementum, dui quis accumsan convallis, ante lectus convallis est, vitae sodales nisi magna sed dui. Fusce aliquam, enim nec tempus scelerisque, lorem ipsum sodales purus, in molestie tortor nibh sit amet orci. Ut sagittis lobortis mauris. Suspendisse aliquet molestie tellus. Aenean egestas hendrerit neque." 472 | ], 473 | [ 474 | "Myles Olsen", 475 | "1.7861207328842", 476 | "vitae.aliquet@tellus.ca", 477 | "enim. Etiam gravida molestie arcu. Sed eu nibh vulputate mauris sagittis placerat. Cras dictum ultricies ligula. Nullam enim. Sed nulla ante, iaculis nec, eleifend non, dapibus rutrum, justo. Praesent luctus. Curabitur egestas nunc sed libero. Proin sed turpis nec mauris blandit mattis. Cras eget nisi dictum augue malesuada malesuada. Integer id magna et ipsum" 478 | ], 479 | [ 480 | "Ivan Knowles", 481 | "4.1453146849747", 482 | "sem.molestie.sodales@Vestibulum.ca", 483 | "lacus. Nulla tincidunt, neque vitae semper egestas, urna justo faucibus lectus, a sollicitudin orci sem eget massa. Suspendisse eleifend. Cras sed leo. Cras vehicula aliquet libero. Integer in magna. Phasellus dolor elit, pellentesque a, facilisis non, bibendum sed, est. Nunc laoreet lectus quis massa. Mauris vestibulum, neque sed dictum eleifend, nunc risus varius orci, in consequat enim diam vel arcu. Curabitur ut odio vel est tempor bibendum. Donec felis orci, adipiscing non, luctus sit amet, faucibus ut, nulla. Cras eu tellus eu augue porttitor interdum. Sed auctor odio a purus. Duis elementum, dui" 484 | ], 485 | [ 486 | "Cain Watts", 487 | "-0.030489647464192", 488 | "fames.ac@bibendumDonec.net", 489 | "et, magna. Praesent interdum ligula eu enim. Etiam" 490 | ], 491 | [ 492 | "Beau Gibson", 493 | "-1.837832743224", 494 | "Ut.tincidunt@tortornibh.com", 495 | "arcu iaculis enim, sit amet ornare lectus justo eu arcu. Morbi sit amet massa. Quisque porttitor eros nec tellus. Nunc lectus pede, ultrices a, auctor non, feugiat nec, diam. Duis mi enim, condimentum eget, volutpat ornare, facilisis eget, ipsum. Donec sollicitudin adipiscing ligula. Aenean gravida nunc sed pede. Cum sociis natoque penatibus" 496 | ], 497 | [ 498 | "Melvin Cannon", 499 | "-3.3119731013116", 500 | "adipiscing.lobortis@ametrisus.net", 501 | "metus. In nec orci. Donec nibh. Quisque nonummy ipsum non arcu. Vivamus sit amet risus. Donec egestas. Aliquam nec enim." 502 | ], 503 | [ 504 | "Fritz Bean", 505 | "-2.5906932148993", 506 | "semper@vitaedolorDonec.co.uk", 507 | "tincidunt pede ac urna. Ut tincidunt vehicula risus. Nulla eget metus eu erat semper rutrum." 508 | ], 509 | [ 510 | "Anthony Hawkins", 511 | "-0.67372668563255", 512 | "erat.nonummy@tortor.ca", 513 | "convallis erat, eget tincidunt dui augue eu tellus. Phasellus elit pede, malesuada vel, venenatis vel, faucibus id, libero. Donec consectetuer mauris id sapien. Cras dolor dolor, tempus non, lacinia at, iaculis quis, pede. Praesent eu dui. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Aenean eget magna. Suspendisse tristique neque venenatis lacus. Etiam bibendum fermentum metus. Aenean sed pede nec ante blandit viverra. Donec tempus," 514 | ], 515 | [ 516 | "Quamar Norton", 517 | "-1.4736499148849", 518 | "lectus@volutpatNulladignissim.co.uk", 519 | "Maecenas iaculis aliquet diam. Sed diam" 520 | ], 521 | [ 522 | "Abdul Decker", 523 | "-3.843727324784", 524 | "nec.mollis@etrisus.org", 525 | "arcu. Vestibulum ut eros non enim commodo hendrerit. Donec porttitor tellus non magna. Nam ligula elit, pretium et, rutrum non, hendrerit id, ante. Nunc mauris sapien, cursus in, hendrerit consectetuer, cursus et, magna. Praesent interdum ligula eu enim. Etiam imperdiet dictum magna. Ut tincidunt orci quis lectus. Nullam suscipit, est ac facilisis facilisis, magna tellus faucibus leo, in lobortis tellus justo sit amet nulla. Donec" 526 | ], 527 | [ 528 | "Xavier Harper", 529 | "-0.36097301764123", 530 | "sodales.nisi.magna@pedeSuspendissedui.edu", 531 | "Integer urna. Vivamus" 532 | ], 533 | [ 534 | "Tanner Estrada", 535 | "0.48270337988774", 536 | "accumsan@sempercursus.net", 537 | "tempor erat neque non quam. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Aliquam fringilla cursus purus. Nullam scelerisque neque sed sem egestas blandit. Nam nulla magna, malesuada vel, convallis in, cursus et, eros. Proin" 538 | ], 539 | [ 540 | "Timothy Sharp", 541 | "0.58137910463222", 542 | "gravida.Praesent.eu@euplacerateget.com", 543 | "dis parturient montes, nascetur ridiculus mus. Proin vel arcu eu odio tristique pharetra. Quisque ac libero nec ligula" 544 | ], 545 | [ 546 | "Kieran Clayton", 547 | "0.98005356053939", 548 | "ut@idnuncinterdum.co.uk", 549 | "elit erat vitae risus. Duis a mi fringilla mi lacinia mattis. Integer eu lacus. Quisque imperdiet, erat nonummy ultricies ornare, elit elit fermentum risus, at fringilla purus mauris a nunc. In at pede. Cras vulputate velit eu sem. Pellentesque ut ipsum ac mi eleifend egestas. Sed pharetra, felis eget varius ultrices, mauris ipsum porta elit, a feugiat tellus lorem eu metus. In lorem. Donec elementum, lorem ut aliquam iaculis, lacus pede sagittis augue, eu tempor erat neque non quam. Pellentesque habitant morbi tristique senectus et netus et" 550 | ], 551 | [ 552 | "Erasmus Huffman", 553 | "1.8806709370891", 554 | "vulputate.risus.a@ac.edu", 555 | "dolor dolor, tempus non, lacinia at, iaculis quis, pede. Praesent eu dui. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Aenean eget magna. Suspendisse tristique neque venenatis lacus. Etiam bibendum fermentum metus. Aenean sed pede nec ante blandit viverra. Donec tempus, lorem fringilla ornare placerat, orci lacus vestibulum lorem, sit amet ultricies sem magna nec quam. Curabitur vel lectus. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec dignissim magna a tortor. Nunc commodo auctor velit. Aliquam nisl. Nulla eu" 556 | ], 557 | [ 558 | "Forrest Russell", 559 | "8.5332255400929", 560 | "velit@accumsansedfacilisis.org", 561 | "tempor bibendum. Donec felis orci, adipiscing non, luctus sit amet," 562 | ], 563 | [ 564 | "Dennis Sykes", 565 | "-1.8076412489931", 566 | "accumsan.interdum@senectus.com", 567 | "sit amet lorem semper auctor. Mauris vel turpis. Aliquam adipiscing lobortis risus. In mi pede, nonummy ut, molestie in, tempus eu, ligula. Aenean euismod mauris eu elit. Nulla facilisi. Sed neque. Sed eget lacus. Mauris non dui nec urna suscipit nonummy. Fusce fermentum fermentum arcu. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Phasellus ornare. Fusce mollis. Duis sit amet diam eu dolor egestas rhoncus. Proin nisl sem, consequat nec, mollis vitae, posuere at," 568 | ], 569 | [ 570 | "Timothy Lowe", 571 | "-2.0903915164183", 572 | "amet@ante.net", 573 | "malesuada malesuada. Integer id magna et ipsum cursus vestibulum. Mauris magna. Duis dignissim tempor arcu. Vestibulum ut eros non enim commodo hendrerit. Donec porttitor tellus non magna. Nam ligula elit, pretium et, rutrum non, hendrerit id, ante. Nunc mauris sapien, cursus in, hendrerit consectetuer, cursus et, magna. Praesent interdum ligula eu enim. Etiam imperdiet dictum magna." 574 | ], 575 | [ 576 | "Quentin Mack", 577 | "-1.4391826485347", 578 | "hendrerit@portaelit.ca", 579 | "consectetuer, cursus et, magna. Praesent interdum ligula eu enim. Etiam imperdiet dictum magna. Ut tincidunt orci quis lectus. Nullam suscipit, est ac facilisis facilisis, magna tellus faucibus leo, in lobortis tellus justo sit" 580 | ], 581 | [ 582 | "Joseph Greer", 583 | "-0.48789210895226", 584 | "pretium@nullamagnamalesuada.com", 585 | "metus eu erat semper rutrum. Fusce dolor quam, elementum at, egestas a, scelerisque sed, sapien. Nunc pulvinar arcu et pede. Nunc sed orci lobortis augue scelerisque mollis. Phasellus libero mauris, aliquam eu, accumsan sed, facilisis vitae, orci. Phasellus dapibus quam quis diam. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Fusce aliquet magna a neque. Nullam ut nisi a odio semper cursus. Integer mollis. Integer tincidunt aliquam arcu. Aliquam ultrices iaculis odio. Nam interdum enim non nisi. Aenean eget metus. In nec orci." 586 | ], 587 | [ 588 | "Lane Yates", 589 | "-0.56844090764023", 590 | "libero.Proin.mi@Donec.co.uk", 591 | "sem elit, pharetra ut, pharetra sed, hendrerit a, arcu. Sed et libero. Proin mi. Aliquam gravida mauris ut mi. Duis risus odio, auctor vitae, aliquet nec, imperdiet nec, leo. Morbi neque tellus, imperdiet non, vestibulum nec, euismod in, dolor. Fusce feugiat. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aliquam auctor, velit eget laoreet posuere," 592 | ], 593 | [ 594 | "Fritz Mccall", 595 | "-0.55275693523155", 596 | "ligula.Nullam.feugiat@eleifendvitae.co.uk", 597 | "a, scelerisque sed, sapien. Nunc pulvinar arcu et pede. Nunc sed orci lobortis augue scelerisque mollis. Phasellus libero mauris, aliquam eu, accumsan sed, facilisis vitae, orci. Phasellus dapibus quam quis diam. Pellentesque habitant morbi tristique senectus et netus et malesuada" 598 | ], 599 | [ 600 | "Amir Tyler", 601 | "2.4082553783746", 602 | "sollicitudin@bibendumfermentummetus.net", 603 | "eu elit. Nulla facilisi. Sed neque. Sed eget lacus. Mauris non dui nec urna suscipit nonummy. Fusce fermentum fermentum arcu. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Phasellus ornare. Fusce mollis. Duis sit amet diam eu dolor egestas rhoncus. Proin nisl sem, consequat nec, mollis vitae, posuere at, velit. Cras lorem lorem, luctus ut, pellentesque eget, dictum placerat, augue. Sed molestie. Sed" 604 | ], 605 | [ 606 | "Chadwick Dixon", 607 | "1.9932249513776", 608 | "ac.fermentum.vel@Donec.co.uk", 609 | "est. Nunc laoreet lectus quis massa. Mauris vestibulum, neque sed dictum eleifend, nunc risus varius orci, in consequat enim diam vel arcu. Curabitur ut odio vel est tempor bibendum. Donec felis orci, adipiscing non, luctus sit amet, faucibus ut, nulla. Cras eu tellus eu augue porttitor interdum. Sed auctor odio a purus. Duis elementum, dui quis accumsan convallis, ante lectus convallis est, vitae sodales nisi magna sed dui. Fusce aliquam, enim nec tempus scelerisque, lorem" 610 | ], 611 | [ 612 | "Marvin Brady", 613 | "0.05781232512703", 614 | "ligula@purusaccumsan.org", 615 | "Phasellus dapibus quam quis diam. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Fusce aliquet magna a neque. Nullam ut nisi a odio semper cursus. Integer mollis. Integer tincidunt aliquam arcu. Aliquam ultrices iaculis odio. Nam interdum enim non nisi. Aenean eget metus. In nec orci. Donec nibh. Quisque nonummy ipsum non arcu. Vivamus sit amet risus. Donec egestas. Aliquam nec enim. Nunc ut erat. Sed nunc est, mollis non, cursus non, egestas a, dui. Cras pellentesque. Sed dictum. Proin eget odio. Aliquam vulputate ullamcorper magna. Sed eu eros." 616 | ], 617 | [ 618 | "Vaughan Battle", 619 | "1.5361009969472", 620 | "vel.turpis.Aliquam@sagittisaugueeu.co.uk", 621 | "arcu. Morbi sit amet massa. Quisque porttitor eros nec tellus. Nunc lectus pede, ultrices a, auctor non, feugiat nec, diam. Duis mi enim, condimentum eget, volutpat ornare, facilisis eget, ipsum. Donec sollicitudin adipiscing ligula. Aenean gravida nunc sed pede. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Proin vel arcu eu odio tristique pharetra. Quisque ac libero nec ligula consectetuer rhoncus. Nullam velit dui," 622 | ], 623 | [ 624 | "Mufutau Hensley", 625 | "-3.1209431406531", 626 | "libero.nec@Maurisnon.net", 627 | "egestas. Aliquam nec enim. Nunc ut erat. Sed nunc est, mollis non, cursus non, egestas a, dui. Cras pellentesque. Sed dictum. Proin" 628 | ], 629 | [ 630 | "Hashim Burgess", 631 | "-1.0366913548628", 632 | "amet@pedesagittis.co.uk", 633 | "Aliquam adipiscing lobortis risus. In mi" 634 | ], 635 | [ 636 | "Keegan Dickson", 637 | "-0.29610893334066", 638 | "ligula.consectetuer.rhoncus@Quisquetincidunt.com", 639 | "Quisque ornare tortor at risus. Nunc ac sem ut dolor dapibus gravida. Aliquam tincidunt, nunc ac mattis ornare, lectus ante dictum mi, ac mattis velit justo nec ante. Maecenas mi felis, adipiscing fringilla, porttitor vulputate, posuere vulputate, lacus. Cras interdum. Nunc sollicitudin commodo ipsum. Suspendisse non leo. Vivamus nibh dolor, nonummy ac, feugiat non, lobortis quis, pede. Suspendisse dui. Fusce diam nunc, ullamcorper eu, euismod ac, fermentum vel, mauris. Integer sem elit, pharetra ut, pharetra sed, hendrerit" 640 | ], 641 | [ 642 | "Emmanuel Cochran", 643 | "-3.8255590426156", 644 | "pellentesque@Mauris.net", 645 | "Cras lorem lorem, luctus ut, pellentesque eget, dictum placerat, augue. Sed molestie. Sed id risus quis diam luctus lobortis." 646 | ], 647 | [ 648 | "Coby Munoz", 649 | "0.21262067727363", 650 | "vulputate@Donec.edu", 651 | "ullamcorper magna. Sed eu eros. Nam consequat dolor vitae dolor. Donec fringilla. Donec feugiat metus sit amet ante. Vivamus non lorem vitae odio sagittis semper. Nam tempor diam" 652 | ], 653 | [ 654 | "Hamish Wilkerson", 655 | "-0.89291892461309", 656 | "felis.Donec@malesuada.edu", 657 | "ornare egestas ligula. Nullam feugiat placerat velit. Quisque varius. Nam porttitor scelerisque neque. Nullam nisl. Maecenas malesuada fringilla est. Mauris eu turpis. Nulla aliquet. Proin velit. Sed malesuada augue ut lacus. Nulla tincidunt, neque vitae semper egestas, urna justo faucibus lectus, a sollicitudin orci sem eget" 658 | ], 659 | [ 660 | "Channing West", 661 | "1.974470855538", 662 | "vulputate.dui.nec@lacus.co.uk", 663 | "ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Phasellus ornare. Fusce mollis. Duis sit amet diam eu dolor egestas rhoncus. Proin nisl sem, consequat nec, mollis vitae, posuere at, velit. Cras lorem lorem, luctus ut, pellentesque eget, dictum placerat, augue. Sed molestie. Sed id risus quis diam luctus lobortis. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos hymenaeos. Mauris ut" 664 | ] 665 | ] 666 | }`; 667 | 668 | private immutable testString2 = `[ 669 | { 670 | "id": 0, 671 | "guid": "9f9b6edf-2386-4681-bdac-1797f5e5e397", 672 | "isActive": true, 673 | "balance": "$1,381.05", 674 | "picture": "http://placehold.it/32x32", 675 | "age": 21, 676 | "eyeColor": "brown", 677 | "name": "Dodson Lamb", 678 | "gender": "male", 679 | "company": "ZENTILITY", 680 | "email": "dodsonlamb@zentility.com", 681 | "phone": "+1 (878) 551-3097", 682 | "address": "328 Campus Place, Sparkill, New Jersey, 3647", 683 | "about": "In nostrud nostrud reprehenderit anim duis labore. Officia ut sit sunt Lorem. In ipsum aute sit mollit mollit non. Duis sit est eu aliqua amet. Exercitation esse nostrud cillum aute ad excepteur esse est nostrud.\r\n", 684 | "registered": "2014-04-02T11:36:36 +07:00", 685 | "latitude": 23.677115, 686 | "longitude": -148.126595, 687 | "tags": [ 688 | "tempor", 689 | "excepteur", 690 | "dolor", 691 | "eu", 692 | "adipisicing", 693 | "dolor", 694 | "Lorem" 695 | ], 696 | "friends": [ 697 | { 698 | "id": 0, 699 | "name": "Harrell Sheppard" 700 | }, 701 | { 702 | "id": 1, 703 | "name": "Alana Hardin" 704 | }, 705 | { 706 | "id": 2, 707 | "name": "Hollie Merritt" 708 | } 709 | ], 710 | "greeting": "Hello, Dodson Lamb! You have 6 unread messages.", 711 | "favoriteFruit": "strawberry" 712 | }, 713 | { 714 | "id": 1, 715 | "guid": "d25a9f69-a260-458f-bcac-0e1e59e05215", 716 | "isActive": true, 717 | "balance": "$3,733.60", 718 | "picture": "http://placehold.it/32x32", 719 | "age": 29, 720 | "eyeColor": "blue", 721 | "name": "Nell Ray", 722 | "gender": "female", 723 | "company": "FUTURITY", 724 | "email": "nellray@futurity.com", 725 | "phone": "+1 (823) 450-2959", 726 | "address": "761 Tompkins Avenue, Kansas, Marshall Islands, 8019", 727 | "about": "Incididunt anim in minim sint laborum consectetur sint adipisicing. Aliqua ullamco eiusmod aute quis voluptate reprehenderit pariatur ea eu sunt. Consectetur commodo Lorem laborum dolore eiusmod nisi dolor laborum. Magna mollit Lorem occaecat aliqua est consectetur officia quis cillum ea ea laborum. Aliqua et enim cillum dolor ad labore cillum quis non officia est pariatur incididunt mollit. Dolor elit aliquip ullamco esse magna commodo in.\r\n", 728 | "registered": "2014-01-30T21:32:18 +08:00", 729 | "latitude": 51.832448, 730 | "longitude": -98.48817, 731 | "tags": [ 732 | "eu", 733 | "qui", 734 | "ad", 735 | "consequat", 736 | "occaecat", 737 | "ullamco", 738 | "est" 739 | ], 740 | "friends": [ 741 | { 742 | "id": 0, 743 | "name": "Letha Ramsey" 744 | }, 745 | { 746 | "id": 1, 747 | "name": "Lewis Cotton" 748 | }, 749 | { 750 | "id": 2, 751 | "name": "Vega Hunt" 752 | } 753 | ], 754 | "greeting": "Hello, Nell Ray! You have 4 unread messages.", 755 | "favoriteFruit": "banana" 756 | }, 757 | { 758 | "id": 2, 759 | "guid": "95deefa7-5468-4838-bbbc-c17e5d8afca7", 760 | "isActive": false, 761 | "balance": "$2,920.86", 762 | "picture": "http://placehold.it/32x32", 763 | "age": 36, 764 | "eyeColor": "green", 765 | "name": "Parks Wyatt", 766 | "gender": "male", 767 | "company": "NETPLODE", 768 | "email": "parkswyatt@netplode.com", 769 | "phone": "+1 (857) 514-3706", 770 | "address": "220 Canda Avenue, Wilsonia, Texas, 8807", 771 | "about": "Magna mollit incididunt ex occaecat mollit. Et dolore amet duis enim aute est dolor tempor sunt velit. Nisi anim reprehenderit eiusmod nostrud ut ut ea labore sint enim ut ut. Nisi laborum incididunt velit est irure nisi. Velit ut commodo ullamco magna ullamco fugiat cupidatat consequat enim. Aliqua reprehenderit ipsum quis sit duis consectetur nulla proident eu velit ex.\r\n", 772 | "registered": "2014-02-12T23:53:10 +08:00", 773 | "latitude": -57.207168, 774 | "longitude": 157.559663, 775 | "tags": [ 776 | "pariatur", 777 | "laborum", 778 | "cillum", 779 | "aute", 780 | "excepteur", 781 | "deserunt", 782 | "cupidatat" 783 | ], 784 | "friends": [ 785 | { 786 | "id": 0, 787 | "name": "Hart Gillespie" 788 | }, 789 | { 790 | "id": 1, 791 | "name": "Donaldson Wise" 792 | }, 793 | { 794 | "id": 2, 795 | "name": "Heidi Horton" 796 | } 797 | ], 798 | "greeting": "Hello, Parks Wyatt! You have 2 unread messages.", 799 | "favoriteFruit": "strawberry" 800 | }, 801 | { 802 | "id": 3, 803 | "guid": "b675df80-e3fc-4cd5-819d-dbb2e2285734", 804 | "isActive": true, 805 | "balance": "$1,475.81", 806 | "picture": "http://placehold.it/32x32", 807 | "age": 26, 808 | "eyeColor": "blue", 809 | "name": "Amber Petty", 810 | "gender": "female", 811 | "company": "NSPIRE", 812 | "email": "amberpetty@nspire.com", 813 | "phone": "+1 (950) 443-2267", 814 | "address": "601 Amersfort Place, Bath, Arizona, 7539", 815 | "about": "In proident duis dolore voluptate est velit aute non tempor est nisi non nisi occaecat. Tempor consequat exercitation minim eiusmod sunt cillum ex voluptate aute pariatur magna consectetur esse. Eiusmod culpa ex est do consectetur mollit. Eu proident quis culpa aliquip aute do pariatur consequat sit id irure consectetur irure. Tempor nulla reprehenderit qui cupidatat excepteur sit eiusmod exercitation duis laborum laboris. Laboris laboris minim fugiat nostrud minim qui sit Lorem. Cillum aliqua magna sit velit eiusmod magna.\r\n", 816 | "registered": "2014-07-14T00:12:43 +07:00", 817 | "latitude": -41.714895, 818 | "longitude": 177.329422, 819 | "tags": [ 820 | "duis", 821 | "voluptate", 822 | "veniam", 823 | "id", 824 | "dolore", 825 | "dolore", 826 | "voluptate" 827 | ], 828 | "friends": [ 829 | { 830 | "id": 0, 831 | "name": "Hernandez Stephens" 832 | }, 833 | { 834 | "id": 1, 835 | "name": "Strickland Harper" 836 | }, 837 | { 838 | "id": 2, 839 | "name": "Pope Knowles" 840 | } 841 | ], 842 | "greeting": "Hello, Amber Petty! You have 6 unread messages.", 843 | "favoriteFruit": "strawberry" 844 | }, 845 | { 846 | "id": 4, 847 | "guid": "22051a85-3d83-43ed-9219-f3df28b4f67a", 848 | "isActive": true, 849 | "balance": "$3,291.25", 850 | "picture": "http://placehold.it/32x32", 851 | "age": 24, 852 | "eyeColor": "brown", 853 | "name": "Pennington Burt", 854 | "gender": "male", 855 | "company": "INSECTUS", 856 | "email": "penningtonburt@insectus.com", 857 | "phone": "+1 (875) 423-2987", 858 | "address": "881 Powers Street, Williamson, Indiana, 7457", 859 | "about": "Cillum consectetur ea do laborum mollit officia nulla nisi ut laborum consequat voluptate dolore mollit. Ea consequat esse deserunt dolor aliquip eiusmod irure commodo nisi. Voluptate esse dolor et commodo do elit enim cillum magna.\r\n", 860 | "registered": "2014-01-30T15:34:15 +08:00", 861 | "latitude": -46.545254, 862 | "longitude": -67.199307, 863 | "tags": [ 864 | "excepteur", 865 | "id", 866 | "tempor", 867 | "veniam", 868 | "velit", 869 | "occaecat", 870 | "velit" 871 | ], 872 | "friends": [ 873 | { 874 | "id": 0, 875 | "name": "Giles Underwood" 876 | }, 877 | { 878 | "id": 1, 879 | "name": "Bernard Short" 880 | }, 881 | { 882 | "id": 2, 883 | "name": "Josefina Weiss" 884 | } 885 | ], 886 | "greeting": "Hello, Pennington Burt! You have 6 unread messages.", 887 | "favoriteFruit": "apple" 888 | }, 889 | { 890 | "id": 5, 891 | "guid": "feec8dbd-4f61-4adf-8f45-1b2d5f0b5e65", 892 | "isActive": false, 893 | "balance": "$3,919.03", 894 | "picture": "http://placehold.it/32x32", 895 | "age": 31, 896 | "eyeColor": "green", 897 | "name": "Esther Herring", 898 | "gender": "female", 899 | "company": "XYLAR", 900 | "email": "estherherring@xylar.com", 901 | "phone": "+1 (973) 436-3800", 902 | "address": "926 Baycliff Terrace, Biehle, Georgia, 3487", 903 | "about": "Sint Lorem excepteur fugiat est quis consequat ea. Cillum incididunt enim exercitation tempor quis excepteur laboris minim. Eiusmod ullamco minim commodo deserunt.\r\n", 904 | "registered": "2014-03-20T19:18:54 +07:00", 905 | "latitude": 89.573316, 906 | "longitude": 40.609971, 907 | "tags": [ 908 | "reprehenderit", 909 | "sint", 910 | "veniam", 911 | "non", 912 | "dolor", 913 | "commodo", 914 | "incididunt" 915 | ], 916 | "friends": [ 917 | { 918 | "id": 0, 919 | "name": "Traci Newman" 920 | }, 921 | { 922 | "id": 1, 923 | "name": "Hull Charles" 924 | }, 925 | { 926 | "id": 2, 927 | "name": "Christian Nielsen" 928 | } 929 | ], 930 | "greeting": "Hello, Esther Herring! You have 6 unread messages.", 931 | "favoriteFruit": "apple" 932 | } 933 | ]`; 934 | } 935 | 936 | unittest 937 | { 938 | import std.algorithm; 939 | import std.math; 940 | 941 | static void test(string txt) 942 | { 943 | /*auto val = */toJSONValue(txt); 944 | // Note: order is undefined due to AA use in JSONValue 945 | //assert(val.toJSON() == val.toJSON().parseJSONValue().toJSON()); 946 | 947 | static bool ncmp(JSONParserNode!string a, JSONParserNode!string b) 948 | { 949 | if (a.kind == b.kind && a.kind == JSONParserNodeKind.literal) { 950 | if (a.literal.kind == b.literal.kind && a.literal.kind == JSONTokenKind.number) 951 | return a.literal.number.approxEqual(b.literal.number); 952 | } 953 | return a == b; 954 | } 955 | 956 | auto nodes = parseJSONStream(txt); 957 | auto nodes2 = nodes.toJSON().parseJSONStream(); 958 | assert(equal!ncmp(nodes, nodes2)); 959 | } 960 | 961 | test(testString1); 962 | test(testString2); 963 | } 964 | -------------------------------------------------------------------------------- /source/stdx/data/json/lexer.d: -------------------------------------------------------------------------------- 1 | /** 2 | * Provides JSON lexing facilities. 3 | * 4 | * Synopsis: 5 | * --- 6 | * // Lex a JSON string into a lazy range of tokens 7 | * auto tokens = lexJSON(`{"name": "Peter", "age": 42}`); 8 | * 9 | * with (JSONToken) { 10 | * assert(tokens.map!(t => t.kind).equal( 11 | * [Kind.objectStart, Kind.string, Kind.colon, Kind.string, Kind.comma, 12 | * Kind.string, Kind.colon, Kind.number, Kind.objectEnd])); 13 | * } 14 | * 15 | * // Get detailed information 16 | * tokens.popFront(); // skip the '{' 17 | * assert(tokens.front.string == "name"); 18 | * tokens.popFront(); // skip "name" 19 | * tokens.popFront(); // skip the ':' 20 | * assert(tokens.front.string == "Peter"); 21 | * assert(tokens.front.location.line == 0); 22 | * assert(tokens.front.location.column == 9); 23 | * --- 24 | * 25 | * Credits: 26 | * Support for escaped UTF-16 surrogates was contributed to the original 27 | * vibe.d JSON module by Etienne Cimon. The number parsing code is based 28 | * on the version contained in Andrei Alexandrescu's "std.jgrandson" 29 | * module draft. 30 | * 31 | * Copyright: Copyright 2012 - 2015, Sönke Ludwig. 32 | * License: $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0). 33 | * Authors: Sönke Ludwig 34 | * Source: $(PHOBOSSRC std/data/json/lexer.d) 35 | */ 36 | module stdx.data.json.lexer; 37 | 38 | import std.range; 39 | import std.array : appender; 40 | import std.traits : isIntegral, isSomeChar, isSomeString; 41 | import stdx.data.json.foundation; 42 | 43 | 44 | /** 45 | * Returns a lazy range of tokens corresponding to the given JSON input string. 46 | * 47 | * The input must be a valid JSON string, given as an input range of either 48 | * characters, or of integral values. In case of integral types, the input 49 | * ecoding is assumed to be a superset of ASCII that is parsed unit by unit. 50 | * 51 | * For inputs of type $(D string) and of type $(D immutable(ubyte)[]), all 52 | * string literals will be stored as slices into the original string. String 53 | * literals containung escape sequences will be unescaped on demand when 54 | * $(D JSONString.value) is accessed. 55 | * 56 | * Throws: 57 | * Without $(D LexOptions.noThrow), a $(D JSONException) is thrown as soon as 58 | * an invalid token is encountered. 59 | * 60 | * If $(D LexOptions.noThrow) is given, lexJSON does not throw any exceptions, 61 | * apart from letting through any exceptins thrown by the input range. 62 | * Instead, a token with kind $(D JSONToken.Kind.error) is generated as the 63 | * last token in the range. 64 | */ 65 | JSONLexerRange!(Input, options, String) lexJSON 66 | (LexOptions options = LexOptions.init, String = string, Input) 67 | (Input input, string filename = null) 68 | if (isInputRange!Input && (isSomeChar!(ElementType!Input) || isIntegral!(ElementType!Input))) 69 | { 70 | return JSONLexerRange!(Input, options, String)(input, filename); 71 | } 72 | 73 | /// 74 | unittest 75 | { 76 | import std.algorithm : equal, map; 77 | 78 | auto rng = lexJSON(`{"hello": 1.2, "world": [1, true, null]}`); 79 | with (JSONTokenKind) 80 | { 81 | assert(rng.map!(t => t.kind).equal( 82 | [objectStart, string, colon, number, comma, 83 | string, colon, arrayStart, number, comma, 84 | boolean, comma, null_, arrayEnd, 85 | objectEnd])); 86 | } 87 | } 88 | 89 | /// 90 | unittest 91 | { 92 | auto rng = lexJSON("true\n false null\r\n 1.0\r \"test\""); 93 | rng.popFront(); 94 | assert(rng.front.boolean == false); 95 | assert(rng.front.location.line == 1 && rng.front.location.column == 3); 96 | rng.popFront(); 97 | assert(rng.front.kind == JSONTokenKind.null_); 98 | assert(rng.front.location.line == 1 && rng.front.location.column == 9); 99 | rng.popFront(); 100 | assert(rng.front.number == 1.0); 101 | assert(rng.front.location.line == 2 && rng.front.location.column == 2); 102 | rng.popFront(); 103 | assert(rng.front.string == "test"); 104 | assert(rng.front.location.line == 3 && rng.front.location.column == 1); 105 | rng.popFront(); 106 | assert(rng.empty); 107 | } 108 | 109 | unittest 110 | { 111 | import std.exception; 112 | assertThrown(lexJSON(`trui`).front); // invalid token 113 | assertThrown(lexJSON(`fal`).front); // invalid token 114 | assertThrown(lexJSON(`falsi`).front); // invalid token 115 | assertThrown(lexJSON(`nul`).front); // invalid token 116 | assertThrown(lexJSON(`nulX`).front); // invalid token 117 | assertThrown(lexJSON(`0.e`).front); // invalid number 118 | assertThrown(lexJSON(`xyz`).front); // invalid token 119 | } 120 | 121 | unittest { // test built-in UTF validation 122 | import std.exception; 123 | 124 | static void test_invalid(immutable(ubyte)[] str) 125 | { 126 | assertThrown(lexJSON(str).front); 127 | assertNotThrown(lexJSON(cast(string)str).front); 128 | } 129 | 130 | test_invalid(['"', 0xFF, '"']); 131 | test_invalid(['"', 0xFF, 'x', '"']); 132 | test_invalid(['"', 0xFF, 'x', '\\', 't','"']); 133 | test_invalid(['"', '\\', 't', 0xFF,'"']); 134 | test_invalid(['"', '\\', 't', 0xFF,'x','"']); 135 | 136 | static void testw_invalid(immutable(ushort)[] str) 137 | { 138 | import std.conv; 139 | assertThrown(lexJSON(str).front, str.to!string); 140 | 141 | // Invalid UTF sequences can still throw in the non-validating case, 142 | // because UTF-16 is converted to UTF-8 internally, so we don't test 143 | // this case: 144 | // assertNotThrown(lexJSON(cast(wstring)str).front); 145 | } 146 | 147 | static void testw_valid(immutable(ushort)[] str) 148 | { 149 | import std.conv; 150 | assertNotThrown(lexJSON(str).front, str.to!string); 151 | assertNotThrown(lexJSON(cast(wstring)str).front); 152 | } 153 | 154 | testw_invalid(['"', 0xD800, 0xFFFF, '"']); 155 | testw_invalid(['"', 0xD800, 0xFFFF, 'x', '"']); 156 | testw_invalid(['"', 0xD800, 0xFFFF, 'x', '\\', 't','"']); 157 | testw_invalid(['"', '\\', 't', 0xD800, 0xFFFF,'"']); 158 | testw_invalid(['"', '\\', 't', 0xD800, 0xFFFF,'x','"']); 159 | testw_valid(['"', 0xE000, '"']); 160 | testw_valid(['"', 0xE000, 'x', '"']); 161 | testw_valid(['"', 0xE000, 'x', '\\', 't','"']); 162 | testw_valid(['"', '\\', 't', 0xE000,'"']); 163 | testw_valid(['"', '\\', 't', 0xE000,'x','"']); 164 | } 165 | 166 | // Not possible to test anymore with the new String customization scheme 167 | /*static if (__VERSION__ >= 2069) 168 | @safe unittest { // test for @nogc and @safe interface 169 | static struct MyAppender { 170 | @nogc: 171 | void put(string s) { } 172 | void put(dchar ch) {} 173 | void put(char ch) {} 174 | @property string data() { return null; } 175 | } 176 | static MyAppender createAppender() @nogc { return MyAppender.init; } 177 | 178 | @nogc void test(T)() 179 | { 180 | T text; 181 | auto rng = lexJSON!(LexOptions.noThrow, createAppender)(text); 182 | while (!rng.empty) { 183 | auto f = rng.front; 184 | rng.popFront(); 185 | cast(void)f.boolean; 186 | f.number.longValue; 187 | cast(void)f.string; 188 | cast(void)f.string.anyValue; 189 | } 190 | } 191 | 192 | // just instantiate, don't run 193 | auto t1 = &test!string; 194 | auto t2 = &test!wstring; 195 | auto t3 = &test!dstring; 196 | }*/ 197 | 198 | 199 | /** 200 | * A lazy input range of JSON tokens. 201 | * 202 | * This range type takes an input string range and converts it into a range of 203 | * $(D JSONToken) values. 204 | * 205 | * See $(D lexJSON) for more information. 206 | */ 207 | struct JSONLexerRange(Input, LexOptions options = LexOptions.init, String = string) 208 | if (isInputRange!Input && (isSomeChar!(ElementType!Input) || isIntegral!(ElementType!Input))) 209 | { 210 | import std.string : representation; 211 | 212 | static if (isSomeString!Input) 213 | alias InternalInput = typeof(Input.init.representation); 214 | else 215 | alias InternalInput = Input; 216 | 217 | static if (typeof(InternalInput.init.front).sizeof > 1) 218 | alias CharType = dchar; 219 | else 220 | alias CharType = char; 221 | 222 | private 223 | { 224 | InternalInput _input; 225 | JSONToken!String _front; 226 | Location _loc; 227 | string _error; 228 | } 229 | 230 | /** 231 | * Constructs a new token stream. 232 | */ 233 | this(Input input, string filename = null) 234 | { 235 | _input = cast(InternalInput)input; 236 | _front.location.file = filename; 237 | skipWhitespace(); 238 | } 239 | 240 | /** 241 | * Returns a copy of the underlying input range. 242 | */ 243 | @property Input input() { return cast(Input)_input; } 244 | 245 | /** 246 | * The current location of the lexer. 247 | */ 248 | @property Location location() const { return _loc; } 249 | 250 | /** 251 | * Determines if the token stream has been exhausted. 252 | */ 253 | @property bool empty() 254 | { 255 | if (_front.kind != JSONTokenKind.none) return false; 256 | return _input.empty; 257 | } 258 | 259 | /** 260 | * Returns the current token in the stream. 261 | */ 262 | @property ref const(JSONToken!String) front() 263 | { 264 | ensureFrontValid(); 265 | return _front; 266 | } 267 | 268 | /** 269 | * Skips to the next token. 270 | */ 271 | void popFront() 272 | { 273 | assert(!empty); 274 | ensureFrontValid(); 275 | 276 | // make sure an error token is the last token in the range 277 | if (_front.kind == JSONTokenKind.error && !_input.empty) 278 | { 279 | // clear the input 280 | _input = InternalInput.init; 281 | assert(_input.empty); 282 | } 283 | 284 | _front.kind = JSONTokenKind.none; 285 | } 286 | 287 | private void ensureFrontValid() 288 | { 289 | assert(!empty, "Reading from an empty JSONLexerRange."); 290 | if (_front.kind == JSONTokenKind.none) 291 | { 292 | readToken(); 293 | assert(_front.kind != JSONTokenKind.none); 294 | 295 | static if (!(options & LexOptions.noThrow)) 296 | enforceJson(_front.kind != JSONTokenKind.error, _error, _loc); 297 | } 298 | } 299 | 300 | private void readToken() 301 | { 302 | assert(!_input.empty, "Reading JSON token from empty input stream."); 303 | 304 | static if (!(options & LexOptions.noTrackLocation)) 305 | _front.location = _loc; 306 | 307 | switch (_input.front) 308 | { 309 | default: setError("Malformed token"); break; 310 | case 'f': _front.boolean = false; skipKeyword("false"); break; 311 | case 't': _front.boolean = true; skipKeyword("true"); break; 312 | case 'n': _front.kind = JSONTokenKind.null_; skipKeyword("null"); break; 313 | case '"': parseString(); break; 314 | case '0': .. case '9': case '-': parseNumber(); break; 315 | case '[': skipChar(); _front.kind = JSONTokenKind.arrayStart; break; 316 | case ']': skipChar(); _front.kind = JSONTokenKind.arrayEnd; break; 317 | case '{': skipChar(); _front.kind = JSONTokenKind.objectStart; break; 318 | case '}': skipChar(); _front.kind = JSONTokenKind.objectEnd; break; 319 | case ':': skipChar(); _front.kind = JSONTokenKind.colon; break; 320 | case ',': skipChar(); _front.kind = JSONTokenKind.comma; break; 321 | 322 | static if (options & LexOptions.specialFloatLiterals) 323 | { 324 | case 'N', 'I': parseNumber(); break; 325 | } 326 | } 327 | 328 | skipWhitespace(); 329 | } 330 | 331 | private void skipChar() 332 | { 333 | _input.popFront(); 334 | static if (!(options & LexOptions.noTrackLocation)) _loc.column++; 335 | } 336 | 337 | private void skipKeyword(string kw) 338 | { 339 | import std.algorithm : skipOver; 340 | if (!_input.skipOver(kw)) setError("Invalid keyord"); 341 | else static if (!(options & LexOptions.noTrackLocation)) _loc.column += kw.length; 342 | } 343 | 344 | private void skipWhitespace() 345 | { 346 | import std.traits; 347 | static if (!(options & LexOptions.noTrackLocation)) 348 | { 349 | while (!_input.empty) 350 | { 351 | switch (_input.front) 352 | { 353 | default: return; 354 | case '\r': // Mac and Windows line breaks 355 | _loc.line++; 356 | _loc.column = 0; 357 | _input.popFront(); 358 | if (!_input.empty && _input.front == '\n') 359 | _input.popFront(); 360 | break; 361 | case '\n': // Linux line breaks 362 | _loc.line++; 363 | _loc.column = 0; 364 | _input.popFront(); 365 | break; 366 | case ' ', '\t': 367 | _loc.column++; 368 | _input.popFront(); 369 | break; 370 | } 371 | } 372 | } 373 | else static if (isDynamicArray!InternalInput && is(Unqual!(ElementType!InternalInput) == ubyte)) 374 | { 375 | () @trusted { 376 | while (true) { 377 | auto idx = skip!(true, '\r', '\n', ' ', '\t')(_input.ptr); 378 | if (idx == 0) break; 379 | _input.popFrontN(idx); 380 | } 381 | } (); 382 | } 383 | else 384 | { 385 | while (!_input.empty) 386 | { 387 | switch (_input.front) 388 | { 389 | default: return; 390 | case '\r', '\n', ' ', '\t': 391 | _input.popFront(); 392 | break; 393 | } 394 | } 395 | } 396 | } 397 | 398 | private void parseString() 399 | { 400 | static if ((is(Input == string) || is(Input == immutable(ubyte)[])) && is(String == string)) // TODO: make this work for other kinds of "String" 401 | { 402 | InternalInput lit; 403 | bool has_escapes = false; 404 | if (skipStringLiteral!(!(options & LexOptions.noTrackLocation))(_input, lit, _error, _loc.column, has_escapes)) 405 | { 406 | auto litstr = cast(string)lit; 407 | static if (!isSomeChar!(typeof(Input.init.front))) { 408 | import std.encoding; 409 | if (!()@trusted{ return isValid(litstr); }()) { 410 | setError("Invalid UTF sequence in string literal."); 411 | return; 412 | } 413 | } 414 | JSONString!String js; 415 | if (has_escapes) js.rawValue = litstr; 416 | else js.value = litstr[1 .. $-1]; 417 | _front.string = js; 418 | } 419 | else _front.kind = JSONTokenKind.error; 420 | } 421 | else 422 | { 423 | bool appender_init = false; 424 | Appender!String dst; 425 | String slice; 426 | 427 | void initAppender() 428 | @safe { 429 | dst = appender!String(); 430 | appender_init = true; 431 | } 432 | 433 | if (unescapeStringLiteral!(!(options & LexOptions.noTrackLocation), isSomeChar!(typeof(Input.init.front)))( 434 | _input, dst, slice, &initAppender, _error, _loc.column 435 | )) 436 | { 437 | if (!appender_init) _front.string = slice; 438 | else _front.string = dst.data; 439 | } 440 | else _front.kind = JSONTokenKind.error; 441 | } 442 | } 443 | 444 | private void parseNumber() 445 | { 446 | import std.algorithm : among; 447 | import std.ascii; 448 | import std.bigint; 449 | import std.math; 450 | import std.string; 451 | import std.traits; 452 | 453 | assert(!_input.empty, "Passed empty range to parseNumber"); 454 | 455 | static if (options & (LexOptions.useBigInt/*|LexOptions.useDecimal*/)) 456 | BigInt int_part = 0; 457 | else 458 | long int_part = 0; 459 | bool neg = false; 460 | 461 | void setInt() 462 | { 463 | if (neg) int_part = -int_part; 464 | static if (options & LexOptions.useBigInt) 465 | { 466 | static if (options & LexOptions.useLong) 467 | { 468 | if (int_part >= long.min && int_part <= long.max) _front.number = int_part.toLong(); 469 | else _front.number = int_part; 470 | } 471 | else _front.number = int_part; 472 | } 473 | //else static if (options & LexOptions.useDecimal) _front.number = Decimal(int_part, 0); 474 | else _front.number = int_part; 475 | } 476 | 477 | 478 | // negative sign 479 | if (_input.front == '-') 480 | { 481 | skipChar(); 482 | neg = true; 483 | } 484 | 485 | // support non-standard float special values 486 | static if (options & LexOptions.specialFloatLiterals) 487 | { 488 | import std.algorithm : skipOver; 489 | if (!_input.empty) { 490 | if (_input.front == 'I') { 491 | if (_input.skipOver("Infinity".representation)) 492 | { 493 | static if (!(options & LexOptions.noTrackLocation)) _loc.column += 8; 494 | _front.number = neg ? -double.infinity : double.infinity; 495 | } 496 | else setError("Invalid number, expected 'Infinity'"); 497 | return; 498 | } 499 | if (!neg && _input.front == 'N') 500 | { 501 | if (_input.skipOver("NaN".representation)) 502 | { 503 | static if (!(options & LexOptions.noTrackLocation)) _loc.column += 3; 504 | _front.number = double.nan; 505 | } 506 | else setError("Invalid number, expected 'NaN'"); 507 | return; 508 | } 509 | } 510 | } 511 | 512 | // integer part of the number 513 | if (_input.empty || !_input.front.isDigit()) 514 | { 515 | setError("Invalid number, expected digit"); 516 | return; 517 | } 518 | 519 | if (_input.front == '0') 520 | { 521 | skipChar(); 522 | if (_input.empty) // return 0 523 | { 524 | setInt(); 525 | return; 526 | } 527 | 528 | if (_input.front.isDigit) 529 | { 530 | setError("Invalid number, 0 must not be followed by another digit"); 531 | return; 532 | } 533 | } 534 | else do 535 | { 536 | int_part = int_part * 10 + (_input.front - '0'); 537 | skipChar(); 538 | if (_input.empty) // return integer 539 | { 540 | setInt(); 541 | return; 542 | } 543 | } 544 | while (isDigit(_input.front)); 545 | 546 | int exponent = 0; 547 | 548 | void setFloat() 549 | { 550 | if (neg) int_part = -int_part; 551 | /*static if (options & LexOptions.useDecimal) _front.number = Decimal(int_part, exponent); 552 | else*/ if (exponent == 0) _front.number = int_part; 553 | else 554 | { 555 | static if (is(typeof(int_part) == BigInt)) 556 | { 557 | import std.conv : to; 558 | _front.number = exp10(exponent) * int_part.toDecimalString.to!double; 559 | } else _front.number = exp10(exponent) * int_part; 560 | } 561 | } 562 | 563 | // post decimal point part 564 | assert(!_input.empty); 565 | if (_input.front == '.') 566 | { 567 | skipChar(); 568 | 569 | if (_input.empty) 570 | { 571 | setError("Missing fractional number part"); 572 | return; 573 | } 574 | 575 | while (true) 576 | { 577 | uint digit = _input.front - '0'; 578 | if (digit > 9) break; 579 | 580 | int_part = int_part * 10 + digit; 581 | exponent--; 582 | skipChar(); 583 | 584 | if (_input.empty) 585 | { 586 | setFloat(); 587 | return; 588 | } 589 | } 590 | 591 | if (exponent == 0) 592 | { 593 | // No digits were read after decimal 594 | setError("Missing fractional number part"); 595 | return; 596 | } 597 | } 598 | 599 | // exponent 600 | assert(!_input.empty); 601 | if (_input.front.among!('e', 'E')) 602 | { 603 | skipChar(); 604 | if (_input.empty) 605 | { 606 | setError("Missing exponent"); 607 | return; 608 | } 609 | 610 | bool negexp = void; 611 | if (_input.front == '-') 612 | { 613 | negexp = true; 614 | skipChar(); 615 | } 616 | else 617 | { 618 | negexp = false; 619 | if (_input.front == '+') skipChar(); 620 | } 621 | 622 | if (_input.empty || !_input.front.isDigit) 623 | { 624 | setError("Missing exponent"); 625 | return; 626 | } 627 | 628 | uint exp = 0; 629 | while (true) 630 | { 631 | exp = exp * 10 + (_input.front - '0'); 632 | skipChar(); 633 | if (_input.empty || !_input.front.isDigit) break; 634 | } 635 | 636 | if (negexp) exponent -= exp; 637 | else exponent += exp; 638 | } 639 | 640 | setFloat(); 641 | } 642 | 643 | private void setError(string err) 644 | { 645 | _front.kind = JSONTokenKind.error; 646 | _error = err; 647 | } 648 | } 649 | 650 | @safe unittest 651 | { 652 | import std.conv; 653 | import std.exception; 654 | import std.string : format, representation; 655 | 656 | static JSONString!string parseStringHelper(R)(ref R input, ref Location loc) 657 | { 658 | auto rng = JSONLexerRange!R(input); 659 | rng.parseString(); 660 | input = cast(R)rng._input; 661 | loc = rng._loc; 662 | return rng._front.string; 663 | } 664 | 665 | void testResult(string str, string expected, string remaining, bool slice_expected = false) 666 | { 667 | { // test with string (possibly sliced result) 668 | Location loc; 669 | string scopy = str; 670 | auto ret = parseStringHelper(scopy, loc); 671 | assert(ret == expected, ret); 672 | assert(scopy == remaining); 673 | auto sval = ret.anyValue; 674 | // string[] must always slice string literals 675 | assert(sval[1] && sval[0].ptr is &str[1] || !sval[1] && sval[0].ptr is &str[0]); 676 | if (slice_expected) assert(&ret[0] is &str[1]); 677 | assert(loc.line == 0); 678 | assert(loc.column == str.length - remaining.length, format("%s col %s", str, loc.column)); 679 | } 680 | 681 | { // test with string representation (possibly sliced result) 682 | Location loc; 683 | immutable(ubyte)[] scopy = str.representation; 684 | auto ret = parseStringHelper(scopy, loc); 685 | assert(ret == expected, ret); 686 | assert(scopy == remaining); 687 | auto sval = ret.anyValue; 688 | // immutable(ubyte)[] must always slice string literals 689 | assert(sval[1] && sval[0].ptr is &str[1] || !sval[1] && sval[0].ptr is &str[0]); 690 | if (slice_expected) assert(&ret[0] is &str[1]); 691 | assert(loc.line == 0); 692 | assert(loc.column == str.length - remaining.length, format("%s col %s", str, loc.column)); 693 | } 694 | 695 | { // test with dstring (fully duplicated result) 696 | Location loc; 697 | dstring scopy = str.to!dstring; 698 | auto ret = parseStringHelper(scopy, loc); 699 | assert(ret == expected); 700 | assert(scopy == remaining.to!dstring); 701 | assert(loc.line == 0); 702 | assert(loc.column == str.to!dstring.length - remaining.to!dstring.length, format("%s col %s", str, loc.column)); 703 | } 704 | } 705 | 706 | testResult(`"test"`, "test", "", true); 707 | testResult(`"test"...`, "test", "...", true); 708 | testResult(`"test\n"`, "test\n", ""); 709 | testResult(`"test\n"...`, "test\n", "..."); 710 | testResult(`"test\""...`, "test\"", "..."); 711 | testResult(`"ä"`, "ä", "", true); 712 | testResult(`"\r\n\\\"\b\f\t\/"`, "\r\n\\\"\b\f\t/", ""); 713 | testResult(`"\u1234"`, "\u1234", ""); 714 | testResult(`"\uD800\udc00"`, "\U00010000", ""); 715 | } 716 | 717 | @safe unittest 718 | { 719 | import std.exception; 720 | 721 | void testFail(string str) 722 | { 723 | Location loc; 724 | auto rng1 = JSONLexerRange!(string, LexOptions.init)(str); 725 | assertThrown(rng1.front); 726 | 727 | auto rng2 = JSONLexerRange!(string, LexOptions.noThrow)(str); 728 | assertNotThrown(rng2.front); 729 | assert(rng2.front.kind == JSONTokenKind.error); 730 | } 731 | 732 | testFail(`"`); // unterminated string 733 | testFail(`"\`); // unterminated string escape sequence 734 | testFail(`"test\"`); // unterminated string 735 | testFail(`"test'`); // unterminated string 736 | testFail("\"test\n\""); // illegal control character 737 | testFail(`"\x"`); // invalid escape sequence 738 | testFail(`"\u123`); // unterminated unicode escape sequence 739 | testFail(`"\u123"`); // too short unicode escape sequence 740 | testFail(`"\u123G"`); // invalid unicode escape sequence 741 | testFail(`"\u123g"`); // invalid unicode escape sequence 742 | testFail(`"\uD800"`); // missing surrogate 743 | testFail(`"\uD800\u"`); // too short second surrogate 744 | testFail(`"\uD800\u1234"`); // invalid surrogate pair 745 | } 746 | 747 | @safe unittest 748 | { 749 | import std.exception; 750 | import std.math : approxEqual, isNaN; 751 | 752 | static double parseNumberHelper(LexOptions options, R)(ref R input, ref Location loc) 753 | { 754 | auto rng = JSONLexerRange!(R, options & ~LexOptions.noTrackLocation)(input); 755 | rng.parseNumber(); 756 | input = cast(R)rng._input; 757 | loc = rng._loc; 758 | assert(rng._front.kind != JSONTokenKind.error, rng._error); 759 | return rng._front.number; 760 | } 761 | 762 | static void test(LexOptions options = LexOptions.init)(string str, double expected, string remainder) 763 | { 764 | import std.conv; 765 | Location loc; 766 | auto strcopy = str; 767 | auto res = parseNumberHelper!options(strcopy, loc); 768 | assert((res.isNaN && expected.isNaN) || approxEqual(res, expected), () @trusted {return res.to!string;}()); 769 | assert(strcopy == remainder); 770 | assert(loc.line == 0); 771 | assert(loc.column == str.length - remainder.length, text(loc.column)); 772 | } 773 | 774 | test("0", 0.0, ""); 775 | test("0 ", 0.0, " "); 776 | test("-0", 0.0, ""); 777 | test("-0 ", 0.0, " "); 778 | test("-0e+10 ", 0.0, " "); 779 | test("123", 123.0, ""); 780 | test("123 ", 123.0, " "); 781 | test("123.0", 123.0, ""); 782 | test("123.0 ", 123.0, " "); 783 | test("123.456", 123.456, ""); 784 | test("123.456 ", 123.456, " "); 785 | test("123.456e1", 1234.56, ""); 786 | test("123.456e1 ", 1234.56, " "); 787 | test("123.456e+1", 1234.56, ""); 788 | test("123.456e+1 ", 1234.56, " "); 789 | test("123.456e-1", 12.3456, ""); 790 | test("123.456e-1 ", 12.3456, " "); 791 | test("123.456e-01", 12.3456, ""); 792 | test("123.456e-01 ", 12.3456, " "); 793 | test("0.123e-12", 0.123e-12, ""); 794 | test("0.123e-12 ", 0.123e-12, " "); 795 | 796 | test!(LexOptions.specialFloatLiterals)("NaN", double.nan, ""); 797 | test!(LexOptions.specialFloatLiterals)("NaN ", double.nan, " "); 798 | test!(LexOptions.specialFloatLiterals)("Infinity", double.infinity, ""); 799 | test!(LexOptions.specialFloatLiterals)("Infinity ", double.infinity, " "); 800 | test!(LexOptions.specialFloatLiterals)("-Infinity", -double.infinity, ""); 801 | test!(LexOptions.specialFloatLiterals)("-Infinity ", -double.infinity, " "); 802 | } 803 | 804 | @safe unittest 805 | { 806 | import std.exception; 807 | 808 | static void testFail(LexOptions options = LexOptions.init)(string str) 809 | { 810 | Location loc; 811 | auto rng1 = JSONLexerRange!(string, options)(str); 812 | assertThrown(rng1.front); 813 | 814 | auto rng2 = JSONLexerRange!(string, options|LexOptions.noThrow)(str); 815 | assertNotThrown(rng2.front); 816 | assert(rng2.front.kind == JSONTokenKind.error); 817 | } 818 | 819 | testFail("+"); 820 | testFail("-"); 821 | testFail("+1"); 822 | testFail("1."); 823 | testFail("1.."); 824 | testFail(".1"); 825 | testFail("01"); 826 | testFail("1e"); 827 | testFail("1e+"); 828 | testFail("1e-"); 829 | testFail("1.e"); 830 | testFail("1.e1"); 831 | testFail("1.e-"); 832 | testFail("1.e-1"); 833 | testFail("1.ee"); 834 | testFail("1.e-e"); 835 | testFail("1.e+e"); 836 | testFail("NaN"); 837 | testFail("Infinity"); 838 | testFail("-Infinity"); 839 | testFail!(LexOptions.specialFloatLiterals)("NaX"); 840 | testFail!(LexOptions.specialFloatLiterals)("InfinitX"); 841 | testFail!(LexOptions.specialFloatLiterals)("-InfinitX"); 842 | } 843 | 844 | @safe unittest 845 | { 846 | auto tokens = lexJSON!(LexOptions.init, char[])(`{"foo": "bar"}`); 847 | assert(tokens.front.kind == JSONTokenKind.objectStart); 848 | tokens.popFront(); 849 | assert(tokens.front.kind == JSONTokenKind.string); 850 | assert(tokens.front.string == "foo"); 851 | tokens.popFront(); 852 | assert(tokens.front.kind == JSONTokenKind.colon); 853 | tokens.popFront(); 854 | assert(tokens.front.kind == JSONTokenKind.string); 855 | assert(tokens.front.string == "bar"); 856 | tokens.popFront(); 857 | assert(tokens.front.kind == JSONTokenKind.objectEnd); 858 | tokens.popFront(); 859 | } 860 | 861 | /** 862 | * A low-level JSON token as returned by $(D JSONLexer). 863 | */ 864 | @safe struct JSONToken(S) 865 | { 866 | import std.algorithm : among; 867 | import std.bigint : BigInt; 868 | 869 | private alias Kind = JSONTokenKind; // compatibility alias 870 | alias String = S; 871 | 872 | private 873 | { 874 | union 875 | { 876 | JSONString!String _string; 877 | bool _boolean; 878 | JSONNumber _number; 879 | } 880 | Kind _kind = Kind.none; 881 | } 882 | 883 | /// The location of the token in the input. 884 | Location location; 885 | 886 | /// Constructs a token from a primitive data value 887 | this(typeof(null)) { _kind = Kind.null_; } 888 | // ditto 889 | this(bool value) @trusted { _kind = Kind.boolean; _boolean = value; } 890 | // ditto 891 | this(JSONNumber value) @trusted { _kind = Kind.number; _number = value; } 892 | // ditto 893 | this(long value) @trusted { _kind = Kind.number; _number = value; } 894 | // ditto 895 | this(double value) @trusted { _kind = Kind.number; _number = value; } 896 | // ditto 897 | this(JSONString!String value) @trusted { _kind = Kind.string; _string = value; } 898 | // ditto 899 | this(String value) @trusted { _kind = Kind.string; _string = value; } 900 | 901 | /** Constructs a token with a specific kind. 902 | * 903 | * Note that only kinds that don't imply additional data are allowed. 904 | */ 905 | this(Kind kind) 906 | in 907 | { 908 | assert(!kind.among!(Kind.string, Kind.boolean, Kind.number)); 909 | } 910 | do 911 | { 912 | _kind = kind; 913 | } 914 | 915 | 916 | ref JSONToken opAssign(ref JSONToken other) nothrow @trusted @nogc 917 | { 918 | _kind = other._kind; 919 | switch (_kind) with (Kind) { 920 | default: break; 921 | case boolean: _boolean = other._boolean; break; 922 | case number: _number = other._number; break; 923 | case string: _string = other._string; break; 924 | } 925 | 926 | this.location = other.location; 927 | return this; 928 | } 929 | 930 | /** 931 | * Gets/sets the kind of the represented token. 932 | * 933 | * Setting the token kind is not allowed for any of the kinds that have 934 | * additional data associated (boolean, number and string). 935 | */ 936 | @property Kind kind() const pure nothrow @nogc { return _kind; } 937 | /// ditto 938 | @property Kind kind(Kind value) nothrow @nogc 939 | in { assert(!value.among!(Kind.boolean, Kind.number, Kind.string)); } 940 | do { return _kind = value; } 941 | 942 | /// Gets/sets the boolean value of the token. 943 | @property bool boolean() const pure nothrow @trusted @nogc 944 | in { assert(_kind == Kind.boolean, "Token is not a boolean."); } 945 | do { return _boolean; } 946 | /// ditto 947 | @property bool boolean(bool value) pure nothrow @nogc 948 | { 949 | _kind = Kind.boolean; 950 | _boolean = value; 951 | return value; 952 | } 953 | 954 | /// Gets/sets the numeric value of the token. 955 | @property JSONNumber number() const pure nothrow @trusted @nogc 956 | in { assert(_kind == Kind.number, "Token is not a number."); } 957 | do { return _number; } 958 | /// ditto 959 | @property JSONNumber number(JSONNumber value) nothrow @nogc 960 | { 961 | _kind = Kind.number; 962 | () @trusted { _number = value; } (); 963 | return value; 964 | } 965 | /// ditto 966 | @property JSONNumber number(long value) nothrow @nogc { return this.number = JSONNumber(value); } 967 | /// ditto 968 | @property JSONNumber number(double value) nothrow @nogc { return this.number = JSONNumber(value); } 969 | /// ditto 970 | @property JSONNumber number(BigInt value) nothrow @nogc { return this.number = JSONNumber(value); } 971 | 972 | /// Gets/sets the string value of the token. 973 | @property const(JSONString!String) string() const pure nothrow @trusted @nogc 974 | in { assert(_kind == Kind.string, "Token is not a string."); } 975 | do { return _kind == Kind.string ? _string : JSONString!String.init; } 976 | /// ditto 977 | @property JSONString!String string(JSONString!String value) pure nothrow @nogc 978 | { 979 | _kind = Kind.string; 980 | () @trusted { _string = value; } (); 981 | return value; 982 | } 983 | /// ditto 984 | @property JSONString!String string(String value) pure nothrow @nogc { return this.string = JSONString!String(value); } 985 | 986 | /** 987 | * Enables equality comparisons. 988 | * 989 | * Note that the location is considered token meta data and thus does not 990 | * affect the comparison. 991 | */ 992 | bool opEquals(const ref JSONToken other) const nothrow @trusted 993 | { 994 | if (this.kind != other.kind) return false; 995 | 996 | switch (this.kind) 997 | { 998 | default: return true; 999 | case Kind.boolean: return this.boolean == other.boolean; 1000 | case Kind.number: return this.number == other.number; 1001 | case Kind.string: return this.string == other.string; 1002 | } 1003 | } 1004 | /// ditto 1005 | bool opEquals(JSONToken other) const nothrow { return opEquals(other); } 1006 | 1007 | /** 1008 | * Enables usage of $(D JSONToken) as an associative array key. 1009 | */ 1010 | size_t toHash() const @trusted nothrow 1011 | { 1012 | hash_t ret = 3781249591u + cast(uint)_kind * 2721371; 1013 | 1014 | switch (_kind) 1015 | { 1016 | default: return ret; 1017 | case Kind.boolean: return ret + _boolean; 1018 | case Kind.number: return ret + typeid(double).getHash(&_number); 1019 | case Kind.string: return ret + typeid(.string).getHash(&_string); 1020 | } 1021 | } 1022 | 1023 | /** 1024 | * Converts the token to a string representation. 1025 | * 1026 | * Note that this representation is NOT the JSON representation, but rather 1027 | * a representation suitable for printing out a token including its 1028 | * location. 1029 | */ 1030 | .string toString() const @trusted 1031 | { 1032 | import std.string; 1033 | switch (this.kind) 1034 | { 1035 | default: return format("[%s %s]", location, this.kind); 1036 | case Kind.boolean: return format("[%s %s]", location, this.boolean); 1037 | case Kind.number: return format("[%s %s]", location, this.number); 1038 | case Kind.string: return format("[%s \"%s\"]", location, this.string); 1039 | } 1040 | } 1041 | } 1042 | 1043 | @safe unittest 1044 | { 1045 | JSONToken!string tok; 1046 | 1047 | assert((tok.boolean = true) == true); 1048 | assert(tok.kind == JSONTokenKind.boolean); 1049 | assert(tok.boolean == true); 1050 | 1051 | assert((tok.number = 1.0) == 1.0); 1052 | assert(tok.kind == JSONTokenKind.number); 1053 | assert(tok.number == 1.0); 1054 | 1055 | assert((tok.string = "test") == "test"); 1056 | assert(tok.kind == JSONTokenKind.string); 1057 | assert(tok.string == "test"); 1058 | 1059 | assert((tok.kind = JSONTokenKind.none) == JSONTokenKind.none); 1060 | assert(tok.kind == JSONTokenKind.none); 1061 | assert((tok.kind = JSONTokenKind.error) == JSONTokenKind.error); 1062 | assert(tok.kind == JSONTokenKind.error); 1063 | assert((tok.kind = JSONTokenKind.null_) == JSONTokenKind.null_); 1064 | assert(tok.kind == JSONTokenKind.null_); 1065 | assert((tok.kind = JSONTokenKind.objectStart) == JSONTokenKind.objectStart); 1066 | assert(tok.kind == JSONTokenKind.objectStart); 1067 | assert((tok.kind = JSONTokenKind.objectEnd) == JSONTokenKind.objectEnd); 1068 | assert(tok.kind == JSONTokenKind.objectEnd); 1069 | assert((tok.kind = JSONTokenKind.arrayStart) == JSONTokenKind.arrayStart); 1070 | assert(tok.kind == JSONTokenKind.arrayStart); 1071 | assert((tok.kind = JSONTokenKind.arrayEnd) == JSONTokenKind.arrayEnd); 1072 | assert(tok.kind == JSONTokenKind.arrayEnd); 1073 | assert((tok.kind = JSONTokenKind.colon) == JSONTokenKind.colon); 1074 | assert(tok.kind == JSONTokenKind.colon); 1075 | assert((tok.kind = JSONTokenKind.comma) == JSONTokenKind.comma); 1076 | assert(tok.kind == JSONTokenKind.comma); 1077 | } 1078 | 1079 | 1080 | /** 1081 | * Identifies the kind of a JSON token. 1082 | */ 1083 | enum JSONTokenKind 1084 | { 1085 | none, /// Used internally, never returned from the lexer 1086 | error, /// Malformed token 1087 | null_, /// The "null" token 1088 | boolean, /// "true" or "false" token 1089 | number, /// Numeric token 1090 | string, /// String token, stored in escaped form 1091 | objectStart, /// The "{" token 1092 | objectEnd, /// The "}" token 1093 | arrayStart, /// The "[" token 1094 | arrayEnd, /// The "]" token 1095 | colon, /// The ":" token 1096 | comma /// The "," token 1097 | } 1098 | 1099 | 1100 | /** 1101 | * Represents a JSON string literal with lazy (un)escaping. 1102 | */ 1103 | @safe struct JSONString(String) { 1104 | import std.typecons : Tuple, tuple; 1105 | 1106 | private { 1107 | String _value; 1108 | String _rawValue; 1109 | } 1110 | 1111 | nothrow: 1112 | 1113 | /** 1114 | * Constructs a JSONString from the given string value (unescaped). 1115 | */ 1116 | this(String value) pure nothrow @nogc 1117 | { 1118 | _value = value; 1119 | } 1120 | 1121 | /** 1122 | * The decoded (unescaped) string value. 1123 | */ 1124 | @property String value() 1125 | { 1126 | if (!_value.length && _rawValue.length) { 1127 | auto res = unescapeStringLiteral(_rawValue, _value); 1128 | assert(res, "Invalid raw string literal passed to JSONString: "~_rawValue); 1129 | } 1130 | return _value; 1131 | } 1132 | /// ditto 1133 | @property const(String) value() const 1134 | { 1135 | if (!_value.length && _rawValue.length) { 1136 | String unescaped; 1137 | auto res = unescapeStringLiteral(_rawValue, unescaped); 1138 | assert(res, "Invalid raw string literal passed to JSONString: "~_rawValue); 1139 | return unescaped; 1140 | } 1141 | return _value; 1142 | } 1143 | /// ditto 1144 | @property String value(String val) nothrow @nogc 1145 | { 1146 | _rawValue = null; 1147 | return _value = val; 1148 | } 1149 | 1150 | /** 1151 | * The raw (escaped) string literal, including the enclosing quotation marks. 1152 | */ 1153 | @property String rawValue() 1154 | { 1155 | if (!_rawValue.length && _value.length) 1156 | _rawValue = escapeStringLiteral(_value); 1157 | return _rawValue; 1158 | } 1159 | /// ditto 1160 | @property String rawValue(String val) nothrow @nogc 1161 | { 1162 | import std.algorithm : canFind; 1163 | import std.string : representation; 1164 | assert(isValidStringLiteral(val), "Invalid raw string literal"); 1165 | _rawValue = val; 1166 | _value = null; 1167 | return val; 1168 | } 1169 | 1170 | /** 1171 | * Returns the string value in the form that is available without allocating memory. 1172 | * 1173 | * Returns: 1174 | * A tuple of the string and a boolean value is returned. The boolean is 1175 | * set to `true` if the returned string is in decoded form. `false` is 1176 | * returned otherwise. 1177 | */ 1178 | @property Tuple!(const(String), bool) anyValue() const pure @nogc 1179 | { 1180 | alias T = Tuple!(const(String), bool); // work around "Cannot convert Tuple!(string, bool) to Tuple!(const(string), bool)" error when using tuple() 1181 | return !_rawValue.length ? T(_value, true) : T(_rawValue, false); 1182 | } 1183 | 1184 | alias value this; 1185 | 1186 | /// Support equality comparisons 1187 | bool opEquals(in JSONString other) nothrow { return value == other.value; } 1188 | /// ditto 1189 | bool opEquals(in JSONString other) const nothrow { return this.value == other.value; } 1190 | /// ditto 1191 | bool opEquals(in String other) nothrow { return this.value == other; } 1192 | /// ditto 1193 | bool opEquals(in String other) const nothrow { return this.value == other; } 1194 | 1195 | /// Support relational comparisons 1196 | int opCmp(JSONString other) nothrow @trusted { import std.algorithm; return cmp(this.value, other.value); } 1197 | 1198 | /// Support use as hash key 1199 | size_t toHash() const nothrow @trusted { auto val = this.value; return typeid(string).getHash(&val); } 1200 | } 1201 | 1202 | @safe unittest { 1203 | JSONString!string s = "test"; 1204 | assert(s == "test"); 1205 | assert(s.value == "test"); 1206 | assert(s.rawValue == `"test"`); 1207 | 1208 | JSONString!string t; 1209 | auto h = `"hello"`; 1210 | s.rawValue = h; 1211 | t = s; assert(s == t); 1212 | assert(s.rawValue == h); 1213 | assert(s.value == "hello"); 1214 | t = s; assert(s == t); 1215 | assert(&s.rawValue[0] is &h[0]); 1216 | assert(&s.value[0] is &h[1]); 1217 | 1218 | auto w = `"world\t!"`; 1219 | s.rawValue = w; 1220 | t = s; assert(s == t); 1221 | assert(s.rawValue == w); 1222 | assert(s.value == "world\t!"); 1223 | t = s; assert(s == t); 1224 | assert(&s.rawValue[0] is &w[0]); 1225 | assert(&s.value[0] !is &h[1]); 1226 | 1227 | JSONString!(char[]) u = "test".dup; 1228 | assert(u == "test"); 1229 | assert(u.value == "test"); 1230 | assert(u.rawValue == `"test"`); 1231 | } 1232 | 1233 | 1234 | /** 1235 | * Represents a JSON number literal with lazy conversion. 1236 | */ 1237 | @safe struct JSONNumber { 1238 | import std.bigint; 1239 | 1240 | enum Type { 1241 | double_, 1242 | long_, 1243 | bigInt/*, 1244 | decimal*/ 1245 | } 1246 | 1247 | private struct Decimal { 1248 | BigInt integer; 1249 | int exponent; 1250 | 1251 | void opAssign(Decimal other) nothrow @nogc 1252 | { 1253 | integer = other.integer; 1254 | exponent = other.exponent; 1255 | } 1256 | } 1257 | 1258 | private { 1259 | union { 1260 | double _double; 1261 | long _long; 1262 | Decimal _decimal; 1263 | } 1264 | Type _type = Type.long_; 1265 | } 1266 | 1267 | /** 1268 | * Constructs a $(D JSONNumber) from a raw number. 1269 | */ 1270 | this(double value) nothrow @nogc { this.doubleValue = value; } 1271 | /// ditto 1272 | this(long value) nothrow @nogc { this.longValue = value; } 1273 | /// ditto 1274 | this(BigInt value) nothrow @nogc { this.bigIntValue = value; } 1275 | // ditto 1276 | //this(Decimal value) nothrow { this.decimalValue = value; } 1277 | 1278 | /** 1279 | * The native type of the stored number. 1280 | */ 1281 | @property Type type() const nothrow @nogc { return _type; } 1282 | 1283 | /** 1284 | * Returns the number as a $(D double) value. 1285 | * 1286 | * Regardless of the current type of this number, this property will always 1287 | * yield a value converted to $(D double). Setting this property will 1288 | * automatically update the number type to $(D Type.double_). 1289 | */ 1290 | @property double doubleValue() const nothrow @trusted @nogc 1291 | { 1292 | final switch (_type) 1293 | { 1294 | case Type.double_: return _double; 1295 | case Type.long_: return cast(double)_long; 1296 | case Type.bigInt: 1297 | { 1298 | scope (failure) assert(false); 1299 | // FIXME: directly convert to double 1300 | return cast(double)_decimal.integer.toLong(); 1301 | } 1302 | //case Type.decimal: try return cast(double)_decimal.integer.toLong() * 10.0 ^^ _decimal.exponent; catch(Exception) assert(false); // FIXME: directly convert to double 1303 | } 1304 | } 1305 | 1306 | /// ditto 1307 | @property double doubleValue(double value) nothrow @nogc 1308 | { 1309 | _type = Type.double_; 1310 | return _double = value; 1311 | } 1312 | 1313 | /** 1314 | * Returns the number as a $(D long) value. 1315 | * 1316 | * Regardless of the current type of this number, this property will always 1317 | * yield a value converted to $(D long). Setting this property will 1318 | * automatically update the number type to $(D Type.long_). 1319 | */ 1320 | @property long longValue() const nothrow @trusted @nogc 1321 | { 1322 | import std.math; 1323 | 1324 | final switch (_type) 1325 | { 1326 | case Type.double_: return rndtol(_double); 1327 | case Type.long_: return _long; 1328 | case Type.bigInt: 1329 | { 1330 | scope (failure) assert(false); 1331 | return _decimal.integer.toLong(); 1332 | } 1333 | /* 1334 | case Type.decimal: 1335 | { 1336 | scope (failure) assert(0); 1337 | if (_decimal.exponent == 0) return _decimal.integer.toLong(); 1338 | else if (_decimal.exponent > 0) return (_decimal.integer * BigInt(10) ^^ _decimal.exponent).toLong(); 1339 | else return (_decimal.integer / BigInt(10) ^^ -_decimal.exponent).toLong(); 1340 | } 1341 | */ 1342 | } 1343 | } 1344 | 1345 | /// ditto 1346 | @property long longValue(long value) nothrow @nogc 1347 | { 1348 | _type = Type.long_; 1349 | return _long = value; 1350 | } 1351 | 1352 | /** 1353 | * Returns the number as a $(D BigInt) value. 1354 | * 1355 | * Regardless of the current type of this number, this property will always 1356 | * yield a value converted to $(D BigInt). Setting this property will 1357 | * automatically update the number type to $(D Type.bigInt). 1358 | */ 1359 | @property BigInt bigIntValue() const nothrow @trusted 1360 | { 1361 | import std.math; 1362 | 1363 | final switch (_type) 1364 | { 1365 | case Type.double_: return BigInt(rndtol(_double)); // FIXME: convert to string and then to bigint 1366 | case Type.long_: return BigInt(_long); 1367 | case Type.bigInt: return _decimal.integer; 1368 | /*case Type.decimal: 1369 | try 1370 | { 1371 | if (_decimal.exponent == 0) return _decimal.integer; 1372 | else if (_decimal.exponent > 0) return _decimal.integer * BigInt(10) ^^ _decimal.exponent; 1373 | else return _decimal.integer / BigInt(10) ^^ -_decimal.exponent; 1374 | } 1375 | catch (Exception) assert(false);*/ 1376 | } 1377 | } 1378 | /// ditto 1379 | @property BigInt bigIntValue(BigInt value) nothrow @trusted @nogc 1380 | { 1381 | _type = Type.bigInt; 1382 | _decimal.exponent = 0; 1383 | return _decimal.integer = value; 1384 | } 1385 | 1386 | /+/** 1387 | * Returns the number as a $(D Decimal) value. 1388 | * 1389 | * Regardless of the current type of this number, this property will always 1390 | * yield a value converted to $(D Decimal). Setting this property will 1391 | * automatically update the number type to $(D Type.decimal). 1392 | */ 1393 | @property Decimal decimalValue() const nothrow @trusted 1394 | { 1395 | import std.bitmanip; 1396 | import std.math; 1397 | 1398 | final switch (_type) 1399 | { 1400 | case Type.double_: 1401 | Decimal ret; 1402 | assert(false, "TODO"); 1403 | case Type.long_: return Decimal(BigInt(_long), 0); 1404 | case Type.bigInt: return Decimal(_decimal.integer, 0); 1405 | case Type.decimal: return _decimal; 1406 | } 1407 | } 1408 | /// ditto 1409 | @property Decimal decimalValue(Decimal value) nothrow @trusted 1410 | { 1411 | _type = Type.decimal; 1412 | try return _decimal = value; 1413 | catch (Exception) assert(false); 1414 | }+/ 1415 | 1416 | /// Makes a JSONNumber behave like a $(D double) by default. 1417 | alias doubleValue this; 1418 | 1419 | /** 1420 | * Support assignment of numbers. 1421 | */ 1422 | void opAssign(JSONNumber other) nothrow @trusted @nogc 1423 | { 1424 | _type = other._type; 1425 | final switch (_type) { 1426 | case Type.double_: _double = other._double; break; 1427 | case Type.long_: _long = other._long; break; 1428 | case Type.bigInt/*, Type.decimal*/: 1429 | { 1430 | scope (failure) assert(false); 1431 | _decimal = other._decimal; 1432 | } 1433 | break; 1434 | } 1435 | } 1436 | /// ditto 1437 | void opAssign(double value) nothrow @nogc { this.doubleValue = value; } 1438 | /// ditto 1439 | void opAssign(long value) nothrow @nogc { this.longValue = value; } 1440 | /// ditto 1441 | void opAssign(BigInt value) nothrow @nogc { this.bigIntValue = value; } 1442 | // ditto 1443 | //void opAssign(Decimal value) { this.decimalValue = value; } 1444 | 1445 | /// Support equality comparisons 1446 | bool opEquals(T)(T other) const nothrow @nogc 1447 | { 1448 | static if (is(T == JSONNumber)) 1449 | { 1450 | if(_type == Type.long_ && other._type == Type.long_) 1451 | return _long == other._long; 1452 | return doubleValue == other.doubleValue; 1453 | } 1454 | else static if (is(T : double)) return doubleValue == other; 1455 | else static if (is(T : long)) return _type == Type.long_ ? _long == other : doubleValue == other; 1456 | else static assert(false, "Unsupported type for comparison: "~T.stringof); 1457 | } 1458 | 1459 | /// Support relational comparisons 1460 | int opCmp(T)(T other) const nothrow @nogc 1461 | { 1462 | static if (is(T == JSONNumber)) 1463 | { 1464 | if(other._type == Type.long_) 1465 | return opCmp(other._long); 1466 | return opCmp(other.doubleValue); 1467 | } 1468 | else static if (is(T : double)) 1469 | { 1470 | auto a = doubleValue; 1471 | auto b = other; 1472 | return a < b ? -1 : a > b ? 1 : 0; 1473 | } 1474 | else static if (is(T : long)) 1475 | { 1476 | if(_type == Type.long_) 1477 | { 1478 | auto a = _long; 1479 | auto b = other; 1480 | return a < b ? -1 : a > b ? 1 : 0; 1481 | } 1482 | return opCmp(cast(double)other); 1483 | } 1484 | else static assert(false, "Unsupported type for comparison: "~T.stringof); 1485 | } 1486 | 1487 | /// Support use as hash key 1488 | size_t toHash() const nothrow @trusted 1489 | { 1490 | auto val = this.doubleValue; 1491 | return typeid(double).getHash(&val); 1492 | } 1493 | } 1494 | 1495 | unittest 1496 | { 1497 | auto j = lexJSON!(LexOptions.init | LexOptions.useLong)(`-3150433919248130042`); 1498 | long value = j.front.number.longValue; 1499 | assert(value == -3150433919248130042L); 1500 | } 1501 | 1502 | @safe unittest // assignment operator 1503 | { 1504 | import std.bigint; 1505 | 1506 | JSONNumber num, num2; 1507 | 1508 | num = 1.0; 1509 | assert(num.type == JSONNumber.Type.double_); 1510 | assert(num == 1.0); 1511 | num2 = num; 1512 | assert(num2.type == JSONNumber.Type.double_); 1513 | assert(num2 == 1.0); 1514 | 1515 | num = 1L; 1516 | assert(num.type == JSONNumber.Type.long_); 1517 | assert(num.longValue == 1); 1518 | num2 = num; 1519 | assert(num2.type == JSONNumber.Type.long_); 1520 | assert(num2.longValue == 1); 1521 | 1522 | num = BigInt(1); 1523 | assert(num.type == JSONNumber.Type.bigInt); 1524 | assert(num.bigIntValue == 1); 1525 | num2 = num; 1526 | assert(num2.type == JSONNumber.Type.bigInt); 1527 | assert(num2.bigIntValue == 1); 1528 | 1529 | /*num = JSONNumber.Decimal(BigInt(1), 0); 1530 | assert(num.type == JSONNumber.Type.decimal); 1531 | assert(num.decimalValue == JSONNumber.Decimal(BigInt(1), 0)); 1532 | num2 = num; 1533 | assert(num2.type == JSONNumber.Type.decimal); 1534 | assert(num2.decimalValue == JSONNumber.Decimal(BigInt(1), 0));*/ 1535 | } 1536 | 1537 | @safe unittest // property access 1538 | { 1539 | import std.bigint; 1540 | 1541 | JSONNumber num; 1542 | 1543 | num.longValue = 2; 1544 | assert(num.type == JSONNumber.Type.long_); 1545 | assert(num.longValue == 2); 1546 | assert(num.doubleValue == 2.0); 1547 | assert(num.bigIntValue == 2); 1548 | //assert(num.decimalValue.integer == 2 && num.decimalValue.exponent == 0); 1549 | 1550 | num.doubleValue = 2.0; 1551 | assert(num.type == JSONNumber.Type.double_); 1552 | assert(num.longValue == 2); 1553 | assert(num.doubleValue == 2.0); 1554 | assert(num.bigIntValue == 2); 1555 | //assert(num.decimalValue.integer == 2 * 10 ^^ -num.decimalValue.exponent); 1556 | 1557 | num.bigIntValue = BigInt(2); 1558 | assert(num.type == JSONNumber.Type.bigInt); 1559 | assert(num.longValue == 2); 1560 | assert(num.doubleValue == 2.0); 1561 | assert(num.bigIntValue == 2); 1562 | //assert(num.decimalValue.integer == 2 && num.decimalValue.exponent == 0); 1563 | 1564 | /*num.decimalValue = JSONNumber.Decimal(BigInt(2), 0); 1565 | assert(num.type == JSONNumber.Type.decimal); 1566 | assert(num.longValue == 2); 1567 | assert(num.doubleValue == 2.0); 1568 | assert(num.bigIntValue == 2); 1569 | assert(num.decimalValue.integer == 2 && num.decimalValue.exponent == 0);*/ 1570 | } 1571 | 1572 | @safe unittest // negative numbers 1573 | { 1574 | import std.bigint; 1575 | 1576 | JSONNumber num; 1577 | 1578 | num.longValue = -2; 1579 | assert(num.type == JSONNumber.Type.long_); 1580 | assert(num.longValue == -2); 1581 | assert(num.doubleValue == -2.0); 1582 | assert(num.bigIntValue == -2); 1583 | //assert(num.decimalValue.integer == -2 && num.decimalValue.exponent == 0); 1584 | 1585 | num.doubleValue = -2.0; 1586 | assert(num.type == JSONNumber.Type.double_); 1587 | assert(num.longValue == -2); 1588 | assert(num.doubleValue == -2.0); 1589 | assert(num.bigIntValue == -2); 1590 | //assert(num.decimalValue.integer == -2 && num.decimalValue.exponent == 0); 1591 | 1592 | num.bigIntValue = BigInt(-2); 1593 | assert(num.type == JSONNumber.Type.bigInt); 1594 | assert(num.longValue == -2); 1595 | assert(num.doubleValue == -2.0); 1596 | assert(num.bigIntValue == -2); 1597 | //assert(num.decimalValue.integer == -2 && num.decimalValue.exponent == 0); 1598 | 1599 | /*num.decimalValue = JSONNumber.Decimal(BigInt(-2), 0); 1600 | assert(num.type == JSONNumber.Type.decimal); 1601 | assert(num.longValue == -2); 1602 | assert(num.doubleValue == -2.0); 1603 | assert(num.bigIntValue == -2); 1604 | assert(num.decimalValue.integer == -2 && num.decimalValue.exponent == 0);*/ 1605 | } 1606 | 1607 | 1608 | /** 1609 | * Flags for configuring the JSON lexer. 1610 | * 1611 | * These flags can be combined using a bitwise or operation. 1612 | */ 1613 | enum LexOptions { 1614 | init = 0, /// Default options - track token location and only use double to represent numbers 1615 | noTrackLocation = 1<<0, /// Counts lines and columns while lexing the source 1616 | noThrow = 1<<1, /// Uses JSONToken.Kind.error instead of throwing exceptions 1617 | useLong = 1<<2, /// Use long to represent integers 1618 | useBigInt = 1<<3, /// Use BigInt to represent integers (if larger than long or useLong is not given) 1619 | //useDecimal = 1<<4, /// Use Decimal to represent floating point numbers 1620 | specialFloatLiterals = 1<<5, /// Support "NaN", "Infinite" and "-Infinite" as valid number literals 1621 | } 1622 | 1623 | 1624 | // returns true for success 1625 | package bool unescapeStringLiteral(bool track_location, bool skip_utf_validation, Input, Output, String, OutputInitFunc)( 1626 | ref Input input, // input range, string and immutable(ubyte)[] can be sliced 1627 | ref Output output, // uninitialized output range 1628 | ref String sliced_result, // target for possible result slice 1629 | scope OutputInitFunc output_init, // delegate that is called before writing to output 1630 | ref string error, // target for error message 1631 | ref size_t column) // counter to use for tracking the current column 1632 | { 1633 | static if (typeof(Input.init.front).sizeof > 1) 1634 | alias CharType = dchar; 1635 | else 1636 | alias CharType = char; 1637 | 1638 | import std.algorithm : skipOver; 1639 | import std.array; 1640 | import std.string : representation; 1641 | 1642 | if (input.empty || input.front != '"') 1643 | { 1644 | error = "String literal must start with double quotation mark"; 1645 | return false; 1646 | } 1647 | 1648 | input.popFront(); 1649 | static if (track_location) column++; 1650 | 1651 | // try the fast slice based route first 1652 | static if ((is(Input == string) || is(Input == immutable(ubyte)[])) && is(String == string)) // TODO: make this work for other kinds of "String" 1653 | { 1654 | auto orig = input; 1655 | size_t idx = 0; 1656 | while (true) 1657 | { 1658 | if (idx >= input.length) 1659 | { 1660 | error = "Unterminated string literal"; 1661 | return false; 1662 | } 1663 | 1664 | // return a slice for simple strings 1665 | if (input[idx] == '"') 1666 | { 1667 | input = input[idx+1 .. $]; 1668 | static if (track_location) column += idx+1; 1669 | sliced_result = cast(string)orig[0 .. idx]; 1670 | 1671 | static if (!skip_utf_validation) 1672 | { 1673 | import std.encoding; 1674 | if (!isValid(sliced_result)) 1675 | { 1676 | error = "Invalid UTF sequence in string literal"; 1677 | return false; 1678 | } 1679 | } 1680 | 1681 | return true; 1682 | } 1683 | 1684 | // fall back to full decoding when an escape sequence is encountered 1685 | if (input[idx] == '\\') 1686 | { 1687 | output_init(); 1688 | static if (!skip_utf_validation) 1689 | { 1690 | if (!isValid(input[0 .. idx])) 1691 | { 1692 | error = "Invalid UTF sequence in string literal"; 1693 | return false; 1694 | } 1695 | } 1696 | output.put(cast(string)input[0 .. idx]); 1697 | input = input[idx .. $]; 1698 | static if (track_location) column += idx; 1699 | break; 1700 | } 1701 | 1702 | // Make sure that no illegal characters are present 1703 | if (input[idx] < 0x20) 1704 | { 1705 | error = "Control chararacter found in string literal"; 1706 | return false; 1707 | } 1708 | idx++; 1709 | } 1710 | } else output_init(); 1711 | 1712 | // perform full decoding 1713 | while (true) 1714 | { 1715 | if (input.empty) 1716 | { 1717 | error = "Unterminated string literal"; 1718 | return false; 1719 | } 1720 | 1721 | static if (!skip_utf_validation) 1722 | { 1723 | import std.utf; 1724 | dchar ch; 1725 | size_t numcu; 1726 | auto chrange = castRange!CharType(input); 1727 | try ch = ()@trusted{ return decodeFront(chrange); }(); 1728 | catch (UTFException) 1729 | { 1730 | error = "Invalid UTF sequence in string literal"; 1731 | return false; 1732 | } 1733 | if (!isValidDchar(ch)) 1734 | { 1735 | error = "Invalid Unicode character in string literal"; 1736 | return false; 1737 | } 1738 | static if (track_location) column += numcu; 1739 | } 1740 | else 1741 | { 1742 | auto ch = input.front; 1743 | input.popFront(); 1744 | static if (track_location) column++; 1745 | } 1746 | 1747 | switch (ch) 1748 | { 1749 | default: 1750 | output.put(cast(CharType)ch); 1751 | break; 1752 | case 0x00: .. case 0x19: 1753 | error = "Illegal control character in string literal"; 1754 | return false; 1755 | case '"': return true; 1756 | case '\\': 1757 | if (input.empty) 1758 | { 1759 | error = "Unterminated string escape sequence."; 1760 | return false; 1761 | } 1762 | 1763 | auto ech = input.front; 1764 | input.popFront(); 1765 | static if (track_location) column++; 1766 | 1767 | switch (ech) 1768 | { 1769 | default: 1770 | error = "Invalid string escape sequence."; 1771 | return false; 1772 | case '"': output.put('\"'); break; 1773 | case '\\': output.put('\\'); break; 1774 | case '/': output.put('/'); break; 1775 | case 'b': output.put('\b'); break; 1776 | case 'f': output.put('\f'); break; 1777 | case 'n': output.put('\n'); break; 1778 | case 'r': output.put('\r'); break; 1779 | case 't': output.put('\t'); break; 1780 | case 'u': // \uXXXX 1781 | dchar uch = decodeUTF16CP(input, error); 1782 | if (uch == dchar.max) return false; 1783 | static if (track_location) column += 4; 1784 | 1785 | // detect UTF-16 surrogate pairs 1786 | if (0xD800 <= uch && uch <= 0xDBFF) 1787 | { 1788 | static if (track_location) column += 6; 1789 | 1790 | if (!input.skipOver("\\u".representation)) 1791 | { 1792 | error = "Missing second UTF-16 surrogate"; 1793 | return false; 1794 | } 1795 | 1796 | auto uch2 = decodeUTF16CP(input, error); 1797 | if (uch2 == dchar.max) return false; 1798 | 1799 | if (0xDC00 > uch2 || uch2 > 0xDFFF) 1800 | { 1801 | error = "Invalid UTF-16 surrogate sequence"; 1802 | return false; 1803 | } 1804 | 1805 | // combine to a valid UCS-4 character 1806 | uch = ((uch - 0xD800) << 10) + (uch2 - 0xDC00) + 0x10000; 1807 | } 1808 | 1809 | output.put(uch); 1810 | break; 1811 | } 1812 | break; 1813 | } 1814 | } 1815 | } 1816 | 1817 | package bool unescapeStringLiteral(String)(in String str_lit, ref String dst) 1818 | nothrow { 1819 | import std.string; 1820 | 1821 | bool appender_init = false; 1822 | Appender!String app; 1823 | String slice; 1824 | string error; 1825 | size_t col; 1826 | 1827 | void initAppender() @safe nothrow { app = appender!String(); appender_init = true; } 1828 | 1829 | auto rep = str_lit.representation; 1830 | { 1831 | // Appender.put and skipOver are not nothrow 1832 | scope (failure) assert(false); 1833 | if (!unescapeStringLiteral!(false, true)(rep, app, slice, &initAppender, error, col)) 1834 | return false; 1835 | } 1836 | 1837 | dst = appender_init ? app.data : slice; 1838 | return true; 1839 | } 1840 | 1841 | package bool isValidStringLiteral(String)(String str) 1842 | nothrow @nogc @safe { 1843 | import std.range : NullSink; 1844 | import std.string : representation; 1845 | 1846 | auto rep = str.representation; 1847 | auto nullSink = NullSink(); 1848 | string slice, error; 1849 | size_t col; 1850 | 1851 | scope (failure) assert(false); 1852 | return unescapeStringLiteral!(false, true)(rep, nullSink, slice, {}, error, col); 1853 | } 1854 | 1855 | 1856 | package bool skipStringLiteral(bool track_location = true, Array)( 1857 | ref Array input, 1858 | ref Array destination, 1859 | ref string error, // target for error message 1860 | ref size_t column, // counter to use for tracking the current column 1861 | ref bool has_escapes 1862 | ) 1863 | { 1864 | import std.algorithm : skipOver; 1865 | import std.array; 1866 | import std.string : representation; 1867 | 1868 | if (input.empty || input.front != '"') 1869 | { 1870 | error = "String literal must start with double quotation mark"; 1871 | return false; 1872 | } 1873 | 1874 | destination = input; 1875 | 1876 | input.popFront(); 1877 | 1878 | while (true) 1879 | { 1880 | if (input.empty) 1881 | { 1882 | error = "Unterminated string literal"; 1883 | return false; 1884 | } 1885 | 1886 | auto ch = input.front; 1887 | input.popFront(); 1888 | 1889 | static assert(typeof(ch).min == 0); 1890 | 1891 | if (ch <= 0x19) { 1892 | error = "Illegal control character in string literal"; 1893 | return false; 1894 | } 1895 | 1896 | if (ch == '"') { 1897 | size_t len = destination.length - input.length; 1898 | static if (track_location) column += len; 1899 | destination = destination[0 .. len]; 1900 | return true; 1901 | } 1902 | 1903 | if (ch == '\\') { 1904 | has_escapes = true; 1905 | 1906 | if (input.empty) 1907 | { 1908 | error = "Unterminated string escape sequence."; 1909 | return false; 1910 | } 1911 | 1912 | auto ech = input.front; 1913 | input.popFront(); 1914 | 1915 | switch (ech) 1916 | { 1917 | default: 1918 | error = "Invalid string escape sequence."; 1919 | return false; 1920 | case '"', '\\', '/', 'b', 'f', 'n', 'r', 't': break; 1921 | case 'u': // \uXXXX 1922 | dchar uch = decodeUTF16CP(input, error); 1923 | if (uch == dchar.max) return false; 1924 | 1925 | // detect UTF-16 surrogate pairs 1926 | if (0xD800 <= uch && uch <= 0xDBFF) 1927 | { 1928 | if (!input.skipOver("\\u".representation)) 1929 | { 1930 | error = "Missing second UTF-16 surrogate"; 1931 | return false; 1932 | } 1933 | 1934 | auto uch2 = decodeUTF16CP(input, error); 1935 | if (uch2 == dchar.max) return false; 1936 | 1937 | if (0xDC00 > uch2 || uch2 > 0xDFFF) 1938 | { 1939 | error = "Invalid UTF-16 surrogate sequence"; 1940 | return false; 1941 | } 1942 | } 1943 | break; 1944 | } 1945 | } 1946 | } 1947 | } 1948 | 1949 | 1950 | package void escapeStringLiteral(bool use_surrogates = false, Input, Output)( 1951 | ref Input input, // input range containing the string 1952 | ref Output output) // output range to hold the escaped result 1953 | { 1954 | import std.format; 1955 | import std.utf : decode; 1956 | 1957 | output.put('"'); 1958 | 1959 | while (!input.empty) 1960 | { 1961 | immutable ch = input.front; 1962 | input.popFront(); 1963 | 1964 | switch (ch) 1965 | { 1966 | case '\\': output.put(`\\`); break; 1967 | case '\b': output.put(`\b`); break; 1968 | case '\f': output.put(`\f`); break; 1969 | case '\r': output.put(`\r`); break; 1970 | case '\n': output.put(`\n`); break; 1971 | case '\t': output.put(`\t`); break; 1972 | case '\"': output.put(`\"`); break; 1973 | default: 1974 | static if (use_surrogates) 1975 | { 1976 | if (ch >= 0x20 && ch < 0x80) 1977 | { 1978 | output.put(ch); 1979 | break; 1980 | } 1981 | 1982 | dchar cp = decode(s, pos); 1983 | pos--; // account for the next loop increment 1984 | 1985 | // encode as one or two UTF-16 code points 1986 | if (cp < 0x10000) 1987 | { // in BMP -> 1 CP 1988 | formattedWrite(output, "\\u%04X", cp); 1989 | } 1990 | else 1991 | { // not in BMP -> surrogate pair 1992 | int first, last; 1993 | cp -= 0x10000; 1994 | first = 0xD800 | ((cp & 0xffc00) >> 10); 1995 | last = 0xDC00 | (cp & 0x003ff); 1996 | formattedWrite(output, "\\u%04X\\u%04X", first, last); 1997 | } 1998 | } 1999 | else 2000 | { 2001 | if (ch < 0x20) formattedWrite(output, "\\u%04X", ch); 2002 | else output.put(ch); 2003 | } 2004 | break; 2005 | } 2006 | } 2007 | 2008 | output.put('"'); 2009 | } 2010 | 2011 | package String escapeStringLiteral(String)(String str) 2012 | nothrow @safe { 2013 | import std.string; 2014 | 2015 | auto rep = str.representation; 2016 | auto ret = appender!String(); 2017 | { 2018 | // Appender.put it not nothrow 2019 | scope (failure) assert(false); 2020 | escapeStringLiteral(rep, ret); 2021 | } 2022 | return ret.data; 2023 | } 2024 | 2025 | private dchar decodeUTF16CP(R)(ref R input, ref string error) 2026 | { 2027 | dchar uch = 0; 2028 | foreach (i; 0 .. 4) 2029 | { 2030 | if (input.empty) 2031 | { 2032 | error = "Premature end of unicode escape sequence"; 2033 | return dchar.max; 2034 | } 2035 | 2036 | uch *= 16; 2037 | auto dc = input.front; 2038 | input.popFront(); 2039 | 2040 | if (dc >= '0' && dc <= '9') 2041 | uch += dc - '0'; 2042 | else if ((dc >= 'a' && dc <= 'f') || (dc >= 'A' && dc <= 'F')) 2043 | uch += (dc & ~0x20) - 'A' + 10; 2044 | else 2045 | { 2046 | error = "Invalid character in Unicode escape sequence"; 2047 | return dchar.max; 2048 | } 2049 | } 2050 | return uch; 2051 | } 2052 | 2053 | // little helper to be able to pass integer ranges to std.utf.decodeFront 2054 | private struct CastRange(T, R) 2055 | { 2056 | private R* _range; 2057 | 2058 | this(R* range) { _range = range; } 2059 | @property bool empty() { return (*_range).empty; } 2060 | @property T front() { return cast(T)(*_range).front; } 2061 | void popFront() { (*_range).popFront(); } 2062 | } 2063 | private CastRange!(T, R) castRange(T, R)(ref R range) @trusted { return CastRange!(T, R)(&range); } 2064 | static assert(isInputRange!(CastRange!(char, uint[]))); 2065 | 2066 | 2067 | private double exp10(int exp) pure @trusted @nogc 2068 | { 2069 | enum min = -19; 2070 | enum max = 19; 2071 | static __gshared immutable expmuls = { 2072 | double[max - min + 1] ret; 2073 | double m = 0.1; 2074 | foreach_reverse (i; min .. 0) { ret[i-min] = m; m *= 0.1; } 2075 | m = 1.0; 2076 | foreach (i; 0 .. max) { ret[i-min] = m; m *= 10.0; } 2077 | return ret; 2078 | }(); 2079 | if (exp >= min && exp <= max) return expmuls[exp-min]; 2080 | return 10.0 ^^ exp; 2081 | } 2082 | 2083 | 2084 | // derived from libdparse 2085 | private ulong skip(bool matching, chars...)(const(ubyte)* p) pure nothrow @safe @nogc 2086 | if (chars.length <= 8) 2087 | { 2088 | version (Windows) { 2089 | // TODO: implement ASM version (Win64 ABI)! 2090 | import std.algorithm; 2091 | const(ubyte)* pc = p; 2092 | while ((*pc).among!chars) pc++; 2093 | return pc - p; 2094 | } else { 2095 | enum constant = ByteCombine!chars; 2096 | enum charsLength = chars.length; 2097 | 2098 | static if (matching) 2099 | enum flags = 0b0001_0000; 2100 | else 2101 | enum flags = 0b0000_0000; 2102 | 2103 | asm pure @nogc nothrow 2104 | { 2105 | naked; 2106 | movdqu XMM1, [RDI]; 2107 | mov R10, constant; 2108 | movq XMM2, R10; 2109 | mov RAX, charsLength; 2110 | mov RDX, 16; 2111 | pcmpestri XMM2, XMM1, flags; 2112 | mov RAX, RCX; 2113 | ret; 2114 | } 2115 | } 2116 | } 2117 | 2118 | private template ByteCombine(c...) 2119 | { 2120 | static assert (c.length <= 8); 2121 | static if (c.length > 1) 2122 | enum ulong ByteCombine = c[0] | (ByteCombine!(c[1..$]) << 8); 2123 | else 2124 | enum ulong ByteCombine = c[0]; 2125 | } 2126 | --------------------------------------------------------------------------------