1 /**
2  * LibDJSONcontains functions and classes for reading, parsing, and writing JSON
3  * documents.
4  *
5  * Copyright:	(c) 2009 William K. Moore, III (nyphbl8d (at) gmail (dot) com, opticron on freenode)
6  * Authors:	William K. Moore, III
7  * License:	Boost Software License - Version 1.0 - August 17th, 2003
8 
9  Permission is hereby granted, free of charge, to any person or organization
10  obtaining a copy of the software and accompanying documentation covered by
11  this license (the "Software") to use, reproduce, display, distribute,
12  execute, and transmit the Software, and to prepare derivative works of the
13  Software, and to permit third-parties to whom the Software is furnished to
14  do so, all subject to the following:
15 
16  The copyright notices in the Software and this entire statement, including
17  the above license grant, this restriction and the following disclaimer,
18  must be included in all copies of the Software, in whole or in part, and
19  all derivative works of the Software, unless such copies or derivative
20  works are solely in the form of machine-executable object code generated by
21  a source language processor.
22 
23  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25  FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
26  SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
27  FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
28  ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
29  DEALINGS IN THE SOFTWARE.
30 
31  * Standards:	Attempts to conform to the subset of Javascript required to implement the JSON Specification
32  */
33 
34 /* My TODO list:
35  *	implement some kind of XPath like search ability
36  */
37 module libdjson.json;
38 version(Tango) {
39 	import tango.text.Util:isspace=isSpace,stripl=triml,strip=trim,stripr=trimr,find=locatePattern,split,replace=substitute;
40 	import tango.text.convert.Integer:tostring=toString,agToULong=toLong;
41 	import tango.text.convert.Float:tostring=toString,agToFloat=toFloat;
42 	import tango.text.Ascii:icmp=icompare,cmp=compare;
43 	import tango.io.Stdout:writef=Stdout;
44 	import tango.text.Regex;
45 	alias char[] string;
46 	string regrep(string input,string pattern,string delegate(string) translator) {
47 		string tmpdel(RegExpT!(char) m) {
48 			return translator(m.match(0));
49 		}
50 		auto rgxp = Regex(pattern,"g");
51 		return rgxp.replaceAll(input,&tmpdel);
52 	}
53 } else {
54 	version(D_Version2) {
55 		import std.conv:to;
56 		import std.string:strip,stripr=stripRight,stripl=stripLeft,split,replace,find=indexOf,cmp,icmp;
57 		import std.ascii:isspace=isWhite;
58 		real agToFloat(string data) {
59 			return to!(real)(data);
60 		}
61 		ulong agToULong(string data) {
62 			return to!(ulong)(data);
63 		}
64 		string tostring(real data) {
65 			return to!(string)(data);
66 		}
67 		string tostring(long data) {
68 			return to!(string)(data);
69 		}
70 		import std.traits : isSomeString, isNumeric;
71 		import std.regex;
72 		string regrep(string input, string pattern, string delegate(string) translator) {
73 			string tmpdel(Captures!(string) m) {
74 				return translator(m.hit);
75 			}
76 			return std.regex.replace!(tmpdel)(input, regex(pattern, "g"));
77 		}
78 	} else {
79 		import std.string:tostring=toString,strip,stripr,stripl,split,replace,find,cmp,icmp;
80 		import std.conv:agToULong=toUlong,agToFloat=toReal;
81 		import std.ctype:isspace;
82 		import std.regexp:sub,RegExp;
83 		string regrep(string input,string pattern,string delegate(string) translator) {
84 			string tmpdel(RegExp m) {
85 				return translator(m.match(0));
86 			}
87 			return sub(input,pattern,&tmpdel,"g");
88 		}
89 	}
90 	import std.stdio:writef;
91 }
92 
93 /**
94  * Read an entire string into a JSON tree.
95  * Example:
96  * --------------------------------
97  * auto root = new JSONObject();
98  * auto arr = new JSONArray();
99  * arr ~= new JSONString("da blue teeths!\"\\");
100  * root["what is that on your ear?"] = arr;
101  * root["my pants"] = new JSONString("are on fire");
102  * root["i am this many"] = new JSONNumber(10.253);
103  * string jstr = root.toString;
104  * writef("Unit Test libDJSON JSON creation...\n");
105  * writef("Generated JSON string: ");writef(jstr);writef("\n");
106  * writef("Regenerated JSON string: ");writef(readJSON(jstr).toString);writef("\n");
107  * --------------------------------
108  * Returns: A JSONObject with no name that is the root of the document that was read.
109  * Throws: JSONError on any parsing errors.
110  */
111 JSONType readJSON(string src) {
112 	string pointcpy = src;
113 	JSONType root = null;
114 	src = src.stripl();
115 	root = parseHelper(src);
116 	return root;
117 }
118 
119 /// An exception thrown on JSON parsing errors.
120 class JSONError : Exception {
121 	// Throws an exception with an error message.
122 	this(string msg) {
123 		super(msg);
124 	}
125 }
126 
127 /// This is the interface implemented by all classes that represent JSON objects.
128 abstract class JSONType {
129 	override abstract string toString();
130 	abstract string toPrettyString(string indent=null);
131 	/// The parse method of this interface should ALWAYS be destructive, removing things from the front of source as it parses.
132 	abstract void parse(ref string source);
133 	/// Convenience function for casting to JSONObject
134 	/// Returns: The casted object or null if the cast fails
135 	JSONObject toJSONObject(){return cast(JSONObject)this;}
136 	/// Convenience function for casting to JSONArray
137 	/// Returns: The casted object or null if the cast fails
138 	JSONArray toJSONArray(){return cast(JSONArray)this;}
139 	/// Convenience function for casting to JSONString
140 	/// Returns: The casted object or null if the cast fails
141 	JSONString toJSONString(){return cast(JSONString)this;}
142 	/// Convenience function for casting to JSONBoolean
143 	/// Returns: The casted object or null if the cast fails
144 	JSONBoolean toJSONBoolean(){return cast(JSONBoolean)this;}
145 	/// Convenience function for casting to JSONNumber
146 	/// Returns: The casted object or null if the cast fails
147 	JSONNumber toJSONNumber(){return cast(JSONNumber)this;}
148 	/// Convenience function for casting to JSONNull
149 	/// Returns: The casted object or null if the cast fails
150 	JSONNull toJSONNull(){return cast(JSONNull)this;}
151 	/// Associative array index function for objects describing associative array-like attributes.
152 	/// Returns: The chosen index or a null reference if the index does not exist.
153 	JSONType opIndex(string key) {
154 		throw new JSONError(typeof(this).stringof ~" does not support string indexing, check your JSON structure.");
155 	}
156 	/// Allow foreach over the object with string key and ref value.
157 	int opApply(int delegate(ref string,ref JSONType) dg) {
158 		throw new JSONError(typeof(this).stringof ~" does not support string index foreach, check your JSON structure.");
159 	}
160 	/// Array index function for objects describing array-like attributes.
161 	/// Returns: The chosen index or a null reference if the index does not exist.
162 	JSONType opIndex(int key) {
163 		throw new JSONError(typeof(this).stringof ~" does not support integer indexing, check your JSON structure.");
164 	}
165 	/// Allow foreach over the object with integer key and ref value.
166 	int opApply(int delegate(ref ulong,ref JSONType) dg) {
167 		throw new JSONError(typeof(this).stringof ~" does not support numeric index foreach, check your JSON structure.");
168 	}
169 	/// Convenience function for iteration that apply to both AA and array type operations with ref value
170 	int opApply(int delegate(ref JSONType) dg) {
171 		throw new JSONError(typeof(this).stringof ~" does not support foreach, check your JSON structure.");
172 	}
173 	/// Allow "in" operator to work as expected for object types without an explicit cast
174 	JSONType*opIn_r(string key) {
175 		throw new JSONError(typeof(this).stringof ~" does not support opIn, check your JSON structure.");
176 	}
177 }
178 /**
179  * JSONObject represents a single JSON object node and has methods for 
180  * adding children.  All methods that make changes modify this
181  * JSONObject rather than making a copy, unless otherwise noted.  Many methods
182  * return a self reference to allow cascaded calls.
183  */
184 class JSONObject:JSONType {
185 	/// Nothing to see here except for the boring constructor, move along.
186 	this(){}
187 	protected JSONType[string] _children;
188 	
189 	
190 	version(D_Version2) {
191 		/// Operator overload for setting keys in the AA.
192 		mixin("void opIndexAssign(T)(T type,string key) if(is(T : JSONType) && !is(T == typeof(null))) {
193 			_children[key] = type;
194 		}
195 		/// ditto
196 		void opIndexAssign(T)(T val, string key) if(isSomeString!T && !is(T == typeof(null))) {
197 			_children[key] = new JSONString(cast(string)val);
198 		}
199 		/// ditto
200 		void opIndexAssign(T)(T val, string key) if(isNumeric!T) {
201 			_children[key] = new JSONNumber(val);
202 		}
203 		/// ditto
204 		void opIndexAssign(T)(T val, string key) if(is(T == bool)) {
205 			_children[key] = new JSONBoolean(val);
206 		}
207 		/// ditto
208 		void opIndexAssign(T)(T val, string key) if(is(T == typeof(null))) {
209 			_children[key] = new JSONNull();
210 		}");
211 	} else {
212 		/// Operator overload for setting keys in the AA.
213 		void opIndexAssign(JSONType type,string key) {
214 			_children[key] = type;
215 		}
216 		/// ditto
217 		void opIndexAssign(string val, string key) {
218 			_children[key] = new JSONString(cast(string)val);
219 		}
220 		/// ditto
221 		void opIndexAssign(bool val, string key) {
222 			_children[key] = new JSONBoolean(val);
223 		}
224 		/// ditto
225 		void opIndexAssign(long val, string key) {
226 			_children[key] = new JSONNumber(val);
227 		}
228 		/// ditto
229 		void opIndexAssign(real val, string key) {
230 			_children[key] = new JSONNumber(val);
231 		}
232 		/// ditto
233 		void opIndexAssign(int val, string key) {
234 			_children[key] = new JSONNumber(val);
235 		}
236 	}
237 	
238 	/// Operator overload for accessing values already in the AA.
239 	/// Returns: The child node if it exists, otherwise null.
240 	override JSONType opIndex(string key) {
241 		return (key in _children)?_children[key]:null;
242 	}
243 	/// Allow the user to get the number of elements in this object
244 	/// Returns: The number of child nodes contained within this JSONObject
245 	ulong length() {return _children.length;}
246 	/// Operator overload for foreach iteration through the object with values only and allow modification of the reference
247 	override int opApply(int delegate(ref JSONType) dg) {
248 		int res;
249 		foreach(ref child;_children) {
250 			res = dg(child);
251 			if (res) return res;
252 		}
253 		return 0;
254 	}
255 	/// Operator overload for foreach iteration through the object with key and value and allow modification of the reference
256 	override int opApply(int delegate(ref string,ref JSONType) dg) {
257 		int res;
258 		foreach(key,ref child;_children) {
259 			res = dg(key,child);
260 			if (res) return res;
261 		}
262 		return 0;
263 	}
264 
265 	/// A method to convert this JSONObject to a user-readable format.
266 	/// Returns: A JSON string representing this object and it's contents.
267 	override string toString() {
268 		string ret;
269 		ret ~= "{";
270 		foreach (key,val;_children) {
271 			ret ~= "\""~JSONEncode(key)~"\":"~val.toString~",";
272 		}
273 		// rip off the trailing comma, we don't need it
274 		if (_children.length) ret = ret[0..$-1];
275 		ret ~= "}";
276 		return ret;
277 	}
278 
279 	/// A method to convert this JSONObject to a formatted, user-readable format.
280 	/// Returns: A JSON string representing this object and it's contents.
281 	override string toPrettyString(string indent=null) {
282 		string ret;
283 		ret ~= "{\n";
284 		foreach (key,val;_children) {
285 			ret ~= indent~"	\""~JSONEncode(key)~"\":"~val.toPrettyString(indent~"	")~",\n";
286 		}
287 		// rip off the trailing comma, we don't need it
288 		if (_children.length) ret = ret[0..$-2]~"\n";
289 		ret ~= indent~"}";
290 		return ret;
291 	}
292 
293 	/// This function parses a JSONObject out of a string
294 	override void parse(ref string source) {
295 		// make sure the first byte is {
296 		if (source[0] != '{') throw new JSONError("Missing open brace '{' at start of JSONObject parse: "~source);
297 		// rip off the leading {
298 		source = stripl(source[1..$]);
299 		while (source[0] != '}') {
300 			if (source[0] != '"') throw new JSONError("Missing open quote for element key before: "~source);
301 			// use JSONString class to help us out here (read, I'm lazy :D)
302 			auto jstr = new JSONString();
303 			jstr.parse(source);
304 			source = stripl(source);
305 			if (source[0] != ':') throw new JSONError("Missing ':' after keystring in object before: "~source);
306 			source = stripl(source[1..$]);
307 			_children[jstr.get] = parseHelper(source);
308 			source = stripl(source);
309 			// handle end cases
310 			if (source[0] == '}') continue;
311 			if (source[0] != ',') throw new JSONError("Missing continuation via ',' or end of JSON object via '}' before "~source);
312 			// rip the , in preparation for the next loop
313 			source = stripl(source[1..$]);
314 			// make sure we don't have a ",}", since I'm assuming it's not allowed
315 			if (source[0] == '}') throw new JSONError("Empty array elements (',' followed by '}') are not allowed. Fill the space or remove the comma.\nThis error occurred before: "~source);
316 		}
317 		// rip off the } and be done with it
318 		source = stripl(source[1..$]);
319 	}
320 	/// Allow "in" operator to work as expected for object types without an explicit cast
321 	override JSONType*opIn_r(string key) {
322 		return key in _children;
323 	}
324 }
325 
326 /// JSONArray represents a single JSON array, capable of being heterogenous
327 class JSONArray:JSONType {
328 	protected JSONType[] _children;
329 	
330 	version(D_Version2) {
331 		mixin("
332 		this(T...)(T args) {
333 			foreach(arg; args) {
334 				this ~= arg;
335 			}
336 		}
337 		/// Operator overload to allow addition of children
338 		void opCatAssign(T)(T child) if(is(T : JSONType)) {
339 			_children ~= child;
340 		}
341 		/// ditto
342 		void opCatAssign(T)(T val) if(isSomeString!T && !is(T == typeof(null))) {
343 			_children ~= new JSONString(cast(string)val);
344 		}
345 		/// ditto
346 		void opCatAssign(T)(T val) if(isNumeric!T) {
347 			_children ~= new JSONNumber(val);
348 		}
349 		/// ditto
350 		void opCatAssign(T)(T val) if(is(T == bool)) {
351 			_children ~= new JSONBoolean(val);
352 		}
353 		/// ditto
354 		void opCatAssign(T)(T val) if(is(T == typeof(null))) {
355 			_children ~= new JSONNull();
356 		}");
357 	} else {
358 		this(){}
359 		/// Operator overload to allow addition of children
360 		void opCatAssign(JSONType child) {
361 			_children ~= child;
362 		}
363 		/// ditto
364 		void opCatAssign(string val) {
365 			_children ~= new JSONString(cast(string)val);
366 		}
367 		/// ditto
368 		void opCatAssign(bool val) {
369 			_children ~= new JSONBoolean(val);
370 		}
371 		/// ditto
372 		void opCatAssign(long val) {
373 			_children ~= new JSONNumber(val);
374 		}
375 		/// ditto
376 		void opCatAssign(real val) {
377 			_children ~= new JSONNumber(val);
378 		}
379 		/// ditto
380 		void opCatAssign(int val) {
381 			_children ~= new JSONNumber(val);
382 		}
383 	}
384 	
385 	/// Operator overload to allow access of children
386 	/// Returns: The child node if it exists, otherwise null.
387 	override JSONType opIndex(int key) {
388 		return _children[key];
389 	}
390 	/// Allow the user to get the number of elements in this object
391 	/// Returns: The number of child nodes contained within this JSONObject
392 	ulong length() {return _children.length;}
393 	/// Operator overload for foreach iteration through the array with values only and allow modification of the reference
394 	override int opApply(int delegate(ref JSONType) dg) {
395 		int res;
396 		foreach(ref child;_children) {
397 			res = dg(child);
398 			if (res) return res;
399 		}
400 		return 0;
401 	}
402 	/// Operator overload for foreach iteration through the array with key and value and allow modification of the reference
403 	override int opApply(int delegate(ref ulong,ref JSONType) dg) {
404 		int res;
405 		ulong tmp;
406 		foreach(key,ref child;_children) {
407 			tmp = key;
408 			res = dg(tmp,child);
409 			if (res) return res;
410 		}
411 		return 0;
412 	}
413 
414 	/// A method to convert this JSONArray to a user-readable format.
415 	/// Returns: A JSON string representing this object and it's contents.
416 	override string toString() {
417 		string ret;
418 		ret ~= "[";
419 		foreach (val;_children) {
420 			ret ~= val.toString~",";
421 		}
422 		// rip off the trailing comma, we don't need it
423 		if (_children.length) ret = ret[0..$-1];
424 		ret ~= "]";
425 		return ret;
426 	}
427 
428 	/// A method to convert this JSONArray to a formatted, user-readable format.
429 	/// Returns: A JSON string representing this object and it's contents.
430 	override string toPrettyString(string indent=null) {
431 		string ret;
432 		ret ~= "[\n";
433 		foreach (val;_children) {
434 			ret ~= indent~"	"~val.toPrettyString(indent~"	")~",\n";
435 		}
436 		// rip off the trailing comma, we don't need it
437 		if (_children.length) ret = ret[0..$-2]~"\n";
438 		ret ~= indent~"]";
439 		return ret;
440 	}
441 
442 	/// This function parses a JSONArray out of a string
443 	override void parse(ref string source) {
444 		if (source[0] != '[') throw new JSONError("Missing open brace '[' at start of JSONArray parse: "~source);
445 		// rip off the leading [
446 		source = stripl(source[1..$]);
447 		while (source[0] != ']') {
448 			_children ~= parseHelper(source);
449 			source = stripl(source);
450 			// handle end cases
451 			if (source[0] == ']') continue;
452 			if (source[0] != ',') throw new JSONError("Missing continuation via ',' or end of JSON array via ']' before "~source);
453 			// rip the , in preparation for the next loop
454 			source = stripl(source[1..$]);
455 			// take care of trailing null entries
456 			if (source[0] == ']') _children ~= new JSONNull();
457 		}
458 		// rip off the ] and be done with it
459 		source = stripl(source[1..$]);
460 	}
461 }
462 
463 /// JSONString represents a JSON string.  Internal representation is escaped for faster parsing and JSON generation.
464 class JSONString:JSONType {
465 	/// The boring default constructor.
466 	this(){}
467 	/// The ever so slightly more interesting initializing constructor.
468 	this(string data) {set(data);}
469 	protected string _data;
470 	/// Allow the data to be set so the object can be reused.
471 	void set(string data) {_data = JSONEncode(data);}
472 	/// Allow the data to be retreived.
473 	string get() {return JSONDecode(_data);}
474 
475 	/// A method to convert this JSONString to a user-readable format.
476 	/// Returns: A JSON string representing this object and it's contents.
477 	override string toString() {
478 		return "\""~_data~"\"";
479 	}
480 
481 	/// A method to convert this JSONString to a formatted, user-readable format.
482 	/// Returns: A JSON string representing this object and it's contents.
483 	override string toPrettyString(string indent=null) {
484 		return toString;
485 	}
486 
487 	/// This function parses a JSONArray out of a string and eats characters as it goes, hence the ref string parameter.
488 	override void parse(ref string source) {
489 		if (source[0] != '"') throw new JSONError("Missing open quote '\"' at start of JSONArray parse: "~source);
490 		// rip off the leading [
491 		source = source[1..$];
492 		// scan to find the closing quote
493 		int bscount = 0;
494 		int sliceloc = -1;
495 		for(int i = 0;i<source.length;i++) {
496 			switch(source[i]) {
497 			case '\\':
498 				bscount++;
499 				continue;
500 			case '"':
501 				// if the count is even, backslashes cancel and we have the end of the string, otherwise cascade
502 				if (bscount%2 == 0) {
503 					break;
504 				}
505 				goto default;
506 			default:
507 				bscount = 0;
508 				continue;
509 			}
510 			// we have reached the terminating case! huzzah!
511 			sliceloc = i;
512 			break;
513 		}
514 		// take care of failure to find the end of the string
515 		if (sliceloc == -1) throw new JSONError("Unable to find the end of the JSON string starting here: "~source);
516 		_data = source[0..sliceloc];
517 		// eat the " that is known to be there
518 		source = stripl(source[sliceloc+1..$]);
519 	}
520 }
521 
522 /// JSONBoolean represents a JSON boolean value.
523 class JSONBoolean:JSONType {
524 	/// The boring constructor, again.
525 	this(){}
526 	/// Only a bit of input for this constructor.
527 	this(bool data) {_data = data;}
528 	/// Allow setting of the hidden bit.
529 	void set(bool data) {_data = data;}
530 	/// Allow the bit to be retreived.
531 	bool get() {return _data;}
532 	protected bool _data;
533 
534 	/// A method to convert this JSONBoolean to a user-readable format.
535 	/// Returns: A JSON string representing this object and it's contents.
536 	override string toString() {
537 		if (_data) return "true";
538 		return "false";
539 	}
540 
541 	/// A method to convert this JSONBoolean to a formatted, user-readable format.
542 	/// Returns: A JSON string representing this object and it's contents.
543 	override string toPrettyString(string indent=null) {
544 		return toString;
545 	}
546 
547 	/// This function parses a JSONBoolean out of a string and eats characters as it goes, hence the ref string parameter.
548 	override void parse(ref string source) {
549 		if (source[0..4] == "true") {
550 			source = stripl(source[4..$]);
551 			set(true);
552 		} else if (source[0..5] == "false") {
553 			source = stripl(source[5..$]);
554 			set(false);
555 		} else throw new JSONError("Could not parse JSON boolean variable from: "~source);
556 	}
557 }
558 
559 /// JSONNull represents a JSON null value.
560 class JSONNull:JSONType {
561 	/// You're forced to use the boring constructor here.
562 	this(){}
563 
564 	/// A method to convert this JSONNull to a user-readable format.
565 	/// Returns: "null". Always. Forever.
566 	override string toString() {
567 		return "null";
568 	}
569 
570 	/// A method to convert this JSONNull to a formatted, user-readable format.
571 	/// Returns: "null". Always. Forever.
572 	override string toPrettyString(string indent=null) {
573 		return toString;
574 	}
575 
576 	/// This function parses a JSONNull out of a string.  Really, it just rips "null" off the beginning of the string and eats whitespace.
577 	override void parse(ref string source) in { assert(source[0..4] == "null"); } body {
578 		source = stripl(source[4..$]);
579 	}
580 }
581 
582 /// JSONNumber represents any JSON numeric value.
583 class JSONNumber:JSONType {
584 	/// Another boring constructor...
585 	this(){}
586 	/// ...and its slightly less boring sibling.
587 	this(real data) {_data = tostring(data);}
588 	this(long data) {_data = tostring(data);}
589 	this(int data) {_data = tostring(cast(long)data);}
590 	/// Allow setting of the hidden number.
591 	void set(real data) {_data = tostring(data);}
592 	void set(long data) {_data = tostring(data);}
593 	void set(int data) {_data = tostring(cast(long)data);}
594 	/// Allow the number to be retreived.
595 	real get() {return agToFloat(_data);}
596 	long getLong() {return agToULong(_data);}
597 	real getReal() {return agToULong(_data);}
598 	protected string _data;
599 
600 	/// A method to convert this JSONNumber to a user-readable format.
601 	/// Returns: A JSON string representing this number.
602 	override string toString() {
603 		return _data;
604 	}
605 
606 	/// A method to convert this JSONNumber to a formatted, user-readable format.
607 	/// Returns: A JSON string representing this number.
608 	override string toPrettyString(string indent=null) {
609 		return toString;
610 	}
611 
612 	/// This function parses a JSONNumber out of a string and eats characters as it goes, hence the ref string parameter.
613 	override void parse(ref string source) {
614 		// this parser sucks...
615 		int i = 0;
616 		// check for leading minus sign
617 		if (source[i] == '-') i++;
618 		// sift through whole numerics
619 		if (source[i] == '0') {
620 			i++;
621 		} else if (source[i] <= '9' && source[i] >= '1') {
622 			while (source[i] >= '0' && source[i] <= '9') i++;
623 		} else throw new JSONError("A numeric parse error occurred while parsing the numeric beginning at: "~source);
624 		// if the next char is not a '.', we know we're done with fractional parts 
625 		if (source[i] == '.') {
626 			i++;
627 			while (source[i] >= '0' && source[i] <= '9') i++;
628 		}
629 		// if the next char is e or E, we're poking at an exponential
630 		if (source[i] == 'e' || source[i] == 'E') {
631 			i++;
632 			if (source[i] == '-' || source[i] == '+') i++;
633 			while (source[i] >= '0' && source[i] <= '9') i++;
634 		}
635 		_data = source[0..i];
636 		source = stripl(source[i..$]);
637 	}
638 }
639 
640 private JSONType parseHelper(ref string source) {
641 	JSONType ret;
642 	switch(source[0]) {
643 	case '{':
644 		ret = new JSONObject();
645 		break;
646 	case '[':
647 		ret = new JSONArray();
648 		break;
649 	case '"':
650 		ret = new JSONString();
651 		break;
652 	case '-','0','1','2','3','4','5','6','7','8','9':
653 		ret = new JSONNumber();
654 		break;
655 	default:
656 		if (source.length >= 4 && source[0..4] == "null") ret = new JSONNull();
657 		else if ((source.length >= 4 && source[0..4] == "true") || (source.length >= 5 && source[0..5] == "false")) ret = new JSONBoolean();
658 		else if (source.length && source[0] == ',') return new JSONNull(); // no parse here
659 		else throw new JSONError("Unable to determine type of next element beginning: "~source);
660 		break;
661 	}
662 	ret.parse(source);
663 	return ret;
664 }
665 
666 /// Perform JSON escapes on a string
667 /// Returns: A JSON encoded string
668 string JSONEncode(string src) {
669 	string tempStr;
670         tempStr = replace(src    , "\\", "\\\\");
671         tempStr = replace(tempStr, "\"", "\\\"");
672         return tempStr;
673 }
674 
675 /// Unescape a JSON string
676 /// Returns: A decoded string.
677 string JSONDecode(string src) {
678 	string tempStr;
679         tempStr = replace(src    , "\\\\", "\\");
680         tempStr = replace(tempStr, "\\\"", "\"");
681         tempStr = replace(tempStr, "\\/", "/");
682         tempStr = replace(tempStr, "\\n", "\n");
683         tempStr = replace(tempStr, "\\r", "\r");
684         tempStr = replace(tempStr, "\\f", "\f");
685         tempStr = replace(tempStr, "\\t", "\t");
686         tempStr = replace(tempStr, "\\b", "\b");
687 	// take care of hex character entities
688 	// XXX regex is broken in tango 0.99.9 which means this doesn't work right when numbers enter the mix
689 	version(D_Version2) {
690 		tempStr = regrep(tempStr,"\\\\u[0-9a-fA-F]{4};",(string m) {
691 			auto cnum = m[3..$-1];
692 			dchar dnum = hex2dchar(cnum[1..$]);
693 			return quickUTF8(dnum);
694 		});
695 	} else {
696 		tempStr = regrep(tempStr,"\\u[0-9a-fA-F]{4};",(string m) {
697 			auto cnum = m[3..$-1];
698 			dchar dnum = hex2dchar(cnum[1..$]);
699 			return quickUTF8(dnum);
700 		});
701 	}
702         return tempStr;
703 }
704 
705 /// This probably needs documentation.  It looks like it converts a dchar to the necessary length string of chars.
706 string quickUTF8(dchar dachar) {
707 	char[] ret;
708 	if (dachar <= 0x7F) {
709 		ret.length = 1;
710 		ret[0] = cast(char) dachar;
711 	} else if (dachar <= 0x7FF) {
712 		ret.length = 2;
713 		ret[0] = cast(char)(0xC0 | (dachar >> 6));
714 		ret[1] = cast(char)(0x80 | (dachar & 0x3F));
715 	} else if (dachar <= 0xFFFF) {
716 		ret.length = 3;
717 		ret[0] = cast(char)(0xE0 | (dachar >> 12));
718 		ret[1] = cast(char)(0x80 | ((dachar >> 6) & 0x3F));
719 		ret[2] = cast(char)(0x80 | (dachar & 0x3F));
720 	} else if (dachar <= 0x10FFFF) {
721 		ret.length = 4;
722 		ret[0] = cast(char)(0xF0 | (dachar >> 18));
723 		ret[1] = cast(char)(0x80 | ((dachar >> 12) & 0x3F));
724 		ret[2] = cast(char)(0x80 | ((dachar >> 6) & 0x3F));
725 		ret[3] = cast(char)(0x80 | (dachar & 0x3F));
726 	} else {
727 	    assert(0);
728 	}
729 	return cast(string)ret;
730 }
731 private dchar hex2dchar (string hex) {
732 	dchar res;
733 	foreach(digit;hex) {
734 		res <<= 4;
735 		res |= toHVal(digit);
736 	}
737 	return res;
738 }
739 
740 private dchar toHVal(char digit) {
741 	if (digit >= '0' && digit <= '9') {
742 		return digit-'0';
743 	}
744 	if (digit >= 'a' && digit <= 'f') {
745 		return digit-'a';
746 	}
747 	if (digit >= 'A' && digit <= 'F') {
748 		return digit-'A';
749 	}
750 	return 0;
751 }
752 
753 unittest {
754 	auto root = new JSONObject();
755 	auto arr = new JSONArray();
756 	arr ~= new JSONString("da blue teeths!\"\\");
757 	root["what is that on your ear?"] = arr;
758 	root["my pants"] = new JSONString("are on fire");
759 	root["i am this many"] = new JSONNumber(10.253);
760 	root["blank"] = new JSONObject();
761 	string jstr = root.toString;
762 	writef("Unit Test libDJSON JSON creation...\n");
763 	writef("Generated JSON string: " ~ jstr ~ "\n");
764 	writef("Regenerated JSON string: " ~ readJSON(jstr).toString ~ "\n");
765 	writef("Output using toPrettyString:\n"~root.toPrettyString~"\nEnd pretty output\n");
766 	assert(jstr == readJSON(jstr).toString);
767 	writef("Unit Test libDJSON JSON parsing...\n");
768 	jstr = "{\"firstName\": \"John\",\"lastName\": \"Smith\",\"address\": {\"streetAddress\": \"21 2nd Street\",\"city\": \"New York\",\"state\": \"NY\",\"postalCode\": 10021},\"phoneNumbers\": [{ \"type\": \"home\", \"number\": \"212 555-1234\" },{ \"type\": \"fax\", \"number\": \"646 555-4567\" }],\"newSubscription\": false,\"companyName\": null }";
769 	writef("Sample JSON string: " ~ jstr ~ "\n");
770 	jstr = jstr.readJSON().toString;
771 	auto tmp = jstr.readJSON();
772 	writef("Parsed JSON string: " ~ jstr ~ "\n");
773 	writef("Output using toPrettyString:\n"~tmp.toPrettyString~"\nEnd pretty output\n");
774 	// ensure that the string doesn't mutate after a second reading, it shouldn't
775 	assert(tmp.toString == jstr);
776 	// ensure that pretty output still parses properly and doesn't mutate
777 	jstr = tmp.toPrettyString;
778 	tmp = jstr.readJSON();
779 	assert(tmp.toPrettyString == jstr);
780 	writef("Unit Test libDJSON JSON access...\n");
781 	writef("Got first name:" ~ tmp["firstName"].toJSONString.get ~ "\n");
782 	writef("Got last name:" ~ tmp["lastName"].toJSONString.get ~ "\n");
783 	writef("Unit Test libDJSON opApply interface...\n");
784 	foreach(obj;tmp["phoneNumbers"]) {
785 		writef("Got " ~ obj["type"].toJSONString.get ~ " phone number:" ~ obj["number"].toJSONString.get ~ "\n");
786 	}
787 	foreach(string name,JSONType obj;tmp) {
788 		writef("Got element name " ~ name ~ "\n");
789 	}
790 	writef("Unit Test libDJSON opIndex interface to ensure breakage where incorrectly used...\n");
791 	try {
792 		tmp[5];
793 		assert(false,"An exception should have been thrown on the line above.");
794 	} catch (Exception e) {/*shazam! program flow should get here, it is a correct thing*/}
795 	writef("Testing alternate base container and empty elements...\n");
796 	assert("[,,]".readJSON().toString == "[null,null,null]");
797 	jstr = 
798 " {
799 	\"realms\": [
800 		{
801 			\"type\": \"pve\",
802 			\"queue\": false,
803 			\"status\": true,
804 			\"population\": \"medium\",
805 			\"name\": \"Aerie Peak\",
806 			\"slug\": \"aerie-peak\"
807 		}
808 	]
809 }";
810 	tmp = jstr.readJSON();
811 	tmp["realms"].toJSONArray().length();
812 	writef("Testing opIn_r functionality...\n");
813 	assert("realms" in tmp);
814 	assert(!("bob" in tmp));
815 	
816 	
817 	writef("Testing autodetect type opAssign\n");
818 	root = new JSONObject();
819 	root["string"] = "str";
820 	assert(root["string"].toJSONString().get() == "str");
821 	root["int"] = 4;
822 	assert(root["int"].toJSONNumber().getLong() == 4);
823 	root["float"] = 4.5f;
824 	assert(root["float"].toJSONNumber().get() == 4.5f);
825 	version(D_Version2) {
826 		root["array"] = new JSONArray("foosh", "bar", 1);
827 		assert(root["array"].toJSONArray()[0].toJSONString().get == "foosh");
828 	}
829 	
830 	writef("Testing autodetect type opCatAssign\n");
831 	arr = new JSONArray();
832 	arr ~= "foo";
833 	assert(arr[0].toJSONString().get() == "foo");
834 	arr ~= 5;
835 	assert(arr[1].toJSONNumber().get() == 5);
836 }
837 
838 version(JSON_main) {
839 	void main(){}
840 }