1 /**
2 	JSON serialization and value handling.
3 
4 	This module provides the Json struct for reading, writing and manipulating JSON values in a seamless,
5 	JavaScript like way. De(serialization) of arbitrary D types is also supported.
6 
7 	Examples:
8 
9 	---
10 	void manipulateJson(Json j)
11 	{
12 		// object members can be accessed using member syntax, just like in JavaScript
13 		j = Json.EmptyObject;
14 		j.name = "Example";
15 		j.id = 1;
16 
17 		// retrieving the values is done using get()
18 		assert(j["name"].get!string == "Example");
19 		assert(j["id"].get!int == 1);
20 
21 		// semantic convertions can be done using to()
22 		assert(j.id.to!string == "1");
23 
24 		// prints:
25 		// name: "Example"
26 		// id: 1
27 		foreach( string key, value; j ){
28 			writefln("%s: %s", key, value);
29 		}
30 
31 		// print out as JSON: {"name": "Example", "id": 1}
32 		writefln("JSON: %s", j.toString());
33 	}
34 	---
35 
36 	Copyright: © 2012 RejectedSoftware e.K.
37 	License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file.
38 	Authors: Sönke Ludwig
39 */
40 module dub.internal.vibecompat.data.json;
41 
42 import dub.internal.vibecompat.data.utils;
43 
44 import std.array;
45 import std.conv;
46 import std.datetime;
47 import std.exception;
48 import std.format;
49 import std.string;
50 import std.range;
51 import std.traits;
52 
53 
54 /******************************************************************************/
55 /* public types                                                               */
56 /******************************************************************************/
57 
58 /**
59 	Represents a single JSON value.
60 
61 	Json values can have one of the types defined in the Json.Type enum. They
62 	behave mostly like values in ECMA script in the way that you can
63 	transparently perform operations on them. However, strict typechecking is
64 	done, so that operations between differently typed JSON values will throw
65 	an exception. Additionally, an explicit cast or using get!() or to!() is
66 	required to convert a JSON value to the corresponding static D type.
67 */
68 struct Json {
69 	private {
70 		union {
71 			bool m_bool;
72 			long m_int;
73 			double m_float;
74 			string m_string;
75 			Json[] m_array;
76 			Json[string] m_object;
77 		};
78 		Type m_type = Type.Undefined;
79 	}
80 
81 	/** Represents the run time type of a JSON value.
82 	*/
83 	enum Type {
84 		/// A non-existent value in a JSON object
85 		Undefined,
86 		/// Null value
87 		Null,
88 		/// Boolean value
89 		Bool,
90 		/// 64-bit integer value
91 		Int,
92 		/// 64-bit floating point value
93 		Float,
94 		/// UTF-8 string
95 		String,
96 		/// Array of JSON values
97 		Array,
98 		/// JSON object aka. dictionary from string to Json
99 		Object
100 	}
101 
102 	/// New JSON value of Type.Undefined
103 	static @property Json Undefined() { return Json(); }
104 
105 	/// New JSON value of Type.Object
106 	static @property Json EmptyObject() { return Json(cast(Json[string])null); }
107 
108 	/// New JSON value of Type.Array
109 	static @property Json EmptyArray() { return Json(cast(Json[])null); }
110 
111 	version(JsonLineNumbers) int line;
112 
113 	/**
114 		Constructor for a JSON object.
115 	*/
116 	this(typeof(null)) { m_type = Type.Null; }
117 	/// ditto
118 	this(bool v) { m_type = Type.Bool; m_bool = v; }
119 	/// ditto
120 	this(int v) { m_type = Type.Int; m_int = v; }
121 	/// ditto
122 	this(long v) { m_type = Type.Int; m_int = v; }
123 	/// ditto
124 	this(double v) { m_type = Type.Float; m_float = v; }
125 	/// ditto
126 	this(string v) { m_type = Type.String; m_string = v; }
127 	/// ditto
128 	this(Json[] v) { m_type = Type.Array; m_array = v; }
129 	/// ditto
130 	this(Json[string] v) { m_type = Type.Object; m_object = v; }
131 
132 	/**
133 		Allows assignment of D values to a JSON value.
134 	*/
135 	ref Json opAssign(Json v){
136 		m_type = v.m_type;
137 		final switch(m_type){
138 			case Type.Undefined: m_string = null; break;
139 			case Type.Null: m_string = null; break;
140 			case Type.Bool: m_bool = v.m_bool; break;
141 			case Type.Int: m_int = v.m_int; break;
142 			case Type.Float: m_float = v.m_float; break;
143 			case Type.String: m_string = v.m_string; break;
144 			case Type.Array: m_array = v.m_array; break;
145 			case Type.Object: m_object = v.m_object; break;
146 		}
147 		return this;
148 	}
149 	/// ditto
150 	void opAssign(typeof(null)) { m_type = Type.Null; m_string = null; }
151 	/// ditto
152 	bool opAssign(bool v) { m_type = Type.Bool; m_bool = v; return v; }
153 	/// ditto
154 	int opAssign(int v) { m_type = Type.Int; m_int = v; return v; }
155 	/// ditto
156 	long opAssign(long v) { m_type = Type.Int; m_int = v; return v; }
157 	/// ditto
158 	double opAssign(double v) { m_type = Type.Float; m_float = v; return v; }
159 	/// ditto
160 	string opAssign(string v) { m_type = Type.String; m_string = v; return v; }
161 	/// ditto
162 	Json[] opAssign(Json[] v) { m_type = Type.Array; m_array = v; return v; }
163 	/// ditto
164 	Json[string] opAssign(Json[string] v) { m_type = Type.Object; m_object = v; return v; }
165 
166 	/**
167 		The current type id of this JSON object.
168 	*/
169 	@property Type type() const { return m_type; }
170 
171 	/**
172 		Allows direct indexing of array typed JSON values.
173 	*/
174 	ref inout(Json) opIndex(size_t idx) inout { checkType!(Json[])(); return m_array[idx]; }
175 
176 	/**
177 		Allows direct indexing of object typed JSON values using a string as
178 		the key.
179 	*/
180 	const(Json) opIndex(string key) const {
181 		checkType!(Json[string])();
182 		if( auto pv = key in m_object ) return *pv;
183 		Json ret = Json.Undefined;
184 		ret.m_string = key;
185 		return ret;
186 	}
187 	/// ditto
188 	ref Json opIndex(string key){
189 		checkType!(Json[string])();
190 		if( auto pv = key in m_object )
191 			return *pv;
192 		m_object[key] = Json();
193 		m_object[key].m_type = Type.Undefined; // DMDBUG: AAs are teh $H1T!!!11
194 		assert(m_object[key].type == Type.Undefined);
195 		m_object[key].m_string = key;
196 		return m_object[key];
197 	}
198 
199 	/**
200 		Returns a slice of a JSON array.
201 	*/
202 	inout(Json[]) opSlice() inout { checkType!(Json[])(); return m_array; }
203 	///
204 	inout(Json[]) opSlice(size_t from, size_t to) inout { checkType!(Json[])(); return m_array[from .. to]; }
205 
206 	/**
207 		Removes an entry from an object.
208 	*/
209 	void remove(string item) { checkType!(Json[string])(); m_object.remove(item); }
210 
211 	/**
212 		Returns the number of entries of string, array or object typed JSON values.
213 	*/
214 	@property size_t length()
215 	const {
216 		switch(m_type){
217 			case Type.String: return m_string.length;
218 			case Type.Array: return m_array.length;
219 			case Type.Object: return m_object.length;
220 			default:
221 				enforce(false, "Json.length() can only be called on strings, arrays and objects, not "~.to!string(m_type)~".");
222 				return 0;
223 		}
224 	}
225 
226 	/**
227 		Allows foreach iterating over JSON objects and arrays.
228 	*/
229 	int opApply(int delegate(ref Json obj) del)
230 	{
231 		enforce(m_type == Type.Array || m_type == Type.Object, "opApply may only be called on objects and arrays, not "~.to!string(m_type)~".");
232 		if( m_type == Type.Array ){
233 			foreach( ref v; m_array )
234 				if( auto ret = del(v) )
235 					return ret;
236 			return 0;
237 		} else {
238 			foreach( ref v; m_object )
239 				if( v.type != Type.Undefined )
240 					if( auto ret = del(v) )
241 						return ret;
242 			return 0;
243 		}
244 	}
245 	/// ditto
246 	int opApply(int delegate(ref const Json obj) del)
247 	const {
248 		enforce(m_type == Type.Array || m_type == Type.Object, "opApply may only be called on objects and arrays, not "~.to!string(m_type)~".");
249 		if( m_type == Type.Array ){
250 			foreach( ref v; m_array )
251 				if( auto ret = del(v) )
252 					return ret;
253 			return 0;
254 		} else {
255 			foreach( ref v; m_object )
256 				if( v.type != Type.Undefined )
257 					if( auto ret = del(v) )
258 						return ret;
259 			return 0;
260 		}
261 	}
262 	/// ditto
263 	int opApply(int delegate(ref size_t idx, ref Json obj) del)
264 	{
265 		enforce(m_type == Type.Array, "opApply may only be called on arrays, not "~.to!string(m_type)~"");
266 		foreach( idx, ref v; m_array )
267 			if( auto ret = del(idx, v) )
268 				return ret;
269 		return 0;
270 	}
271 	/// ditto
272 	int opApply(int delegate(ref size_t idx, ref const Json obj) del)
273 	const {
274 		enforce(m_type == Type.Array, "opApply may only be called on arrays, not "~.to!string(m_type)~".");
275 		foreach( idx, ref v; m_array )
276 			if( auto ret = del(idx, v) )
277 				return ret;
278 		return 0;
279 	}
280 	/// ditto
281 	int opApply(int delegate(ref string idx, ref Json obj) del)
282 	{
283 		enforce(m_type == Type.Object, "opApply may only be called on objects, not "~.to!string(m_type)~".");
284 		foreach( idx, ref v; m_object )
285 			if( v.type != Type.Undefined )
286 				if( auto ret = del(idx, v) )
287 					return ret;
288 		return 0;
289 	}
290 	/// ditto
291 	int opApply(int delegate(ref string idx, ref const Json obj) del)
292 	const {
293 		enforce(m_type == Type.Object, "opApply may only be called on objects, not "~.to!string(m_type)~".");
294 		foreach( idx, ref v; m_object )
295 			if( v.type != Type.Undefined )
296 				if( auto ret = del(idx, v) )
297 					return ret;
298 		return 0;
299 	}
300 
301 	/**
302 		Converts the JSON value to the corresponding D type - types must match exactly.
303 	*/
304 	inout(T) opCast(T)() inout { return get!T; }
305 	/// ditto
306 	@property inout(T) get(T)()
307 	inout {
308 		checkType!T();
309 		static if( is(T == bool) ) return m_bool;
310 		else static if( is(T == double) ) return m_float;
311 		else static if( is(T == float) ) return cast(T)m_float;
312 		else static if( is(T == long) ) return m_int;
313 		else static if( is(T : long) ){ enforce(m_int <= T.max && m_int >= T.min); return cast(T)m_int; }
314 		else static if( is(T == string) ) return m_string;
315 		else static if( is(T == Json[]) ) return m_array;
316 		else static if( is(T == Json[string]) ) return m_object;
317 		else static assert("JSON can only be casted to (bool, long, double, string, Json[] or Json[string]. Not "~T.stringof~".");
318 	}
319 	/// ditto
320 	@property const(T) opt(T)(const(T) def = T.init)
321 	const {
322 		if( typeId!T != m_type ) return def;
323 		return get!T;
324 	}
325 	/// ditto
326 	@property T opt(T)(T def = T.init)
327 	{
328 		if( typeId!T != m_type ) return def;
329 		return get!T;
330 	}
331 
332 	/**
333 		Converts the JSON value to the corresponding D type - types are converted as neccessary.
334 	*/
335 	@property inout(T) to(T)()
336 	inout {
337 		static if( is(T == bool) ){
338 			final switch( m_type ){
339 				case Type.Undefined: return false;
340 				case Type.Null: return false;
341 				case Type.Bool: return m_bool;
342 				case Type.Int: return m_int != 0;
343 				case Type.Float: return m_float != 0;
344 				case Type.String: return m_string.length > 0;
345 				case Type.Array: return m_array.length > 0;
346 				case Type.Object: return m_object.length > 0;
347 			}
348 		} else static if( is(T == double) ){
349 			final switch( m_type ){
350 				case Type.Undefined: return T.init;
351 				case Type.Null: return 0;
352 				case Type.Bool: return m_bool ? 1 : 0;
353 				case Type.Int: return m_int;
354 				case Type.Float: return m_float;
355 				case Type.String: return .to!double(cast(string)m_string);
356 				case Type.Array: return double.init;
357 				case Type.Object: return double.init;
358 			}
359 		} else static if( is(T == float) ){
360 			final switch( m_type ){
361 				case Type.Undefined: return T.init;
362 				case Type.Null: return 0;
363 				case Type.Bool: return m_bool ? 1 : 0;
364 				case Type.Int: return m_int;
365 				case Type.Float: return m_float;
366 				case Type.String: return .to!float(cast(string)m_string);
367 				case Type.Array: return float.init;
368 				case Type.Object: return float.init;
369 			}
370 		}
371 		else static if( is(T == long) ){
372 			final switch( m_type ){
373 				case Type.Undefined: return 0;
374 				case Type.Null: return 0;
375 				case Type.Bool: return m_bool ? 1 : 0;
376 				case Type.Int: return m_int;
377 				case Type.Float: return cast(long)m_float;
378 				case Type.String: return .to!long(m_string);
379 				case Type.Array: return 0;
380 				case Type.Object: return 0;
381 			}
382 		} else static if( is(T : long) ){
383 			final switch( m_type ){
384 				case Type.Undefined: return 0;
385 				case Type.Null: return 0;
386 				case Type.Bool: return m_bool ? 1 : 0;
387 				case Type.Int: return cast(T)m_int;
388 				case Type.Float: return cast(T)m_float;
389 				case Type.String: return cast(T).to!long(cast(string)m_string);
390 				case Type.Array: return 0;
391 				case Type.Object: return 0;
392 			}
393 		} else static if( is(T == string) ){
394 			switch( m_type ){
395 				default: return toString();
396 				case Type.String: return m_string;
397 			}
398 		} else static if( is(T == Json[]) ){
399 			switch( m_type ){
400 				default: return Json([this]);
401 				case Type.Array: return m_array;
402 			}
403 		} else static if( is(T == Json[string]) ){
404 			switch( m_type ){
405 				default: return Json(["value": this]);
406 				case Type.Object: return m_object;
407 			}
408 		} else static assert("JSON can only be casted to (bool, long, double, string, Json[] or Json[string]. Not "~T.stringof~".");
409 	}
410 
411 	/**
412 		Performs unary operations on the JSON value.
413 
414 		The following operations are supported for each type:
415 
416 		$(DL
417 			$(DT Null)   $(DD none)
418 			$(DT Bool)   $(DD ~)
419 			$(DT Int)    $(DD +, -, ++, --)
420 			$(DT Float)  $(DD +, -, ++, --)
421 			$(DT String) $(DD none)
422 			$(DT Array)  $(DD none)
423 			$(DT Object) $(DD none)
424 		)
425 	*/
426 	Json opUnary(string op)()
427 	const {
428 		static if( op == "~" ){
429 			checkType!bool();
430 			return Json(~m_bool);
431 		} else static if( op == "+" || op == "-" || op == "++" || op == "--" ){
432 			if( m_type == Type.Int ) mixin("return Json("~op~"m_int);");
433 			else if( m_type == Type.Float ) mixin("return Json("~op~"m_float);");
434 			else enforce(false, "'"~op~"' only allowed on scalar types, not on "~.to!string(m_type)~".");
435 		} else static assert("Unsupported operator '"~op~"' for type JSON.");
436 	}
437 
438 	/**
439 		Performs binary operations between JSON values.
440 
441 		The two JSON values must be of the same run time type or an exception
442 		will be thrown. Only the operations listed are allowed for each of the
443 		types.
444 
445 		$(DL
446 			$(DT Null)   $(DD none)
447 			$(DT Bool)   $(DD &&, ||)
448 			$(DT Int)    $(DD +, -, *, /, %)
449 			$(DT Float)  $(DD +, -, *, /, %)
450 			$(DT String) $(DD ~)
451 			$(DT Array)  $(DD ~)
452 			$(DT Object) $(DD none)
453 		)
454 	*/
455 	Json opBinary(string op)(ref const(Json) other)
456 	const {
457 		enforce(m_type == other.m_type, "Binary operation '"~op~"' between "~.to!string(m_type)~" and "~.to!string(other.m_type)~" JSON objects.");
458 		static if( op == "&&" ){
459 			enforce(m_type == Type.Bool, "'&&' only allowed for Type.Bool, not "~.to!string(m_type)~".");
460 			return Json(m_bool && other.m_bool);
461 		} else static if( op == "||" ){
462 			enforce(m_type == Type.Bool, "'||' only allowed for Type.Bool, not "~.to!string(m_type)~".");
463 			return Json(m_bool || other.m_bool);
464 		} else static if( op == "+" ){
465 			if( m_type == Type.Int ) return Json(m_int + other.m_int);
466 			else if( m_type == Type.Float ) return Json(m_float + other.m_float);
467 			else enforce(false, "'+' only allowed for scalar types, not "~.to!string(m_type)~".");
468 		} else static if( op == "-" ){
469 			if( m_type == Type.Int ) return Json(m_int - other.m_int);
470 			else if( m_type == Type.Float ) return Json(m_float - other.m_float);
471 			else enforce(false, "'-' only allowed for scalar types, not "~.to!string(m_type)~".");
472 		} else static if( op == "*" ){
473 			if( m_type == Type.Int ) return Json(m_int * other.m_int);
474 			else if( m_type == Type.Float ) return Json(m_float * other.m_float);
475 			else enforce(false, "'*' only allowed for scalar types, not "~.to!string(m_type)~".");
476 		} else static if( op == "/" ){
477 			if( m_type == Type.Int ) return Json(m_int / other.m_int);
478 			else if( m_type == Type.Float ) return Json(m_float / other.m_float);
479 			else enforce(false, "'/' only allowed for scalar types, not "~.to!string(m_type)~".");
480 		} else static if( op == "%" ){
481 			if( m_type == Type.Int ) return Json(m_int % other.m_int);
482 			else if( m_type == Type.Float ) return Json(m_float % other.m_float);
483 			else enforce(false, "'%' only allowed for scalar types, not "~.to!string(m_type)~".");
484 		} else static if( op == "~" ){
485 			if( m_type == Type.String ) return Json(m_string ~ other.m_string);
486 			else enforce(false, "'~' only allowed for strings, not "~.to!string(m_type)~".");
487 		} else static assert("Unsupported operator '"~op~"' for type JSON.");
488 		assert(false);
489 	}
490 	/// ditto
491 	Json opBinary(string op)(Json other)
492 		if( op == "~" )
493 	{
494 		static if( op == "~" ){
495 			if( m_type == Type.String ) return Json(m_string ~ other.m_string);
496 			else if( m_type == Type.Array ) return Json(m_array ~ other.m_array);
497 			else enforce(false, "'~' only allowed for strings and arrays, not "~.to!string(m_type)~".");
498 		} else static assert("Unsupported operator '"~op~"' for type JSON.");
499 		assert(false);
500 	}
501 	/// ditto
502 	void opOpAssign(string op)(Json other)
503 		if( op == "+" || op == "-" || op == "*" ||op == "/" || op == "%" )
504 	{
505 		enforce(m_type == other.m_type, "Binary operation '"~op~"' between "~.to!string(m_type)~" and "~.to!string(other.m_type)~" JSON objects.");
506 		static if( op == "+" ){
507 			if( m_type == Type.Int ) m_int += other.m_int;
508 			else if( m_type == Type.Float ) m_float += other.m_float;
509 			else enforce(false, "'+' only allowed for scalar types, not "~.to!string(m_type)~".");
510 		} else static if( op == "-" ){
511 			if( m_type == Type.Int ) m_int -= other.m_int;
512 			else if( m_type == Type.Float ) m_float -= other.m_float;
513 			else enforce(false, "'-' only allowed for scalar types, not "~.to!string(m_type)~".");
514 		} else static if( op == "*" ){
515 			if( m_type == Type.Int ) m_int *= other.m_int;
516 			else if( m_type == Type.Float ) m_float *= other.m_float;
517 			else enforce(false, "'*' only allowed for scalar types, not "~.to!string(m_type)~".");
518 		} else static if( op == "/" ){
519 			if( m_type == Type.Int ) m_int /= other.m_int;
520 			else if( m_type == Type.Float ) m_float /= other.m_float;
521 			else enforce(false, "'/' only allowed for scalar types, not "~.to!string(m_type)~".");
522 		} else static if( op == "%" ){
523 			if( m_type == Type.Int ) m_int %= other.m_int;
524 			else if( m_type == Type.Float ) m_float %= other.m_float;
525 			else enforce(false, "'%' only allowed for scalar types, not "~.to!string(m_type)~".");
526 		} /*else static if( op == "~" ){
527 			if( m_type == Type.String ) m_string ~= other.m_string;
528 			else if( m_type == Type.Array ) m_array ~= other.m_array;
529 			else enforce(false, "'%' only allowed for scalar types, not "~.to!string(m_type)~".");
530 		}*/ else static assert("Unsupported operator '"~op~"' for type JSON.");
531 		assert(false);
532 	}
533 	/// ditto
534 	Json opBinary(string op)(bool other) const { checkType!bool(); mixin("return Json(m_bool "~op~" other);"); }
535 	/// ditto
536 	Json opBinary(string op)(long other) const { checkType!long(); mixin("return Json(m_int "~op~" other);"); }
537 	/// ditto
538 	Json opBinary(string op)(double other) const { checkType!double(); mixin("return Json(m_float "~op~" other);"); }
539 	/// ditto
540 	Json opBinary(string op)(string other) const { checkType!string(); mixin("return Json(m_string "~op~" other);"); }
541 	/// ditto
542 	Json opBinary(string op)(Json other)
543 	if (op == "~") {
544 		if (m_type == Type.Array) return Json(m_array ~ other);
545 		else return Json(this ~ other);
546 	}
547 	/// ditto
548 	Json opBinaryRight(string op)(bool other) const { checkType!bool(); mixin("return Json(other "~op~" m_bool);"); }
549 	/// ditto
550 	Json opBinaryRight(string op)(long other) const { checkType!long(); mixin("return Json(other "~op~" m_int);"); }
551 	/// ditto
552 	Json opBinaryRight(string op)(double other) const { checkType!double(); mixin("return Json(other "~op~" m_float);"); }
553 	/// ditto
554 	Json opBinaryRight(string op)(string other) const if(op == "~") { checkType!string(); return Json(other ~ m_string); }
555 	/// ditto
556 	inout(Json)* opBinaryRight(string op)(string other) inout if(op == "in") {
557 		checkType!(Json[string])();
558 		auto pv = other in m_object;
559 		if( !pv ) return null;
560 		if( pv.type == Type.Undefined ) return null;
561 		return pv;
562 	}
563 
564 	/**
565 		Allows to access existing fields of a JSON object using dot syntax.
566 	*/
567 	@property const(Json) opDispatch(string prop)() const { return opIndex(prop); }
568 	/// ditto
569 	@property ref Json opDispatch(string prop)() { return opIndex(prop); }
570 
571 	/**
572 		Compares two JSON values for equality.
573 
574 		If the two values have different types, they are considered unequal.
575 		This differs with ECMA script, which performs a type conversion before
576 		comparing the values.
577 	*/
578 	bool opEquals(ref const Json other)
579 	const {
580 		if( m_type != other.m_type ) return false;
581 		final switch(m_type){
582 			case Type.Undefined: return false;
583 			case Type.Null: return true;
584 			case Type.Bool: return m_bool == other.m_bool;
585 			case Type.Int: return m_int == other.m_int;
586 			case Type.Float: return m_float == other.m_float;
587 			case Type.String: return m_string == other.m_string;
588 			case Type.Array: return m_array == other.m_array;
589 			case Type.Object: return m_object == other.m_object;
590 		}
591 	}
592 	/// ditto
593 	bool opEquals(const Json other) const { return opEquals(other); }
594 	/// ditto
595 	bool opEquals(typeof(null)) const { return m_type == Type.Null; }
596 	/// ditto
597 	bool opEquals(bool v) const { return m_type == Type.Bool && m_bool == v; }
598 	/// ditto
599 	bool opEquals(long v) const { return m_type == Type.Int && m_int == v; }
600 	/// ditto
601 	bool opEquals(double v) const { return m_type == Type.Float && m_float == v; }
602 	/// ditto
603 	bool opEquals(string v) const { return m_type == Type.String && m_string == v; }
604 
605 	/**
606 		Compares two JSON values.
607 
608 		If the types of the two values differ, the value with the smaller type
609 		id is considered the smaller value. This differs from ECMA script, which
610 		performs a type conversion before comparing the values.
611 
612 		JSON values of type Object cannot be compared and will throw an
613 		exception.
614 	*/
615 	int opCmp(ref const Json other)
616 	const {
617 		if( m_type != other.m_type ) return m_type < other.m_type ? -1 : 1;
618 		final switch(m_type){
619 			case Type.Undefined: return 0;
620 			case Type.Null: return 0;
621 			case Type.Bool: return m_bool < other.m_bool ? -1 : m_bool == other.m_bool ? 0 : 1;
622 			case Type.Int: return m_int < other.m_int ? -1 : m_int == other.m_int ? 0 : 1;
623 			case Type.Float: return m_float < other.m_float ? -1 : m_float == other.m_float ? 0 : 1;
624 			case Type.String: return m_string < other.m_string ? -1 : m_string == other.m_string ? 0 : 1;
625 			case Type.Array: return m_array < other.m_array ? -1 : m_array == other.m_array ? 0 : 1;
626 			case Type.Object:
627 				enforce(false, "JSON objects cannot be compared.");
628 				assert(false);
629 		}
630 	}
631 
632 
633 
634 	/**
635 		Returns the type id corresponding to the given D type.
636 	*/
637 	static @property Type typeId(T)() {
638 		static if( is(T == typeof(null)) ) return Type.Null;
639 		else static if( is(T == bool) ) return Type.Bool;
640 		else static if( is(T == double) ) return Type.Float;
641 		else static if( is(T == float) ) return Type.Float;
642 		else static if( is(T : long) ) return Type.Int;
643 		else static if( is(T == string) ) return Type.String;
644 		else static if( is(T == Json[]) ) return Type.Array;
645 		else static if( is(T == Json[string]) ) return Type.Object;
646 		else static assert(false, "Unsupported JSON type '"~T.stringof~"'. Only bool, long, double, string, Json[] and Json[string] are allowed.");
647 	}
648 
649 	/**
650 		Returns the JSON object as a string.
651 
652 		For large JSON values use writeJsonString instead as this function will store the whole string
653 		in memory, whereas writeJsonString writes it out bit for bit.
654 
655 		See_Also: writeJsonString, toPrettyString
656 	*/
657 	string toString()
658 	const {
659 		auto ret = appender!string();
660 		writeJsonString(ret, this);
661 		return ret.data;
662 	}
663 
664 	/**
665 		Returns the JSON object as a "pretty" string.
666 
667 		---
668 		auto json = Json(["foo": Json("bar")]);
669 		writeln(json.toPrettyString());
670 
671 		// output:
672 		// {
673 		//     "foo": "bar"
674 		// }
675 		---
676 
677 		Params:
678 			level = Specifies the base amount of indentation for the output. Indentation  is always
679 				done using tab characters.
680 
681 		See_Also: writePrettyJsonString, toString
682 	*/
683 	string toPrettyString(int level = 0)
684 	const {
685 		auto ret = appender!string();
686 		writePrettyJsonString(ret, this, level);
687 		return ret.data;
688 	}
689 
690 	private void checkType(T)()
691 	const {
692 		string dbg;
693 		if( m_type == Type.Undefined ) dbg = " field "~m_string;
694 		enforce(typeId!T == m_type, "Trying to access JSON"~dbg~" of type "~.to!string(m_type)~" as "~T.stringof~".");
695 	}
696 
697 	/*invariant()
698 	{
699 		assert(m_type >= Type.Undefined && m_type <= Type.Object);
700 	}*/
701 }
702 
703 
704 /******************************************************************************/
705 /* public functions                                                           */
706 /******************************************************************************/
707 
708 /**
709 	Parses the given range as a JSON string and returns the corresponding Json object.
710 
711 	The range is shrunk during parsing, leaving any remaining text that is not part of
712 	the JSON contents.
713 
714 	Throws an Exception if any parsing error occured.
715 */
716 Json parseJson(R)(ref R range, int* line = null)
717 	if( is(R == string) )
718 {
719 	Json ret;
720 	enforce(!range.empty, "JSON string is empty.");
721 
722 	skipWhitespace(range, line);
723 
724 	version(JsonLineNumbers){
725 		import vibecompat.core.log;
726 		int curline = line ? *line : 0;
727 		scope(failure) logError("Error in line: %d", curline);
728 	}
729 
730 	switch( range.front ){
731 		case 'f':
732 			enforce(range[1 .. $].startsWith("alse"), "Expected 'false', got '"~range[0 .. 5]~"'.");
733 			range.popFrontN(5);
734 			ret = false;
735 			break;
736 		case 'n':
737 			enforce(range[1 .. $].startsWith("ull"), "Expected 'null', got '"~range[0 .. 4]~"'.");
738 			range.popFrontN(4);
739 			ret = null;
740 			break;
741 		case 't':
742 			enforce(range[1 .. $].startsWith("rue"), "Expected 'true', got '"~range[0 .. 4]~"'.");
743 			range.popFrontN(4);
744 			ret = true;
745 			break;
746 		case '0': .. case '9'+1:
747 		case '-':
748 			bool is_float;
749 			auto num = skipNumber(range, is_float);
750 			if( is_float ) ret = to!double(num);
751 			else ret = to!long(num);
752 			break;
753 		case '\"':
754 			ret = skipJsonString(range);
755 			break;
756 		case '[':
757 			Json[] arr;
758 			range.popFront();
759 			while(true) {
760 				skipWhitespace(range, line);
761 				enforce(!range.empty);
762 				if(range.front == ']') break;
763 				arr ~= parseJson(range, line);
764 				skipWhitespace(range, line);
765 				enforce(!range.empty && (range.front == ',' || range.front == ']'), "Expected ']' or ','.");
766 				if( range.front == ']' ) break;
767 				else range.popFront();
768 			}
769 			range.popFront();
770 			ret = arr;
771 			break;
772 		case '{':
773 			Json[string] obj;
774 			range.popFront();
775 			while(true) {
776 				skipWhitespace(range, line);
777 				enforce(!range.empty);
778 				if(range.front == '}') break;
779 				string key = skipJsonString(range);
780 				skipWhitespace(range, line);
781 				enforce(range.startsWith(":"), "Expected ':' for key '" ~ key ~ "'");
782 				range.popFront();
783 				skipWhitespace(range, line);
784 				Json itm = parseJson(range, line);
785 				obj[key] = itm;
786 				skipWhitespace(range, line);
787 				enforce(!range.empty && (range.front == ',' || range.front == '}'), "Expected '}' or ',' - got '"~range[0]~"'.");
788 				if( range.front == '}' ) break;
789 				else range.popFront();
790 			}
791 			range.popFront();
792 			ret = obj;
793 			break;
794 		default:
795 			enforce(false, "Expected valid json token, got '"~to!string(range.length)~range[0 .. range.length>12?12:range.length]~"'.");
796 	}
797 
798 	assert(ret.type != Json.Type.Undefined);
799 	version(JsonLineNumbers) ret.line = curline;
800 	return ret;
801 }
802 
803 /**
804 	Parses the given JSON string and returns the corresponding Json object.
805 
806 	Throws an Exception if any parsing error occurs.
807 */
808 Json parseJsonString(string str)
809 {
810 	auto ret = parseJson(str);
811 	enforce(str.strip().length == 0, "Expected end of string after JSON value.");
812 	return ret;
813 }
814 
815 unittest {
816 	assert(parseJsonString("null") == Json(null));
817 	assert(parseJsonString("true") == Json(true));
818 	assert(parseJsonString("false") == Json(false));
819 	assert(parseJsonString("1") == Json(1));
820 	assert(parseJsonString("2.0") == Json(2.0));
821 	assert(parseJsonString("\"test\"") == Json("test"));
822 	assert(parseJsonString("[1, 2, 3]") == Json([Json(1), Json(2), Json(3)]));
823 	assert(parseJsonString("{\"a\": 1}") == Json(["a": Json(1)]));
824 	assert(parseJsonString(`"\\\/\b\f\n\r\t\u1234"`).get!string == "\\/\b\f\n\r\t\u1234");
825 }
826 
827 
828 /**
829 	Serializes the given value to JSON.
830 
831 	The following types of values are supported:
832 
833 	$(DL
834 		$(DT Json)            $(DD Used as-is)
835 		$(DT null)            $(DD Converted to Json.Type.Null)
836 		$(DT bool)            $(DD Converted to Json.Type.Bool)
837 		$(DT float, double)   $(DD Converted to Json.Type.Double)
838 		$(DT short, ushort, int, uint, long, ulong) $(DD Converted to Json.Type.Int)
839 		$(DT string)          $(DD Converted to Json.Type.String)
840 		$(DT T[])             $(DD Converted to Json.Type.Array)
841 		$(DT T[string])       $(DD Converted to Json.Type.Object)
842 		$(DT struct)          $(DD Converted to Json.Type.Object)
843 		$(DT class)           $(DD Converted to Json.Type.Object or Json.Type.Null)
844 	)
845 
846 	All entries of an array or an associative array, as well as all R/W properties and
847 	all public fields of a struct/class are recursively serialized using the same rules.
848 
849 	Fields ending with an underscore will have the last underscore stripped in the
850 	serialized output. This makes it possible to use fields with D keywords as their name
851 	by simply appending an underscore.
852 
853 	The following methods can be used to customize the serialization of structs/classes:
854 
855 	---
856 	Json toJson() const;
857 	static T fromJson(Json src);
858 
859 	string toString() const;
860 	static T fromString(string src);
861 	---
862 
863 	The methods will have to be defined in pairs. The first pair that is implemented by
864 	the type will be used for serialization (i.e. toJson overrides toString).
865 */
866 Json serializeToJson(T)(T value)
867 {
868 	alias Unqual!T TU;
869 	static if( is(TU == Json) ) return value;
870 	else static if( is(TU == typeof(null)) ) return Json(null);
871 	else static if( is(TU == bool) ) return Json(value);
872 	else static if( is(TU == float) ) return Json(cast(double)value);
873 	else static if( is(TU == double) ) return Json(value);
874 	else static if( is(TU == DateTime) ) return Json(value.toISOExtString());
875 	else static if( is(TU == SysTime) ) return Json(value.toISOExtString());
876 	else static if( is(TU : long) ) return Json(cast(long)value);
877 	else static if( is(TU == string) ) return Json(value);
878 	else static if( isArray!T ){
879 		auto ret = new Json[value.length];
880 		foreach( i; 0 .. value.length )
881 			ret[i] = serializeToJson(value[i]);
882 		return Json(ret);
883 	} else static if( isAssociativeArray!TU ){
884 		Json[string] ret;
885 		foreach( string key, value; value )
886 			ret[key] = serializeToJson(value);
887 		return Json(ret);
888 	} else static if( __traits(compiles, value = T.fromJson(value.toJson())) ){
889 		return value.toJson();
890 	} else static if( __traits(compiles, value = T.fromString(value.toString())) ){
891 		return Json(value.toString());
892 	} else static if( is(TU == struct) ){
893 		Json[string] ret;
894 		foreach( m; __traits(allMembers, T) ){
895 			static if( isRWField!(TU, m) ){
896 				auto mv = __traits(getMember, value, m);
897 				ret[underscoreStrip(m)] = serializeToJson(mv);
898 			}
899 		}
900 		return Json(ret);
901 	} else static if( is(TU == class) ){
902 		if( value is null ) return Json(null);
903 		Json[string] ret;
904 		foreach( m; __traits(allMembers, T) ){
905 			static if( isRWField!(TU, m) ){
906 				auto mv = __traits(getMember, value, m);
907 				ret[underscoreStrip(m)] = serializeToJson(mv);
908 			}
909 		}
910 		return Json(ret);
911 	} else static if( isPointer!TU ){
912 		if( value is null ) return Json(null);
913 		return serializeToJson(*value);
914 	} else {
915 		static assert(false, "Unsupported type '"~T.stringof~"' for JSON serialization.");
916 	}
917 }
918 
919 
920 /**
921 	Deserializes a JSON value into the destination variable.
922 
923 	The same types as for serializeToJson() are supported and handled inversely.
924 */
925 void deserializeJson(T)(ref T dst, Json src)
926 {
927 	dst = deserializeJson!T(src);
928 }
929 /// ditto
930 T deserializeJson(T)(Json src)
931 {
932 	static if( is(T == Json) ) return src;
933 	else static if( is(T == typeof(null)) ){ return null; }
934 	else static if( is(T == bool) ) return src.get!bool;
935 	else static if( is(T == float) ) return src.to!float;   // since doubles are frequently serialized without
936 	else static if( is(T == double) ) return src.to!double; // a decimal point, we allow conversions here
937 	else static if( is(T == DateTime) ) return DateTime.fromISOExtString(src.get!string);
938 	else static if( is(T == SysTime) ) return SysTime.fromISOExtString(src.get!string);
939 	else static if( is(T : long) ) return cast(T)src.get!long;
940 	else static if( is(T == string) ) return src.get!string;
941 	else static if( isArray!T ){
942 		alias typeof(T.init[0]) TV;
943 		auto dst = new Unqual!TV[src.length];
944 		foreach( size_t i, v; src )
945 			dst[i] = deserializeJson!(Unqual!TV)(v);
946 		return dst;
947 	} else static if( isAssociativeArray!T ){
948 		alias typeof(T.init.values[0]) TV;
949 		Unqual!TV[string] dst;
950 		foreach( string key, value; src )
951 			dst[key] = deserializeJson!(Unqual!TV)(value);
952 		return dst;
953 	} else static if( __traits(compiles, { T dst; dst = T.fromJson(dst.toJson()); }()) ){
954 		return T.fromJson(src);
955 	} else static if( __traits(compiles, { T dst; dst = T.fromString(dst.toString()); }()) ){
956 		return T.fromString(src.get!string);
957 	} else static if( is(T == struct) ){
958 		T dst;
959 		foreach( m; __traits(allMembers, T) ){
960 			static if( isRWPlainField!(T, m) || isRWField!(T, m) ){
961 				alias typeof(__traits(getMember, dst, m)) TM;
962 				__traits(getMember, dst, m) = deserializeJson!TM(src[underscoreStrip(m)]);
963 			}
964 		}
965 		return dst;
966 	} else static if( is(T == class) ){
967 		if( src.type == Json.Type.Null ) return null;
968 		auto dst = new T;
969 		foreach( m; __traits(allMembers, T) ){
970 			static if( isRWPlainField!(T, m) || isRWField!(T, m) ){
971 				alias typeof(__traits(getMember, dst, m)) TM;
972 				__traits(getMember, dst, m) = deserializeJson!TM(src[underscoreStrip(m)]);
973 			}
974 		}
975 		return dst;
976 	} else static if( isPointer!T ){
977 		if( src.type == Json.Type.Null ) return null;
978 		alias typeof(*T.init) TD;
979 		dst = new TD;
980 		*dst = deserializeJson!TD(src);
981 		return dst;
982 	} else {
983 		static assert(false, "Unsupported type '"~T.stringof~"' for JSON serialization.");
984 	}
985 }
986 
987 unittest {
988 	import std.stdio;
989 	static struct S { float a; double b; bool c; int d; string e; byte f; ubyte g; long h; ulong i; float[] j; }
990 	immutable S t = {1.5, -3.0, true, int.min, "Test", -128, 255, long.min, ulong.max, [1.1, 1.2, 1.3]};
991 	S u;
992 	deserializeJson(u, serializeToJson(t));
993 	assert(t.a == u.a);
994 	assert(t.b == u.b);
995 	assert(t.c == u.c);
996 	assert(t.d == u.d);
997 	assert(t.e == u.e);
998 	assert(t.f == u.f);
999 	assert(t.g == u.g);
1000 	assert(t.h == u.h);
1001 	assert(t.i == u.i);
1002 	assert(t.j == u.j);
1003 }
1004 
1005 unittest {
1006 	static class C {
1007 		int a;
1008 		private int _b;
1009 		@property int b() const { return _b; }
1010 		@property void b(int v) { _b = v; }
1011 
1012 		@property int test() const { return 10; }
1013 
1014 		void test2() {}
1015 	}
1016 	C c = new C;
1017 	c.a = 1;
1018 	c.b = 2;
1019 
1020 	C d;
1021 	deserializeJson(d, serializeToJson(c));
1022 	assert(c.a == d.a);
1023 	assert(c.b == d.b);
1024 }
1025 
1026 
1027 /**
1028 	Writes the given JSON object as a JSON string into the destination range.
1029 
1030 	This function will convert the given JSON value to a string without adding
1031 	any white space between tokens (no newlines, no indentation and no padding).
1032 	The output size is thus minizized, at the cost of bad human readability.
1033 
1034 	Params:
1035 		dst   = References the string output range to which the result is written.
1036 		json  = Specifies the JSON value that is to be stringified.
1037 
1038 	See_Also: Json.toString, writePrettyJsonString
1039 */
1040 void writeJsonString(R)(ref R dst, in Json json)
1041 //	if( isOutputRange!R && is(ElementEncodingType!R == char) )
1042 {
1043 	final switch( json.type ){
1044 		case Json.Type.Undefined: dst.put("undefined"); break;
1045 		case Json.Type.Null: dst.put("null"); break;
1046 		case Json.Type.Bool: dst.put(cast(bool)json ? "true" : "false"); break;
1047 		case Json.Type.Int: formattedWrite(dst, "%d", json.get!long); break;
1048 		case Json.Type.Float: formattedWrite(dst, "%.16g", json.get!double); break;
1049 		case Json.Type.String:
1050 			dst.put("\"");
1051 			jsonEscape(dst, cast(string)json);
1052 			dst.put("\"");
1053 			break;
1054 		case Json.Type.Array:
1055 			dst.put("[");
1056 			bool first = true;
1057 			foreach( ref const Json e; json ){
1058 				if( e.type == Json.Type.Undefined ) continue;
1059 				if( !first ) dst.put(",");
1060 				first = false;
1061 				writeJsonString(dst, e);
1062 			}
1063 			dst.put("]");
1064 			break;
1065 		case Json.Type.Object:
1066 			dst.put("{");
1067 			bool first = true;
1068 			foreach( string k, ref const Json e; json ){
1069 				if( e.type == Json.Type.Undefined ) continue;
1070 				if( !first ) dst.put(",");
1071 				first = false;
1072 				dst.put("\"");
1073 				jsonEscape(dst, k);
1074 				dst.put("\":");
1075 				writeJsonString(dst, e);
1076 			}
1077 			dst.put("}");
1078 			break;
1079 	}
1080 }
1081 
1082 /**
1083 	Writes the given JSON object as a prettified JSON string into the destination range.
1084 
1085 	The output will contain newlines and indents to make the output human readable.
1086 
1087 	Params:
1088 		dst   = References the string output range to which the result is written.
1089 		json  = Specifies the JSON value that is to be stringified.
1090 		level = Specifies the base amount of indentation for the output. Indentation  is always
1091 		        done using tab characters.
1092 
1093 	See_Also: Json.toPrettyString, writeJsonString
1094 */
1095 void writePrettyJsonString(R)(ref R dst, in Json json, int level = 0)
1096 //	if( isOutputRange!R && is(ElementEncodingType!R == char) )
1097 {
1098 	final switch( json.type ){
1099 		case Json.Type.Undefined: dst.put("undefined"); break;
1100 		case Json.Type.Null: dst.put("null"); break;
1101 		case Json.Type.Bool: dst.put(cast(bool)json ? "true" : "false"); break;
1102 		case Json.Type.Int: formattedWrite(dst, "%d", json.get!long); break;
1103 		case Json.Type.Float: formattedWrite(dst, "%.16g", json.get!double); break;
1104 		case Json.Type.String:
1105 			dst.put("\"");
1106 			jsonEscape(dst, cast(string)json);
1107 			dst.put("\"");
1108 			break;
1109 		case Json.Type.Array:
1110 			dst.put("[");
1111 			bool first = true;
1112 			foreach( e; json ){
1113 				if( e.type == Json.Type.Undefined ) continue;
1114 				if( !first ) dst.put(",");
1115 				first = false;
1116 				dst.put("\n");
1117 				foreach( tab; 0 .. level+1 ) dst.put('\t');
1118 				writePrettyJsonString(dst, e, level+1);
1119 			}
1120 			if( json.length > 0 ) {
1121 				dst.put('\n');
1122 				foreach( tab; 0 .. level ) dst.put('\t');
1123 			}
1124 			dst.put("]");
1125 			break;
1126 		case Json.Type.Object:
1127 			dst.put("{");
1128 			bool first = true;
1129 			foreach( string k, e; json ){
1130 				if( e.type == Json.Type.Undefined ) continue;
1131 				if( !first ) dst.put(",");
1132 				dst.put("\n");
1133 				first = false;
1134 				foreach( tab; 0 .. level+1 ) dst.put('\t');
1135 				dst.put("\"");
1136 				jsonEscape(dst, k);
1137 				dst.put("\": ");
1138 				writePrettyJsonString(dst, e, level+1);
1139 			}
1140 			if( json.length > 0 ) {
1141 				dst.put('\n');
1142 				foreach( tab; 0 .. level ) dst.put('\t');
1143 			}
1144 			dst.put("}");
1145 			break;
1146 	}
1147 }
1148 
1149 /// private
1150 private void jsonEscape(R)(ref R dst, string s)
1151 {
1152 	foreach( ch; s ){
1153 		switch(ch){
1154 			default: dst.put(ch); break;
1155 			case '\\': dst.put("\\\\"); break;
1156 			case '\r': dst.put("\\r"); break;
1157 			case '\n': dst.put("\\n"); break;
1158 			case '\t': dst.put("\\t"); break;
1159 			case '\"': dst.put("\\\""); break;
1160 		}
1161 	}
1162 }
1163 
1164 /// private
1165 private string jsonUnescape(R)(ref R range)
1166 {
1167 	auto ret = appender!string();
1168 	while(!range.empty){
1169 		auto ch = range.front;
1170 		switch( ch ){
1171 			case '"': return ret.data;
1172 			case '\\':
1173 				range.popFront();
1174 				enforce(!range.empty, "Unterminated string escape sequence.");
1175 				switch(range.front){
1176 					default: enforce("Invalid string escape sequence."); break;
1177 					case '"': ret.put('\"'); range.popFront(); break;
1178 					case '\\': ret.put('\\'); range.popFront(); break;
1179 					case '/': ret.put('/'); range.popFront(); break;
1180 					case 'b': ret.put('\b'); range.popFront(); break;
1181 					case 'f': ret.put('\f'); range.popFront(); break;
1182 					case 'n': ret.put('\n'); range.popFront(); break;
1183 					case 'r': ret.put('\r'); range.popFront(); break;
1184 					case 't': ret.put('\t'); range.popFront(); break;
1185 					case 'u':
1186 						range.popFront();
1187 						dchar uch = 0;
1188 						foreach( i; 0 .. 4 ){
1189 							uch *= 16;
1190 							enforce(!range.empty, "Unicode sequence must be '\\uXXXX'.");
1191 							auto dc = range.front;
1192 							range.popFront();
1193 							if( dc >= '0' && dc <= '9' ) uch += dc - '0';
1194 							else if( dc >= 'a' && dc <= 'f' ) uch += dc - 'a' + 10;
1195 							else if( dc >= 'A' && dc <= 'F' ) uch += dc - 'A' + 10;
1196 							else enforce(false, "Unicode sequence must be '\\uXXXX'.");
1197 						}
1198 						ret.put(uch);
1199 						break;
1200 				}
1201 				break;
1202 			default:
1203 				ret.put(ch);
1204 				range.popFront();
1205 				break;
1206 		}
1207 	}
1208 	return ret.data;
1209 }
1210 
1211 private string skipNumber(ref string s, out bool is_float)
1212 {
1213 	size_t idx = 0;
1214 	is_float = false;
1215 	if( s[idx] == '-' ) idx++;
1216 	if( s[idx] == '0' ) idx++;
1217 	else {
1218 		enforce(isDigit(s[idx++]), "Digit expected at beginning of number.");
1219 		while( idx < s.length && isDigit(s[idx]) ) idx++;
1220 	}
1221 
1222 	if( idx < s.length && s[idx] == '.' ){
1223 		idx++;
1224 		is_float = true;
1225 		while( idx < s.length && isDigit(s[idx]) ) idx++;
1226 	}
1227 
1228 	if( idx < s.length && (s[idx] == 'e' || s[idx] == 'E') ){
1229 		idx++;
1230 		is_float = true;
1231 		if( idx < s.length && (s[idx] == '+' || s[idx] == '-') ) idx++;
1232 		enforce( idx < s.length && isDigit(s[idx]), "Expected exponent." ~ s[0 .. idx]);
1233 		idx++;
1234 		while( idx < s.length && isDigit(s[idx]) ) idx++;
1235 	}
1236 
1237 	string ret = s[0 .. idx];
1238 	s = s[idx .. $];
1239 	return ret;
1240 }
1241 
1242 private string skipJsonString(ref string s, int* line = null)
1243 {
1244 	enforce(s.length >= 2 && s[0] == '\"', "too small: '" ~ s ~ "'");
1245 	s = s[1 .. $];
1246 	string ret = jsonUnescape(s);
1247 	enforce(s.length > 0 && s[0] == '\"', "Unterminated string literal.");
1248 	s = s[1 .. $];
1249 	return ret;
1250 }
1251 
1252 private void skipWhitespace(ref string s, int* line = null)
1253 {
1254 	while( s.length > 0 ){
1255 		switch( s[0] ){
1256 			default: return;
1257 			case ' ', '\t': s = s[1 .. $]; break;
1258 			case '\n':
1259 				s = s[1 .. $];
1260 				if( s.length > 0 && s[0] == '\r' ) s = s[1 .. $];
1261 				if( line ) (*line)++;
1262 				break;
1263 			case '\r':
1264 				s = s[1 .. $];
1265 				if( s.length > 0 && s[0] == '\n' ) s = s[1 .. $];
1266 				if( line ) (*line)++;
1267 				break;
1268 		}
1269 	}
1270 }
1271 
1272 /// private
1273 private bool isDigit(T)(T ch){ return ch >= '0' && ch <= '9'; }
1274 
1275 private string underscoreStrip(string field_name)
1276 {
1277 	if( field_name.length < 1 || field_name[$-1] != '_' ) return field_name;
1278 	else return field_name[0 .. $-1];
1279 }