/**
	JSON serialization and value handling.

	This module provides the Json struct for reading, writing and manipulating
	JSON values. De(serialization) of arbitrary D types is also supported and
	is recommended for handling JSON in performance sensitive applications.

	Copyright: © 2012-2015 RejectedSoftware e.K.
	License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file.
	Authors: Sönke Ludwig
*/
module dub.internal.vibecompat.data.json;

version (Have_vibe_d_data) public import vibe.data.json;
else:

import dub.internal.vibecompat.data.utils;

public import dub.internal.vibecompat.data.serialization;

public import std.json : JSONException;
import std.algorithm : equal, min;
import std.array;
import std.conv;
import std.datetime;
import std.exception;
import std.format;
import std.range;
import std.string : format;
import std.traits;

version = JsonLineNumbers;
version = VibeJsonFieldNames;


/******************************************************************************/
/* public types                                                               */
/******************************************************************************/

/**
	Represents a single JSON value.

	Json values can have one of the types defined in the Json.Type enum. They
	behave mostly like values in ECMA script in the way that you can
	transparently perform operations on them. However, strict typechecking is
	done, so that operations between differently typed JSON values will throw
	a JSONException. Additionally, an explicit cast or using get!() or to!() is
	required to convert a JSON value to the corresponding static D type.
*/
struct Json {
	private {
		// putting all fields in a union results in many false pointers leading to
		// memory leaks and, worse, std.algorithm.swap triggering an assertion
		// because of internal pointers. This crude workaround seems to fix
		// the issues.
		void*[2] m_data;
		ref inout(T) getDataAs(T)() inout { static assert(T.sizeof <= m_data.sizeof); return *cast(inout(T)*)m_data.ptr; }
		@property ref inout(long) m_int() inout { return getDataAs!long(); }
		@property ref inout(double) m_float() inout { return getDataAs!double(); }
		@property ref inout(bool) m_bool() inout { return getDataAs!bool(); }
		@property ref inout(string) m_string() inout { return getDataAs!string(); }
		@property ref inout(Json[string]) m_object() inout { return getDataAs!(Json[string])(); }
		@property ref inout(Json[]) m_array() inout { return getDataAs!(Json[])(); }

		Type m_type = Type.undefined;

		version (VibeJsonFieldNames) {
			uint m_magic = 0x1337f00d; // works around Appender bug (DMD BUG 10690/10859/11357)
			string m_name;
			string m_fileName;
		}
	}

	/** Represents the run time type of a JSON value.
	*/
	enum Type {
		undefined,  /// A non-existent value in a JSON object
		null_,      /// Null value
		bool_,      /// Boolean value
		int_,       /// 64-bit integer value
		float_,     /// 64-bit floating point value
		string,     /// UTF-8 string
		array,      /// Array of JSON values
		object,     /// JSON object aka. dictionary from string to Json

		Undefined = undefined,  /// Compatibility alias - will be deprecated soon
		Null = null_,           /// Compatibility alias - will be deprecated soon
		Bool = bool_,           /// Compatibility alias - will be deprecated soon
		Int = int_,             /// Compatibility alias - will be deprecated soon
		Float = float_,         /// Compatibility alias - will be deprecated soon
		String = string,        /// Compatibility alias - will be deprecated soon
		Array = array,          /// Compatibility alias - will be deprecated soon
		Object = object         /// Compatibility alias - will be deprecated soon
	}

	/// New JSON value of Type.Undefined
	static @property Json undefined() { return Json(); }

	/// New JSON value of Type.Object
	static @property Json emptyObject() { return Json(cast(Json[string])null); }

	/// New JSON value of Type.Array
	static @property Json emptyArray() { return Json(cast(Json[])null); }

	version(JsonLineNumbers) int line;

	/**
		Constructor for a JSON object.
	*/
	this(typeof(null)) { m_type = Type.null_; }
	/// ditto
	this(bool v) { m_type = Type.bool_; m_bool = v; }
	/// ditto
	this(byte v) { this(cast(long)v); }
	/// ditto
	this(ubyte v) { this(cast(long)v); }
	/// ditto
	this(short v) { this(cast(long)v); }
	/// ditto
	this(ushort v) { this(cast(long)v); }
	/// ditto
	this(int v) { this(cast(long)v); }
	/// ditto
	this(uint v) { this(cast(long)v); }
	/// ditto
	this(long v) { m_type = Type.int_; m_int = v; }
	/// ditto
	this(double v) { m_type = Type.float_; m_float = v; }
	/// ditto
	this(string v) { m_type = Type.string; m_string = v; }
	/// ditto
	this(Json[] v) { m_type = Type.array; m_array = v; }
	/// ditto
	this(Json[string] v) { m_type = Type.object; m_object = v; }

	/**
		Allows assignment of D values to a JSON value.
	*/
	ref Json opAssign(Json v) return
	{
		m_type = v.m_type;
		final switch(m_type){
			case Type.undefined: m_string = null; break;
			case Type.null_: m_string = null; break;
			case Type.bool_: m_bool = v.m_bool; break;
			case Type.int_: m_int = v.m_int; break;
			case Type.float_: m_float = v.m_float; break;
			case Type.string: m_string = v.m_string; break;
			case Type.array: opAssign(v.m_array); break;
			case Type.object: opAssign(v.m_object); break;
		}
		return this;
	}
	/// ditto
	void opAssign(typeof(null)) { m_type = Type.null_; m_string = null; }
	/// ditto
	bool opAssign(bool v) { m_type = Type.bool_; m_bool = v; return v; }
	/// ditto
	int opAssign(int v) { m_type = Type.int_; m_int = v; return v; }
	/// ditto
	long opAssign(long v) { m_type = Type.int_; m_int = v; return v; }
	/// ditto
	double opAssign(double v) { m_type = Type.float_; m_float = v; return v; }
	/// ditto
	string opAssign(string v) { m_type = Type.string; m_string = v; return v; }
	/// ditto
	Json[] opAssign(Json[] v)
	{
		m_type = Type.array;
		m_array = v;
		version (VibeJsonFieldNames) { if (m_magic == 0x1337f00d) { foreach (idx, ref av; m_array) av.m_name = format("%s[%s]", m_name, idx); } else m_name = null; }
		return v;
	}
	/// ditto
	Json[string] opAssign(Json[string] v)
	{
		m_type = Type.object;
		m_object = v;
		version (VibeJsonFieldNames) { if (m_magic == 0x1337f00d) { foreach (key, ref av; m_object) av.m_name = format("%s.%s", m_name, key); } else m_name = null; }
		return v;
	}

	/**
		Allows removal of values from Type.Object Json objects.
	*/
	void remove(string item) { checkType!(Json[string])(); m_object.remove(item); }

	/**
		The current type id of this JSON object.
	*/
	@property Type type() const { return m_type; }

	/**
		Clones a JSON value recursively.
	*/
	Json clone()
	const {
		final switch (m_type) {
			case Type.undefined: return Json.undefined;
			case Type.null_: return Json(null);
			case Type.bool_: return Json(m_bool);
			case Type.int_: return Json(m_int);
			case Type.float_: return Json(m_float);
			case Type.string: return Json(m_string);
			case Type.array:
				auto ret = Json.emptyArray;
				foreach (v; this) ret ~= v.clone();
				return ret;
			case Type.object:
				auto ret = Json.emptyObject;
				foreach (string name, v; this) ret[name] = v.clone();
				return ret;
		}
	}

	/**
		Check whether the JSON object contains the given key and if yes,
		return a pointer to the corresponding object, otherwise return `null`.
	*/
	inout(Json*) opBinaryRight(string op : "in")(string key) inout {
		checkType!(Json[string])();
		return key in m_object;
	}

	/**
		Allows direct indexing of array typed JSON values.
	*/
	ref inout(Json) opIndex(size_t idx) inout { checkType!(Json[])(); return m_array[idx]; }

	///
	unittest {
		Json value = Json.emptyArray;
		value ~= 1;
		value ~= true;
		value ~= "foo";
		assert(value[0] == 1);
		assert(value[1] == true);
		assert(value[2] == "foo");
	}


	/**
		Allows direct indexing of object typed JSON values using a string as
		the key.
	*/
	const(Json) opIndex(string key)
	const {
		checkType!(Json[string])();
		if( auto pv = key in m_object ) return *pv;
		Json ret = Json.undefined;
		ret.m_string = key;
		version (VibeJsonFieldNames) ret.m_name = format("%s.%s", m_name, key);
		return ret;
	}
	/// ditto
	ref Json opIndex(string key)
	{
		checkType!(Json[string])();
		if( auto pv = key in m_object )
			return *pv;
		if (m_object is null) {
			m_object = ["": Json.init];
			m_object.remove("");
		}
		m_object[key] = Json.init;
		assert(m_object !is null);
		assert(key in m_object, "Failed to insert key '"~key~"' into AA!?");
		m_object[key].m_type = Type.undefined; // DMDBUG: AAs are the $H1T!!!11
		assert(m_object[key].type == Type.undefined);
		m_object[key].m_string = key;
		version (VibeJsonFieldNames) m_object[key].m_name = format("%s.%s", m_name, key);
		return m_object[key];
	}

	///
	unittest {
		Json value = Json.emptyObject;
		value["a"] = 1;
		value["b"] = true;
		value["c"] = "foo";
		assert(value["a"] == 1);
		assert(value["b"] == true);
		assert(value["c"] == "foo");
	}

	/**
		Returns a slice of a JSON array.
	*/
	inout(Json[]) opSlice() inout { checkType!(Json[])(); return m_array; }
	///
	inout(Json[]) opSlice(size_t from, size_t to) inout { checkType!(Json[])(); return m_array[from .. to]; }

	/**
		Returns the number of entries of string, array or object typed JSON values.
	*/
	@property size_t length()
	const {
		checkType!(string, Json[], Json[string])("property length");
		switch(m_type){
			case Type.string: return m_string.length;
			case Type.array: return m_array.length;
			case Type.object: return m_object.length;
			default: assert(false);
		}
	}

	/**
		Allows foreach iterating over JSON objects and arrays.
	*/
	int opApply(int delegate(ref Json obj) del)
	{
		checkType!(Json[], Json[string])("opApply");
		if( m_type == Type.array ){
			foreach( ref v; m_array )
				if( auto ret = del(v) )
					return ret;
			return 0;
		} else {
			foreach( ref v; m_object )
				if( v.type != Type.undefined )
					if( auto ret = del(v) )
						return ret;
			return 0;
		}
	}
	/// ditto
	int opApply(int delegate(ref const Json obj) del)
	const {
		checkType!(Json[], Json[string])("opApply");
		if( m_type == Type.array ){
			foreach( ref v; m_array )
				if( auto ret = del(v) )
					return ret;
			return 0;
		} else {
			foreach( ref v; m_object )
				if( v.type != Type.undefined )
					if( auto ret = del(v) )
						return ret;
			return 0;
		}
	}
	/// ditto
	int opApply(int delegate(ref size_t idx, ref Json obj) del)
	{
		checkType!(Json[])("opApply");
		foreach( idx, ref v; m_array )
			if( auto ret = del(idx, v) )
				return ret;
		return 0;
	}
	/// ditto
	int opApply(int delegate(ref size_t idx, ref const Json obj) del)
	const {
		checkType!(Json[])("opApply");
		foreach( idx, ref v; m_array )
			if( auto ret = del(idx, v) )
				return ret;
		return 0;
	}
	/// ditto
	int opApply(int delegate(ref string idx, ref Json obj) del)
	{
		checkType!(Json[string])("opApply");
		foreach( idx, ref v; m_object )
			if( v.type != Type.undefined )
				if( auto ret = del(idx, v) )
					return ret;
		return 0;
	}
	/// ditto
	int opApply(int delegate(ref string idx, ref const Json obj) del)
	const {
		checkType!(Json[string])("opApply");
		foreach( idx, ref v; m_object )
			if( v.type != Type.undefined )
				if( auto ret = del(idx, v) )
					return ret;
		return 0;
	}

	/**
		Converts the JSON value to the corresponding D type - types must match exactly.

		Available_Types:
			$(UL
				$(LI `bool` (`Type.bool_`))
				$(LI `double` (`Type.float_`))
				$(LI `float` (Converted from `double`))
				$(LI `long` (`Type.int_`))
				$(LI `ulong`, `int`, `uint`, `short`, `ushort`, `byte`, `ubyte` (Converted from `long`))
				$(LI `string` (`Type.string`))
				$(LI `Json[]` (`Type.array`))
				$(LI `Json[string]` (`Type.object`))
			)

		See_Also: `opt`, `to`, `deserializeJson`
	*/
	inout(T) opCast(T)() inout { return get!T; }
	/// ditto
	@property inout(T) get(T)()
	inout {
		checkType!T();
		static if (is(T == bool)) return m_bool;
		else static if (is(T == double)) return m_float;
		else static if (is(T == float)) return cast(T)m_float;
		else static if (is(T == long)) return m_int;
		else static if (is(T == ulong)) return cast(ulong)m_int;
		else static if (is(T : long)){ enforceJson(m_int <= T.max && m_int >= T.min, "Integer conversion out of bounds error", m_fileName, line); return cast(T)m_int; }
		else static if (is(T == string)) return m_string;
		else static if (is(T == Json[])) return m_array;
		else static if (is(T == Json[string])) return m_object;
		else static assert("JSON can only be cast to (bool, long, double, string, Json[] or Json[string]. Not "~T.stringof~".");
	}

	/**
		Returns the native type for this JSON if it matches the current runtime type.

		If the runtime type does not match the given native type, the 'def' parameter is returned
		instead.

		See_Also: `get`
	*/
	@property const(T) opt(T)(const(T) def = T.init)
	const {
		if( typeId!T != m_type ) return def;
		return get!T;
	}
	/// ditto
	@property T opt(T)(T def = T.init)
	{
		if( typeId!T != m_type ) return def;
		return get!T;
	}

	/**
		Converts the JSON value to the corresponding D type - types are converted as necessary.

		Automatically performs conversions between strings and numbers. See
		`get` for the list of available types. For converting/deserializing
		JSON to complex data types see `deserializeJson`.

		See_Also: `get`, `deserializeJson`
	*/
	@property inout(T) to(T)()
	inout {
		static if( is(T == bool) ){
			final switch( m_type ){
				case Type.undefined: return false;
				case Type.null_: return false;
				case Type.bool_: return m_bool;
				case Type.int_: return m_int != 0;
				case Type.float_: return m_float != 0;
				case Type.string: return m_string.length > 0;
				case Type.array: return m_array.length > 0;
				case Type.object: return m_object.length > 0;
			}
		} else static if( is(T == double) ){
			final switch( m_type ){
				case Type.undefined: return T.init;
				case Type.null_: return 0;
				case Type.bool_: return m_bool ? 1 : 0;
				case Type.int_: return m_int;
				case Type.float_: return m_float;
				case Type.string: return .to!double(cast(string)m_string);
				case Type.array: return double.init;
				case Type.object: return double.init;
			}
		} else static if( is(T == float) ){
			final switch( m_type ){
				case Type.undefined: return T.init;
				case Type.null_: return 0;
				case Type.bool_: return m_bool ? 1 : 0;
				case Type.int_: return m_int;
				case Type.float_: return m_float;
				case Type.string: return .to!float(cast(string)m_string);
				case Type.array: return float.init;
				case Type.object: return float.init;
			}
		}
		else static if( is(T == long) ){
			final switch( m_type ){
				case Type.undefined: return 0;
				case Type.null_: return 0;
				case Type.bool_: return m_bool ? 1 : 0;
				case Type.int_: return m_int;
				case Type.float_: return cast(long)m_float;
				case Type.string: return .to!long(m_string);
				case Type.array: return 0;
				case Type.object: return 0;
			}
		} else static if( is(T : long) ){
			final switch( m_type ){
				case Type.undefined: return 0;
				case Type.null_: return 0;
				case Type.bool_: return m_bool ? 1 : 0;
				case Type.int_: return cast(T)m_int;
				case Type.float_: return cast(T)m_float;
				case Type.string: return cast(T).to!long(cast(string)m_string);
				case Type.array: return 0;
				case Type.object: return 0;
			}
		} else static if( is(T == string) ){
			switch( m_type ){
				default: return toString();
				case Type.string: return m_string;
			}
		} else static if( is(T == Json[]) ){
			switch( m_type ){
				default: return Json([this]);
				case Type.array: return m_array;
			}
		} else static if( is(T == Json[string]) ){
			switch( m_type ){
				default: return Json(["value": this]);
				case Type.object: return m_object;
			}
		} else static assert("JSON can only be cast to (bool, long, double, string, Json[] or Json[string]. Not "~T.stringof~".");
	}

	/**
		Performs unary operations on the JSON value.

		The following operations are supported for each type:

		$(DL
			$(DT Null)   $(DD none)
			$(DT Bool)   $(DD ~)
			$(DT Int)    $(DD +, -, ++, --)
			$(DT Float)  $(DD +, -, ++, --)
			$(DT String) $(DD none)
			$(DT Array)  $(DD none)
			$(DT Object) $(DD none)
		)
	*/
	Json opUnary(string op)()
	const {
		static if( op == "~" ){
			checkType!bool();
			return Json(~m_bool);
		} else static if( op == "+" || op == "-" || op == "++" || op == "--" ){
			checkType!(long, double)("unary "~op);
			if( m_type == Type.int_ ) mixin("return Json("~op~"m_int);");
			else if( m_type == Type.float_ ) mixin("return Json("~op~"m_float);");
			else assert(false);
		} else static assert("Unsupported operator '"~op~"' for type JSON.");
	}

	/**
		Performs binary operations between JSON values.

		The two JSON values must be of the same run time type or a JSONException
		will be thrown. Only the operations listed are allowed for each of the
		types.

		$(DL
			$(DT Null)   $(DD none)
			$(DT Bool)   $(DD &&, ||)
			$(DT Int)    $(DD +, -, *, /, %)
			$(DT Float)  $(DD +, -, *, /, %)
			$(DT String) $(DD ~)
			$(DT Array)  $(DD ~)
			$(DT Object) $(DD in)
		)
	*/
	Json opBinary(string op)(ref const(Json) other)
	const {
		enforceJson(m_type == other.m_type, "Binary operation '"~op~"' between "~.to!string(m_type)~" and "~.to!string(other.m_type)~" JSON objects.");
		static if( op == "&&" ){
			checkType!(bool)(op);
			return Json(m_bool && other.m_bool);
		} else static if( op == "||" ){
			checkType!(bool)(op);
			return Json(m_bool || other.m_bool);
		} else static if( op == "+" ){
			checkType!(long, double)(op);
			if( m_type == Type.Int ) return Json(m_int + other.m_int);
			else if( m_type == Type.float_ ) return Json(m_float + other.m_float);
			else assert(false);
		} else static if( op == "-" ){
			checkType!(long, double)(op);
			if( m_type == Type.Int ) return Json(m_int - other.m_int);
			else if( m_type == Type.float_ ) return Json(m_float - other.m_float);
			else assert(false);
		} else static if( op == "*" ){
			checkType!(long, double)(op);
			if( m_type == Type.Int ) return Json(m_int * other.m_int);
			else if( m_type == Type.float_ ) return Json(m_float * other.m_float);
			else assert(false);
		} else static if( op == "/" ){
			checkType!(long, double)(op);
			if( m_type == Type.Int ) return Json(m_int / other.m_int);
			else if( m_type == Type.float_ ) return Json(m_float / other.m_float);
			else assert(false);
		} else static if( op == "%" ){
			checkType!(long, double)(op);
			if( m_type == Type.Int ) return Json(m_int % other.m_int);
			else if( m_type == Type.float_ ) return Json(m_float % other.m_float);
			else assert(false);
		} else static if( op == "~" ){
			checkType!(string, Json[])(op);
			if( m_type == Type.string ) return Json(m_string ~ other.m_string);
			else if (m_type == Type.array) return Json(m_array ~ other.m_array);
			else assert(false);
		} else static assert("Unsupported operator '"~op~"' for type JSON.");
	}
	/// ditto
	Json opBinary(string op)(Json other)
		if( op == "~" )
	{
		static if( op == "~" ){
			checkType!(string, Json[])(op);
			if( m_type == Type.string ) return Json(m_string ~ other.m_string);
			else if( m_type == Type.array ) return Json(m_array ~ other.m_array);
			else assert(false);
		} else static assert("Unsupported operator '"~op~"' for type JSON.");
	}
	/// ditto
	void opOpAssign(string op)(Json other)
		if (op == "+" || op == "-" || op == "*" || op == "/" || op == "%" || op =="~")
	{
		enforceJson(m_type == other.m_type || op == "~" && m_type == Type.array,
				"Binary operation '"~op~"=' between "~.to!string(m_type)~" and "~.to!string(other.m_type)~" JSON objects.");
		static if( op == "+" ){
			if( m_type == Type.int_ ) m_int += other.m_int;
			else if( m_type == Type.float_ ) m_float += other.m_float;
			else enforceJson(false, "'+=' only allowed for scalar types, not "~.to!string(m_type)~".");
		} else static if( op == "-" ){
			if( m_type == Type.int_ ) m_int -= other.m_int;
			else if( m_type == Type.float_ ) m_float -= other.m_float;
			else enforceJson(false, "'-=' only allowed for scalar types, not "~.to!string(m_type)~".");
		} else static if( op == "*" ){
			if( m_type == Type.int_ ) m_int *= other.m_int;
			else if( m_type == Type.float_ ) m_float *= other.m_float;
			else enforceJson(false, "'*=' only allowed for scalar types, not "~.to!string(m_type)~".");
		} else static if( op == "/" ){
			if( m_type == Type.int_ ) m_int /= other.m_int;
			else if( m_type == Type.float_ ) m_float /= other.m_float;
			else enforceJson(false, "'/=' only allowed for scalar types, not "~.to!string(m_type)~".");
		} else static if( op == "%" ){
			if( m_type == Type.int_ ) m_int %= other.m_int;
			else if( m_type == Type.float_ ) m_float %= other.m_float;
			else enforceJson(false, "'%=' only allowed for scalar types, not "~.to!string(m_type)~".");
		} else static if( op == "~" ){
			if (m_type == Type.string) m_string ~= other.m_string;
			else if (m_type == Type.array) {
				if (other.m_type == Type.array) m_array ~= other.m_array;
				else appendArrayElement(other);
			} else enforceJson(false, "'~=' only allowed for string and array types, not "~.to!string(m_type)~".");
		} else static assert("Unsupported operator '"~op~"=' for type JSON.");
	}
	/// ditto
	void opOpAssign(string op, T)(T other)
		if (!is(T == Json) && is(typeof(Json(other))))
	{
		opOpAssign!op(Json(other));
	}
	/// ditto
	Json opBinary(string op)(bool other) const { checkType!bool(); mixin("return Json(m_bool "~op~" other);"); }
	/// ditto
	Json opBinary(string op)(long other) const { checkType!long(); mixin("return Json(m_int "~op~" other);"); }
	/// ditto
	Json opBinary(string op)(double other) const { checkType!double(); mixin("return Json(m_float "~op~" other);"); }
	/// ditto
	Json opBinary(string op)(string other) const { checkType!string(); mixin("return Json(m_string "~op~" other);"); }
	/// ditto
	Json opBinary(string op)(Json[] other) { checkType!(Json[])(); mixin("return Json(m_array "~op~" other);"); }
	/// ditto
	Json opBinaryRight(string op)(bool other) const { checkType!bool(); mixin("return Json(other "~op~" m_bool);"); }
	/// ditto
	Json opBinaryRight(string op)(long other) const { checkType!long(); mixin("return Json(other "~op~" m_int);"); }
	/// ditto
	Json opBinaryRight(string op)(double other) const { checkType!double(); mixin("return Json(other "~op~" m_float);"); }
	/// ditto
	Json opBinaryRight(string op)(string other) const if(op == "~") { checkType!string(); return Json(other ~ m_string); }
	/// ditto
	inout(Json)* opBinaryRight(string op)(string other) inout if(op == "in") {
		checkType!(Json[string])();
		auto pv = other in m_object;
		if( !pv ) return null;
		if( pv.type == Type.undefined ) return null;
		return pv;
	}
	/// ditto
	Json opBinaryRight(string op)(Json[] other) { checkType!(Json[])(); mixin("return Json(other "~op~" m_array);"); }

	/**
	 * The append operator will append arrays. This method always appends it's argument as an array element, so nested arrays can be created.
	 */
	void appendArrayElement(Json element)
	{
		enforceJson(m_type == Type.array, "'appendArrayElement' only allowed for array types, not "~.to!string(m_type)~".");
		m_array ~= element;
	}

	/**
		Compares two JSON values for equality.

		If the two values have different types, they are considered unequal.
		This differs with ECMA script, which performs a type conversion before
		comparing the values.
	*/
	bool opEquals(ref const Json other)
	const {
		if( m_type != other.m_type ) return false;
		final switch(m_type){
			case Type.undefined: return false;
			case Type.null_: return true;
			case Type.bool_: return m_bool == other.m_bool;
			case Type.int_: return m_int == other.m_int;
			case Type.float_: return m_float == other.m_float;
			case Type.string: return m_string == other.m_string;
			case Type.array: return m_array == other.m_array;
			case Type.object: return m_object == other.m_object;
		}
	}
	/// ditto
	bool opEquals(const Json other) const { return opEquals(other); }
	/// ditto
	bool opEquals(typeof(null)) const { return m_type == Type.null_; }
	/// ditto
	bool opEquals(bool v) const { return m_type == Type.bool_ && m_bool == v; }
	/// ditto
	bool opEquals(int v) const { return m_type == Type.int_ && m_int == v; }
	/// ditto
	bool opEquals(long v) const { return m_type == Type.int_ && m_int == v; }
	/// ditto
	bool opEquals(double v) const { return m_type == Type.float_ && m_float == v; }
	/// ditto
	bool opEquals(string v) const { return m_type == Type.string && m_string == v; }

	/**
		Compares two JSON values.

		If the types of the two values differ, the value with the smaller type
		id is considered the smaller value. This differs from ECMA script, which
		performs a type conversion before comparing the values.

		JSON values of type Object cannot be compared and will throw an
		exception.
	*/
	int opCmp(ref const Json other)
	const {
		if( m_type != other.m_type ) return m_type < other.m_type ? -1 : 1;
		final switch(m_type){
			case Type.undefined: return 0;
			case Type.null_: return 0;
			case Type.bool_: return m_bool < other.m_bool ? -1 : m_bool == other.m_bool ? 0 : 1;
			case Type.int_: return m_int < other.m_int ? -1 : m_int == other.m_int ? 0 : 1;
			case Type.float_: return m_float < other.m_float ? -1 : m_float == other.m_float ? 0 : 1;
			case Type.string: return m_string < other.m_string ? -1 : m_string == other.m_string ? 0 : 1;
			case Type.array: return m_array < other.m_array ? -1 : m_array == other.m_array ? 0 : 1;
			case Type.object:
				enforceJson(false, "JSON objects cannot be compared.");
				assert(false);
		}
	}

	alias opDollar = length;

	/**
		Returns the type id corresponding to the given D type.
	*/
	static @property Type typeId(T)() {
		static if( is(T == typeof(null)) ) return Type.null_;
		else static if( is(T == bool) ) return Type.bool_;
		else static if( is(T == double) ) return Type.float_;
		else static if( is(T == float) ) return Type.float_;
		else static if( is(T : long) ) return Type.int_;
		else static if( is(T == string) ) return Type.string;
		else static if( is(T == Json[]) ) return Type.array;
		else static if( is(T == Json[string]) ) return Type.object;
		else static assert(false, "Unsupported JSON type '"~T.stringof~"'. Only bool, long, double, string, Json[] and Json[string] are allowed.");
	}

	/**
		Returns the JSON object as a string.

		For large JSON values use writeJsonString instead as this function will store the whole string
		in memory, whereas writeJsonString writes it out bit for bit.

		See_Also: writeJsonString, toPrettyString
	*/
	string toString()
	const {
		auto ret = appender!string();
		writeJsonString(ret, this);
		return ret.data;
	}

	/**
		Returns the JSON object as a "pretty" string.

		---
		auto json = Json(["foo": Json("bar")]);
		writeln(json.toPrettyString());

		// output:
		// {
		//     "foo": "bar"
		// }
		---

		Params:
			level = Specifies the base amount of indentation for the output. Indentation  is always
				done using tab characters.

		See_Also: writePrettyJsonString, toString
	*/
	string toPrettyString(int level = 0)
	const {
		auto ret = appender!string();
		writePrettyJsonString(ret, this, level);
		return ret.data;
	}

	private void checkType(TYPES...)(string op = null)
	const {
		bool matched = false;
		foreach (T; TYPES) if (m_type == typeId!T) matched = true;
		if (matched) return;

		string name;
		version (VibeJsonFieldNames) {
			if (m_name.length) name = m_name ~ " of type " ~ m_type.to!string;
			else name = "JSON of type " ~ m_type.to!string;
		} else name = "JSON of type " ~ m_type.to!string;

		string expected;
		static if (TYPES.length == 1) expected = typeId!(TYPES[0]).to!string;
		else {
			foreach (T; TYPES) {
				if (expected.length > 0) expected ~= ", ";
				expected ~= typeId!T.to!string;
			}
		}

		enforceJson(op.length > 0, format("Got %s, expected %s.", name, expected), m_fileName, line);
		enforceJson(false, format("Got %s, expected %s for %s.", name, expected, op), m_fileName, line);
	}

	/*invariant()
	{
		assert(m_type >= Type.Undefined && m_type <= Type.Object);
	}*/
}


/******************************************************************************/
/* public functions                                                           */
/******************************************************************************/

/**
	Parses the given range as a JSON string and returns the corresponding Json object.

	The range is shrunk during parsing, leaving any remaining text that is not part of
	the JSON contents.

	Throws a JSONException if any parsing error occurred.
*/
Json parseJson(R)(ref R range, int* line = null, string filename = null)
	if( is(R == string) )
{
	import std.string : startsWith;

	Json ret;
	enforceJson(!range.empty, "JSON string is empty.", filename, 0);

	skipWhitespace(range, line);

	version(JsonLineNumbers) {
		import dub.internal.vibecompat.core.log;
		int curline = line ? *line : 0;
	}

	switch( range.front ){
		case 'f':
			enforceJson(range[1 .. $].startsWith("alse"), "Expected 'false', got '"~range[0 .. min(5, $)]~"'.", filename, line);
			range.popFrontN(5);
			ret = false;
			break;
		case 'n':
			enforceJson(range[1 .. $].startsWith("ull"), "Expected 'null', got '"~range[0 .. min(4, $)]~"'.", filename, line);
			range.popFrontN(4);
			ret = null;
			break;
		case 't':
			enforceJson(range[1 .. $].startsWith("rue"), "Expected 'true', got '"~range[0 .. min(4, $)]~"'.", filename, line);
			range.popFrontN(4);
			ret = true;
			break;
		case '0': .. case '9':
		case '-':
			bool is_float;
			auto num = skipNumber(range, is_float, filename, line);
			if( is_float ) ret = to!double(num);
			else ret = to!long(num);
			break;
		case '\"':
			ret = skipJsonString(range, filename, line);
			break;
		case '[':
			Json[] arr;
			range.popFront();
			while (true) {
				skipWhitespace(range, line);
				enforceJson(!range.empty, "Missing ']' before EOF.", filename, line);
				if(range.front == ']') break;
				arr ~= parseJson(range, line, filename);
				skipWhitespace(range, line);
				enforceJson(!range.empty, "Missing ']' before EOF.", filename, line);
				enforceJson(range.front == ',' || range.front == ']',
					format("Expected ']' or ',' - got '%s'.", range.front), filename, line);
				if( range.front == ']' ) break;
				else range.popFront();
			}
			range.popFront();
			ret = arr;
			break;
		case '{':
			Json[string] obj;
			range.popFront();
			while (true) {
				skipWhitespace(range, line);
				enforceJson(!range.empty, "Missing '}' before EOF.", filename, line);
				if(range.front == '}') break;
				string key = skipJsonString(range, filename, line);
				skipWhitespace(range, line);
				enforceJson(range.startsWith(":"), "Expected ':' for key '" ~ key ~ "'", filename, line);
				range.popFront();
				skipWhitespace(range, line);
				Json itm = parseJson(range, line, filename);
				obj[key] = itm;
				skipWhitespace(range, line);
				enforceJson(!range.empty, "Missing '}' before EOF.", filename, line);
				enforceJson(range.front == ',' || range.front == '}',
					format("Expected '}' or ',' - got '%s'.", range.front), filename, line);
				if (range.front == '}') break;
				else range.popFront();
			}
			range.popFront();
			ret = obj;
			break;
		default:
			enforceJson(false, format("Expected valid JSON token, got '%s'.", range[0 .. min(12, $)]), filename, line);
			assert(false);
	}

	assert(ret.type != Json.Type.undefined);
	version(JsonLineNumbers) ret.line = curline;
	ret.m_fileName = filename;
	return ret;
}

/**
	Parses the given JSON string and returns the corresponding Json object.

	Throws a JSONException if any parsing error occurs.
*/
Json parseJsonString(string str, string filename = null)
{
	import std.string : strip;

	auto strcopy = str;
	int line = 0;
	auto ret = parseJson(strcopy, &line, filename);
	enforceJson(strcopy.strip().length == 0, "Expected end of string after JSON value.", filename, line);
	return ret;
}

unittest {
	assert(parseJsonString("null") == Json(null));
	assert(parseJsonString("true") == Json(true));
	assert(parseJsonString("false") == Json(false));
	assert(parseJsonString("1") == Json(1));
	assert(parseJsonString("2.0") == Json(2.0));
	assert(parseJsonString("\"test\"") == Json("test"));
	assert(parseJsonString("[1, 2, 3]") == Json([Json(1), Json(2), Json(3)]));
	assert(parseJsonString("{\"a\": 1}") == Json(["a": Json(1)]));
	assert(parseJsonString(`"\\\/\b\f\n\r\t\u1234"`).get!string == "\\/\b\f\n\r\t\u1234");
	auto json = parseJsonString(`{"hey": "This is @à test éhééhhéhéé !%/??*&?\ud83d\udcec"}`);
	assert(json.toPrettyString() == parseJsonString(json.toPrettyString()).toPrettyString());
}

unittest {
	import std.string : endsWith;

	try parseJsonString(`{"a": 1`);
	catch (Exception e) assert(e.msg.endsWith("Missing '}' before EOF."));
	try parseJsonString(`{"a": 1 x`);
	catch (Exception e) assert(e.msg.endsWith("Expected '}' or ',' - got 'x'."));
	try parseJsonString(`[1`);
	catch (Exception e) assert(e.msg.endsWith("Missing ']' before EOF."));
	try parseJsonString(`[1 x`);
	catch (Exception e) assert(e.msg.endsWith("Expected ']' or ',' - got 'x'."));
}

/**
	Serializes the given value to JSON.

	The following types of values are supported:

	$(DL
		$(DT `Json`)            $(DD Used as-is)
		$(DT `null`)            $(DD Converted to `Json.Type.null_`)
		$(DT `bool`)            $(DD Converted to `Json.Type.bool_`)
		$(DT `float`, `double`)   $(DD Converted to `Json.Type.float_`)
		$(DT `short`, `ushort`, `int`, `uint`, `long`, `ulong`) $(DD Converted to `Json.Type.int_`)
		$(DT `string`)          $(DD Converted to `Json.Type.string`)
		$(DT `T[]`)             $(DD Converted to `Json.Type.array`)
		$(DT `T[string]`)       $(DD Converted to `Json.Type.object`)
		$(DT `struct`)          $(DD Converted to `Json.Type.object`)
		$(DT `class`)           $(DD Converted to `Json.Type.object` or `Json.Type.null_`)
	)

	All entries of an array or an associative array, as well as all R/W properties and
	all public fields of a struct/class are recursively serialized using the same rules.

	Fields ending with an underscore will have the last underscore stripped in the
	serialized output. This makes it possible to use fields with D keywords as their name
	by simply appending an underscore.

	The following methods can be used to customize the serialization of structs/classes:

	---
	Json toJson() const;
	static T fromJson(Json src);

	string toString() const;
	static T fromString(string src);
	---

	The methods will have to be defined in pairs. The first pair that is implemented by
	the type will be used for serialization (i.e. `toJson` overrides `toString`).

	See_Also: `deserializeJson`, `vibe.data.serialization`
*/
Json serializeToJson(T)(T value)
{
	version (VibeOldSerialization) {
		return serializeToJsonOld(value);
	} else {
		return serialize!JsonSerializer(value);
	}
}
/// ditto
void serializeToJson(R, T)(R destination, T value)
	if (isOutputRange!(R, char) || isOutputRange!(R, ubyte))
{
	serialize!(JsonStringSerializer!R)(value, destination);
}
/// ditto
string serializeToJsonString(T)(T value)
{
	auto ret = appender!string;
	serializeToJson(ret, value);
	return ret.data;
}

///
unittest {
	struct Foo {
		int number;
		string str;
	}

	Foo f;
	f.number = 12;
	f.str = "hello";

	string json = serializeToJsonString(f);
	assert(json == `{"number":12,"str":"hello"}`);

	Json jsonval = serializeToJson(f);
	assert(jsonval.type == Json.Type.object);
	assert(jsonval["number"] == Json(12));
	assert(jsonval["str"] == Json("hello"));
}


/**
	Serializes the given value to a pretty printed JSON string.

	See_also: `serializeToJson`, `vibe.data.serialization`
*/
void serializeToPrettyJson(R, T)(R destination, T value)
	if (isOutputRange!(R, char) || isOutputRange!(R, ubyte))
{
	serialize!(JsonStringSerializer!(R, true))(value, destination);
}
/// ditto
string serializeToPrettyJson(T)(T value)
{
	auto ret = appender!string;
	serializeToPrettyJson(ret, value);
	return ret.data;
}

///
unittest {
	struct Foo {
		int number;
		string str;
	}

	Foo f;
	f.number = 12;
	f.str = "hello";

	string json = serializeToPrettyJson(f);
	assert(json ==
`{
	"number": 12,
	"str": "hello"
}`);
}


/// private
Json serializeToJsonOld(T)(T value)
{
	import vibe.internal.meta.traits;

	alias TU = Unqual!T;
	static if (is(TU == Json)) return value;
	else static if (is(TU == typeof(null))) return Json(null);
	else static if (is(TU == bool)) return Json(value);
	else static if (is(TU == float)) return Json(cast(double)value);
	else static if (is(TU == double)) return Json(value);
	else static if (is(TU == DateTime)) return Json(value.toISOExtString());
	else static if (is(TU == SysTime)) return Json(value.toISOExtString());
	else static if (is(TU == Date)) return Json(value.toISOExtString());
	else static if (is(TU : long)) return Json(cast(long)value);
	else static if (is(TU : string)) return Json(value);
	else static if (isArray!T) {
		auto ret = new Json[value.length];
		foreach (i; 0 .. value.length)
			ret[i] = serializeToJson(value[i]);
		return Json(ret);
	} else static if (isAssociativeArray!TU) {
		Json[string] ret;
		alias TK = KeyType!T;
		foreach (key, value; value) {
			static if(is(TK == string)) {
				ret[key] = serializeToJson(value);
			} else static if (is(TK == enum)) {
				ret[to!string(key)] = serializeToJson(value);
			} else static if (isStringSerializable!(TK)) {
				ret[key.toString()] = serializeToJson(value);
			} else static assert("AA key type %s not supported for JSON serialization.");
		}
		return Json(ret);
	} else static if (isJsonSerializable!TU) {
		return value.toJson();
	} else static if (isStringSerializable!TU) {
		return Json(value.toString());
	} else static if (is(TU == struct)) {
		Json[string] ret;
		foreach (m; __traits(allMembers, T)) {
			static if (isRWField!(TU, m)) {
				auto mv = __traits(getMember, value, m);
				ret[underscoreStrip(m)] = serializeToJson(mv);
			}
		}
		return Json(ret);
	} else static if(is(TU == class)) {
		if (value is null) return Json(null);
		Json[string] ret;
		foreach (m; __traits(allMembers, T)) {
			static if (isRWField!(TU, m)) {
				auto mv = __traits(getMember, value, m);
				ret[underscoreStrip(m)] = serializeToJson(mv);
			}
		}
		return Json(ret);
	} else static if (isPointer!TU) {
		if (value is null) return Json(null);
		return serializeToJson(*value);
	} else {
		static assert(false, "Unsupported type '"~T.stringof~"' for JSON serialization.");
	}
}


/**
	Deserializes a JSON value into the destination variable.

	The same types as for `serializeToJson()` are supported and handled inversely.

	See_Also: `serializeToJson`, `serializeToJsonString`, `vibe.data.serialization`
*/
void deserializeJson(T)(ref T dst, Json src)
{
	dst = deserializeJson!T(src);
}
/// ditto
T deserializeJson(T)(Json src)
{
	version (VibeOldSerialization) {
		return deserializeJsonOld!T(src);
	} else {
		return deserialize!(JsonSerializer, T)(src);
	}
}
/// ditto
T deserializeJson(T, R)(R input)
	if (isInputRange!R && !is(R == Json))
{
	return deserialize!(JsonStringSerializer!R, T)(input);
}

/// private
T deserializeJsonOld(T)(Json src)
{
	import vibe.internal.meta.traits;

	static if( is(T == struct) || isSomeString!T || isIntegral!T || isFloatingPoint!T )
		if( src.type == Json.Type.null_ ) return T.init;
	static if (is(T == Json)) return src;
	else static if (is(T == typeof(null))) { return null; }
	else static if (is(T == bool)) return src.get!bool;
	else static if (is(T == float)) return src.to!float;   // since doubles are frequently serialized without
	else static if (is(T == double)) return src.to!double; // a decimal point, we allow conversions here
	else static if (is(T == DateTime)) return DateTime.fromISOExtString(src.get!string);
	else static if (is(T == SysTime)) return SysTime.fromISOExtString(src.get!string);
	else static if (is(T == Date)) return Date.fromISOExtString(src.get!string);
	else static if (is(T : long)) return cast(T)src.get!long;
	else static if (is(T : string)) return cast(T)src.get!string;
	else static if (isArray!T) {
		alias TV = typeof(T.init[0]) ;
		auto dst = new Unqual!TV[src.length];
		foreach (size_t i, v; src)
			dst[i] = deserializeJson!(Unqual!TV)(v);
		return cast(T)dst;
	} else static if( isAssociativeArray!T ) {
		alias TV = typeof(T.init.values[0]) ;
		alias TK = KeyType!T;
		Unqual!TV[TK] dst;
		foreach (string key, value; src) {
			static if (is(TK == string)) {
				dst[key] = deserializeJson!(Unqual!TV)(value);
			} else static if (is(TK == enum)) {
				dst[to!(TK)(key)] = deserializeJson!(Unqual!TV)(value);
			} else static if (isStringSerializable!TK) {
				auto dsk = TK.fromString(key);
				dst[dsk] = deserializeJson!(Unqual!TV)(value);
			} else static assert("AA key type %s not supported for JSON serialization.");
		}
		return dst;
	} else static if (isJsonSerializable!T) {
		return T.fromJson(src);
	} else static if (isStringSerializable!T) {
		return T.fromString(src.get!string);
	} else static if (is(T == struct)) {
		T dst;
		foreach (m; __traits(allMembers, T)) {
			static if (isRWPlainField!(T, m) || isRWField!(T, m)) {
				alias TM = typeof(__traits(getMember, dst, m)) ;
				__traits(getMember, dst, m) = deserializeJson!TM(src[underscoreStrip(m)]);
			}
		}
		return dst;
	} else static if (is(T == class)) {
		if (src.type == Json.Type.null_) return null;
		auto dst = new T;
		foreach (m; __traits(allMembers, T)) {
			static if (isRWPlainField!(T, m) || isRWField!(T, m)) {
				alias TM = typeof(__traits(getMember, dst, m)) ;
				__traits(getMember, dst, m) = deserializeJson!TM(src[underscoreStrip(m)]);
			}
		}
		return dst;
	} else static if (isPointer!T) {
		if (src.type == Json.Type.null_) return null;
		alias TD = typeof(*T.init) ;
		dst = new TD;
		*dst = deserializeJson!TD(src);
		return dst;
	} else {
		static assert(false, "Unsupported type '"~T.stringof~"' for JSON serialization.");
	}
}

///
unittest {
	struct Foo {
		int number;
		string str;
	}

	Foo f = deserializeJson!Foo(`{"number": 12, "str": "hello"}`);
	assert(f.number == 12);
	assert(f.str == "hello");
}

unittest {
	import std.stdio;
	enum Foo : string { k = "test" }
	enum Boo : int { l = 5 }
	static struct S { float a; double b; bool c; int d; string e; byte f; ubyte g; long h; ulong i; float[] j; Foo k; Boo l; }
	immutable S t = {1.5, -3.0, true, int.min, "Test", -128, 255, long.min, ulong.max, [1.1, 1.2, 1.3], Foo.k, Boo.l};
	S u;
	deserializeJson(u, serializeToJson(t));
	assert(t.a == u.a);
	assert(t.b == u.b);
	assert(t.c == u.c);
	assert(t.d == u.d);
	assert(t.e == u.e);
	assert(t.f == u.f);
	assert(t.g == u.g);
	assert(t.h == u.h);
	assert(t.i == u.i);
	assert(t.j == u.j);
	assert(t.k == u.k);
	assert(t.l == u.l);
}

unittest
{
	assert(uint.max == serializeToJson(uint.max).deserializeJson!uint);
	assert(ulong.max == serializeToJson(ulong.max).deserializeJson!ulong);
}

unittest {
	static struct A { int value; static A fromJson(Json val) { return A(val.get!int); } Json toJson() const { return Json(value); } }
	static struct C { int value; static C fromString(string val) { return C(val.to!int); } string toString() const { return value.to!string; } }
	static struct D { int value; }

	assert(serializeToJson(const A(123)) == Json(123));
	assert(serializeToJson(A(123))       == Json(123));
	assert(serializeToJson(const C(123)) == Json("123"));
	assert(serializeToJson(C(123))       == Json("123"));
	assert(serializeToJson(const D(123)) == serializeToJson(["value": 123]));
	assert(serializeToJson(D(123))       == serializeToJson(["value": 123]));
}

unittest {
	auto d = Date(2001,1,1);
	deserializeJson(d, serializeToJson(Date.init));
	assert(d == Date.init);
	deserializeJson(d, serializeToJson(Date(2001,1,1)));
	assert(d == Date(2001,1,1));
	struct S { immutable(int)[] x; }
	S s;
	deserializeJson(s, serializeToJson(S([1,2,3])));
	assert(s == S([1,2,3]));
	struct T {
		@optional S s;
		@optional int i;
		@optional float f_; // underscore strip feature
		@optional double d;
		@optional string str;
	}
	auto t = T(S([1,2,3]));
	deserializeJson(t, parseJsonString(`{ "s" : null, "i" : null, "f" : null, "d" : null, "str" : null }`));
	assert(text(t) == text(T()));
}

unittest {
	static class C {
		int a;
		private int _b;
		@property int b() const { return _b; }
		@property void b(int v) { _b = v; }

		@property int test() const { return 10; }

		void test2() {}
	}
	C c = new C;
	c.a = 1;
	c.b = 2;

	C d;
	deserializeJson(d, serializeToJson(c));
	assert(c.a == d.a);
	assert(c.b == d.b);
}

unittest {
	static struct C { int value; static C fromString(string val) { return C(val.to!int); } string toString() const { return value.to!string; } }
	enum Color { Red, Green, Blue }
	{
		static class T {
			string[Color] enumIndexedMap;
			string[C] stringableIndexedMap;
			this() {
				enumIndexedMap = [ Color.Red : "magenta", Color.Blue : "deep blue" ];
                                stringableIndexedMap = [ C(42) : "forty-two" ];
			}
		}

		T original = new T;
		original.enumIndexedMap[Color.Green] = "olive";
		T other;
		deserializeJson(other, serializeToJson(original));
		assert(serializeToJson(other) == serializeToJson(original));
	}
	{
		static struct S {
			string[Color] enumIndexedMap;
			string[C] stringableIndexedMap;
		}

		S *original = new S;
		original.enumIndexedMap = [ Color.Red : "magenta", Color.Blue : "deep blue" ];
		original.enumIndexedMap[Color.Green] = "olive";
                original.stringableIndexedMap = [ C(42) : "forty-two" ];
		S other;
		deserializeJson(other, serializeToJson(original));
		assert(serializeToJson(other) == serializeToJson(original));
	}
}

unittest {
	import std.typecons : Nullable;

	struct S { Nullable!int a, b; }
	S s;
	s.a = 2;

	auto j = serializeToJson(s);
	assert(j["a"].type == Json.Type.int_);
	assert(j["b"].type == Json.Type.null_);

	auto t = deserializeJson!S(j);
	assert(!t.a.isNull() && t.a == 2);
	assert(t.b.isNull());
}

unittest { // #840
	int[2][2] nestedArray = 1;
	assert(nestedArray.serializeToJson.deserializeJson!(typeof(nestedArray)) == nestedArray);
}


/**
	Serializer for a plain Json representation.

	See_Also: vibe.data.serialization.serialize, vibe.data.serialization.deserialize, serializeToJson, deserializeJson
*/
struct JsonSerializer {
	template isJsonBasicType(T) { enum isJsonBasicType = isNumeric!T || isBoolean!T || is(T == string) || is(T == typeof(null)) || isJsonSerializable!T; }

	template isSupportedValueType(T) { enum isSupportedValueType = isJsonBasicType!T || is(T == Json); }

	private {
		Json m_current;
		Json[] m_compositeStack;
	}

	this(Json data) { m_current = data; }

	@disable this(this);

	//
	// serialization
	//
	Json getSerializedResult() { return m_current; }
	void beginWriteDictionary(T)() { m_compositeStack ~= Json.emptyObject; }
	void endWriteDictionary(T)() { m_current = m_compositeStack[$-1]; m_compositeStack.length--; }
	void beginWriteDictionaryEntry(T)(string name) {}
	void endWriteDictionaryEntry(T)(string name) { m_compositeStack[$-1][name] = m_current; }

	void beginWriteArray(T)(size_t) { m_compositeStack ~= Json.emptyArray; }
	void endWriteArray(T)() { m_current = m_compositeStack[$-1]; m_compositeStack.length--; }
	void beginWriteArrayEntry(T)(size_t) {}
	void endWriteArrayEntry(T)(size_t) { m_compositeStack[$-1].appendArrayElement(m_current); }

	void writeValue(T)(T value)
	{
		static if (is(T == Json)) m_current = value;
		else static if (isJsonSerializable!T) m_current = value.toJson();
		else m_current = Json(value);
	}

	void writeValue(T)(in Json value) if (is(T == Json))
	{
		m_current = value.clone;
	}

	//
	// deserialization
	//
	void readDictionary(T)(scope void delegate(string) field_handler)
	{
		enforceJson(m_current.type == Json.Type.object, "Expected JSON object, got "~m_current.type.to!string);
		auto old = m_current;
		foreach (string key, value; m_current) {
			m_current = value;
			field_handler(key);
		}
		m_current = old;
	}

	void readArray(T)(scope void delegate(size_t) size_callback, scope void delegate() entry_callback)
	{
		enforceJson(m_current.type == Json.Type.array, "Expected JSON array, got "~m_current.type.to!string);
		auto old = m_current;
		size_callback(m_current.length);
		foreach (ent; old) {
			m_current = ent;
			entry_callback();
		}
		m_current = old;
	}

	T readValue(T)()
	{
		static if (is(T == Json)) return m_current;
		else static if (isJsonSerializable!T) return T.fromJson(m_current);
		else static if (is(T == float) || is(T == double)) {
			if (m_current.type == Json.Type.undefined) return T.nan;
			return m_current.type == Json.Type.float_ ? cast(T)m_current.get!double : cast(T)m_current.get!long;
		}
		else {
			return m_current.get!T();
		}
	}

	bool tryReadNull() { return m_current.type == Json.Type.null_; }
}


/**
	Serializer for a range based plain JSON string representation.

	See_Also: vibe.data.serialization.serialize, vibe.data.serialization.deserialize, serializeToJson, deserializeJson
*/
struct JsonStringSerializer(R, bool pretty = false)
	if (isInputRange!R || isOutputRange!(R, char))
{
	private {
		R m_range;
		size_t m_level = 0;
	}

	template isJsonBasicType(T) { enum isJsonBasicType = isNumeric!T || isBoolean!T || is(T == string) || is(T == typeof(null)) || isJsonSerializable!T; }

	template isSupportedValueType(T) { enum isSupportedValueType = isJsonBasicType!T || is(T == Json); }

	this(R range)
	{
		m_range = range;
	}

	@disable this(this);

	//
	// serialization
	//
	static if (isOutputRange!(R, char)) {
		private {
			bool m_firstInComposite;
		}

		void getSerializedResult() {}

		void beginWriteDictionary(T)() { startComposite(); m_range.put('{'); }
		void endWriteDictionary(T)() { endComposite(); m_range.put("}"); }
		void beginWriteDictionaryEntry(T)(string name)
		{
			startCompositeEntry();
			m_range.put('"');
			m_range.jsonEscape(name);
			static if (pretty) m_range.put(`": `);
			else m_range.put(`":`);
		}
		void endWriteDictionaryEntry(T)(string name) {}

		void beginWriteArray(T)(size_t) { startComposite(); m_range.put('['); }
		void endWriteArray(T)() { endComposite(); m_range.put(']'); }
		void beginWriteArrayEntry(T)(size_t) { startCompositeEntry(); }
		void endWriteArrayEntry(T)(size_t) {}

		void writeValue(T)(in T value)
		{
			static if (is(T == typeof(null))) m_range.put("null");
			else static if (is(T == bool)) m_range.put(value ? "true" : "false");
			else static if (is(T : long)) m_range.formattedWrite("%s", value);
			else static if (is(T : real)) m_range.formattedWrite("%.16g", value);
			else static if (is(T == string)) {
				m_range.put('"');
				m_range.jsonEscape(value);
				m_range.put('"');
			}
			else static if (is(T == Json)) m_range.writeJsonString(value);
			else static if (isJsonSerializable!T) m_range.writeJsonString!(R, pretty)(value.toJson(), m_level);
			else static assert(false, "Unsupported type: " ~ T.stringof);
		}

		private void startComposite()
		{
			static if (pretty) m_level++;
			m_firstInComposite = true;
		}

		private void startCompositeEntry()
		{
			if (!m_firstInComposite) {
				m_range.put(',');
			} else {
				m_firstInComposite = false;
			}
			static if (pretty) indent();
		}

		private void endComposite()
		{
			static if (pretty) {
				m_level--;
				if (!m_firstInComposite) indent();
			}
			m_firstInComposite = false;
		}

		private void indent()
		{
			m_range.put('\n');
			foreach (i; 0 .. m_level) m_range.put('\t');
		}
	}

	//
	// deserialization
	//
	static if (isInputRange!(R)) {
		private {
			int m_line = 0;
		}

		void readDictionary(T)(scope void delegate(string) entry_callback)
		{
			m_range.skipWhitespace(&m_line);
			enforceJson(!m_range.empty && m_range.front == '{', "Expecting object.");
			m_range.popFront();
			bool first = true;
			while(true) {
				m_range.skipWhitespace(&m_line);
				enforceJson(!m_range.empty, "Missing '}'.");
				if (m_range.front == '}') {
					m_range.popFront();
					break;
				} else if (!first) {
					enforceJson(m_range.front == ',', "Expecting ',' or '}', not '"~m_range.front.to!string~"'.");
					m_range.popFront();
					m_range.skipWhitespace(&m_line);
				} else first = false;

				auto name = m_range.skipJsonString(null, &m_line);

				m_range.skipWhitespace(&m_line);
				enforceJson(!m_range.empty && m_range.front == ':', "Expecting ':', not '"~m_range.front.to!string~"'.");
				m_range.popFront();

				entry_callback(name);
			}
		}

		void readArray(T)(scope void delegate(size_t) size_callback, scope void delegate() entry_callback)
		{
			m_range.skipWhitespace(&m_line);
			enforceJson(!m_range.empty && m_range.front == '[', "Expecting array.");
			m_range.popFront();
			bool first = true;
			while(true) {
				m_range.skipWhitespace(&m_line);
				enforceJson(!m_range.empty, "Missing ']'.");
				if (m_range.front == ']') {
					m_range.popFront();
					break;
				} else if (!first) {
					enforceJson(m_range.front == ',', "Expecting ',' or ']'.");
					m_range.popFront();
				} else first = false;

				entry_callback();
			}
		}

		T readValue(T)()
		{
			m_range.skipWhitespace(&m_line);
			static if (is(T == typeof(null))) { enforceJson(m_range.take(4).equal("null"), "Expecting 'null'."); return null; }
			else static if (is(T == bool)) {
				bool ret = m_range.front == 't';
				string expected = ret ? "true" : "false";
				foreach (ch; expected) {
					enforceJson(m_range.front == ch, "Expecting 'true' or 'false'.");
					m_range.popFront();
				}
				return ret;
			} else static if (is(T : long)) {
				bool is_float;
				auto num = m_range.skipNumber(is_float, null, &m_line);
				enforceJson(!is_float, "Expecting integer number.");
				return to!T(num);
			} else static if (is(T : real)) {
				bool is_float;
				auto num = m_range.skipNumber(is_float);
				return to!T(num);
			}
			else static if (is(T == string)) return m_range.skipJsonString(null, &m_line);
			else static if (is(T == Json)) return m_range.parseJson(&m_line);
			else static if (isJsonSerializable!T) return T.fromJson(m_range.parseJson(&m_line));
			else static assert(false, "Unsupported type: " ~ T.stringof);
		}

		bool tryReadNull()
		{
			m_range.skipWhitespace(&m_line);
			if (m_range.front != 'n') return false;
			foreach (ch; "null") {
				enforceJson(m_range.front == ch, "Expecting 'null'.");
				m_range.popFront();
			}
			assert(m_range.empty || m_range.front != 'l');
			return true;
		}
	}
}



/**
	Writes the given JSON object as a JSON string into the destination range.

	This function will convert the given JSON value to a string without adding
	any white space between tokens (no newlines, no indentation and no padding).
	The output size is thus minimized, at the cost of bad human readability.

	Params:
		dst   = References the string output range to which the result is written.
		json  = Specifies the JSON value that is to be stringified.

	See_Also: Json.toString, writePrettyJsonString
*/
void writeJsonString(R, bool pretty = false)(ref R dst, in Json json, size_t level = 0)
//	if( isOutputRange!R && is(ElementEncodingType!R == char) )
{
	final switch( json.type ){
		case Json.Type.undefined: dst.put("undefined"); break;
		case Json.Type.null_: dst.put("null"); break;
		case Json.Type.bool_: dst.put(cast(bool)json ? "true" : "false"); break;
		case Json.Type.int_: formattedWrite(dst, "%d", json.get!long); break;
		case Json.Type.float_:
			auto d = json.get!double;
			if (d != d)
				dst.put("undefined"); // JSON has no NaN value so set null
			else
				formattedWrite(dst, "%.16g", json.get!double);
			break;
		case Json.Type.string:
			dst.put('\"');
			jsonEscape(dst, cast(string)json);
			dst.put('\"');
			break;
		case Json.Type.array:
			dst.put('[');
			bool first = true;
			foreach (ref const Json e; json) {
				if( !first ) dst.put(",");
				first = false;
				static if (pretty) {
					dst.put('\n');
					foreach (tab; 0 .. level+1) dst.put('\t');
				}
				if (e.type == Json.Type.undefined) dst.put("null");
				else writeJsonString!(R, pretty)(dst, e, level+1);
			}
			static if (pretty) {
				if (json.length > 0) {
					dst.put('\n');
					foreach (tab; 0 .. level) dst.put('\t');
				}
			}
			dst.put(']');
			break;
		case Json.Type.object:
			dst.put('{');
			bool first = true;

			static if (pretty) {
				import std.algorithm.sorting : sort;
				string[] keyOrder;
				foreach (string key, ref const Json e; json) keyOrder ~= key;
				keyOrder.sort();

				foreach( key; keyOrder ){
					if( json[key].type == Json.Type.undefined ) continue;
					if( !first ) dst.put(',');
					first = false;
					dst.put('\n');
					foreach (tab; 0 .. level+1) dst.put('\t');
					dst.put('\"');
					jsonEscape(dst, key);
					dst.put(pretty ? `": ` : `":`);
					writeJsonString!(R, pretty)(dst, json[key], level+1);
				}
				if (json.length > 0) {
					dst.put('\n');
					foreach (tab; 0 .. level) dst.put('\t');
				}
			} else {
				foreach( string k, ref const Json e; json ){
					if( e.type == Json.Type.undefined ) continue;
					if( !first ) dst.put(',');
					first = false;
					dst.put('\"');
					jsonEscape(dst, k);
					dst.put(pretty ? `": ` : `":`);
					writeJsonString!(R, pretty)(dst, e, level+1);
				}
			}
			dst.put('}');
			break;
	}
}

unittest {
	auto a = Json.emptyObject;
	a["a"] = Json.emptyArray;
	a["b"] = Json.emptyArray;
	a["b"] ~= Json(1);
	a["b"] ~= Json.emptyObject;

	assert(a.toString() == `{"a":[],"b":[1,{}]}` || a.toString == `{"b":[1,{}],"a":[]}`);
	assert(a.toPrettyString() ==
`{
	"a": [],
	"b": [
		1,
		{}
	]
}`);
}

unittest { // #735
	auto a = Json.emptyArray;
	a ~= "a";
	a ~= Json();
	a ~= "b";
	a ~= null;
	a ~= "c";
	assert(a.toString() == `["a",null,"b",null,"c"]`);
}

unittest {
	auto a = Json.emptyArray;
	a ~= Json(1);
	a ~= Json(2);
	a ~= Json(3);
	a ~= Json(4);
	a ~= Json(5);

	auto b = Json(a[0..a.length]);
	assert(a == b);

	auto c = Json(a[0..$]);
	assert(a == c);
	assert(b == c);

	auto d = [Json(1),Json(2),Json(3)];
	assert(d == a[0..a.length-2]);
	assert(d == a[0..$-2]);
}

unittest {
	auto j = Json(double.init);

	assert(j.toString == "undefined"); // A double nan should serialize to undefined
	j = 17.04f;
	assert(j.toString == "17.04");	// A proper double should serialize correctly

	double d;
	deserializeJson(d, Json.undefined); // Json.undefined should deserialize to nan
	assert(d != d);
}
/**
	Writes the given JSON object as a prettified JSON string into the destination range.

	The output will contain newlines and indents to make the output human readable.

	Params:
		dst   = References the string output range to which the result is written.
		json  = Specifies the JSON value that is to be stringified.
		level = Specifies the base amount of indentation for the output. Indentation  is always
		        done using tab characters.

	See_Also: Json.toPrettyString, writeJsonString
*/
void writePrettyJsonString(R)(ref R dst, in Json json, int level = 0)
//	if( isOutputRange!R && is(ElementEncodingType!R == char) )
{
	writeJsonString!(R, true)(dst, json, level);
}


/**
	Helper function that escapes all Unicode characters in a JSON string.
*/
string convertJsonToASCII(string json)
{
	auto ret = appender!string;
	jsonEscape!true(ret, json);
	return ret.data;
}


/// private
private void jsonEscape(bool escape_unicode = false, R)(ref R dst, string s)
{
	for (size_t pos = 0; pos < s.length; pos++) {
		immutable(char) ch = s[pos];

		switch (ch) {
			default:
				static if (escape_unicode) {
					if (ch > 0x20 && ch < 0x80) dst.put(ch);
					else {
						import std.utf : decode;
						char[13] buf;
						int len;
						dchar codepoint = decode(s, pos);
						import core.stdc.stdio : sprintf;
						/* codepoint is in BMP */
						if(codepoint < 0x10000)
						{
							sprintf(&buf[0], "\\u%04X", codepoint);
							len = 6;
						}
						/* not in BMP -> construct a UTF-16 surrogate pair */
						else
						{
							int first, last;

							codepoint -= 0x10000;
							first = 0xD800 | ((codepoint & 0xffc00) >> 10);
							last = 0xDC00 | (codepoint & 0x003ff);

							sprintf(&buf[0], "\\u%04X\\u%04X", first, last);
							len = 12;
						}

						pos -= 1;
						foreach (i; 0 .. len)
							dst.put(buf[i]);

					}
				} else {
					if (ch < 0x20) dst.formattedWrite("\\u%04X", ch);
					else dst.put(ch);
				}
				break;
			case '\\': dst.put("\\\\"); break;
			case '\r': dst.put("\\r"); break;
			case '\n': dst.put("\\n"); break;
			case '\t': dst.put("\\t"); break;
			case '\"': dst.put("\\\""); break;
		}
	}
}

/// private
private string jsonUnescape(R)(ref R range, string filename, int* line)
{
	auto ret = appender!string();
	while(!range.empty){
		auto ch = range.front;
		switch( ch ){
			case '"': return ret.data;
			case '\\':
				range.popFront();
				enforceJson(!range.empty, "Unterminated string escape sequence.", filename, line);
				switch(range.front){
					default: enforceJson(false, "Invalid string escape sequence.", filename, line); break;
					case '"': ret.put('\"'); range.popFront(); break;
					case '\\': ret.put('\\'); range.popFront(); break;
					case '/': ret.put('/'); range.popFront(); break;
					case 'b': ret.put('\b'); range.popFront(); break;
					case 'f': ret.put('\f'); range.popFront(); break;
					case 'n': ret.put('\n'); range.popFront(); break;
					case 'r': ret.put('\r'); range.popFront(); break;
					case 't': ret.put('\t'); range.popFront(); break;
					case 'u':

						dchar decode_unicode_escape() {
							enforceJson(range.front == 'u');
							range.popFront();
							dchar uch = 0;
							foreach( i; 0 .. 4 ){
								uch *= 16;
								enforceJson(!range.empty, "Unicode sequence must be '\\uXXXX'.", filename, line);
								auto dc = range.front;
								range.popFront();

								if( dc >= '0' && dc <= '9' ) uch += dc - '0';
								else if( dc >= 'a' && dc <= 'f' ) uch += dc - 'a' + 10;
								else if( dc >= 'A' && dc <= 'F' ) uch += dc - 'A' + 10;
								else enforceJson(false, "Unicode sequence must be '\\uXXXX'.", filename, line);
							}
							return uch;
						}

						auto uch = decode_unicode_escape();

						if(0xD800 <= uch && uch <= 0xDBFF) {
							/* surrogate pair */
							range.popFront(); // backslash '\'
							auto uch2 = decode_unicode_escape();
							enforceJson(0xDC00 <= uch2 && uch2 <= 0xDFFF, "invalid Unicode", filename, line);
							{
								/* valid second surrogate */
								uch =
									((uch - 0xD800) << 10) +
										(uch2 - 0xDC00) +
										0x10000;
							}
						}
						ret.put(uch);
						break;
				}
				break;
			default:
				ret.put(ch);
				range.popFront();
				break;
		}
	}
	return ret.data;
}

/// private
private string skipNumber(R)(ref R s, out bool is_float, string filename, int* line)
{
	// TODO: make this work with input ranges
	size_t idx = 0;
	is_float = false;
	if (s[idx] == '-') idx++;
	if (s[idx] == '0') idx++;
	else {
		enforceJson(isDigit(s[idx++]), "Digit expected at beginning of number.", filename, line);
		while( idx < s.length && isDigit(s[idx]) ) idx++;
	}

	if( idx < s.length && s[idx] == '.' ){
		idx++;
		is_float = true;
		while( idx < s.length && isDigit(s[idx]) ) idx++;
	}

	if( idx < s.length && (s[idx] == 'e' || s[idx] == 'E') ){
		idx++;
		is_float = true;
		if( idx < s.length && (s[idx] == '+' || s[idx] == '-') ) idx++;
		enforceJson( idx < s.length && isDigit(s[idx]), "Expected exponent." ~ s[0 .. idx], filename, line);
		idx++;
		while( idx < s.length && isDigit(s[idx]) ) idx++;
	}

	string ret = s[0 .. idx];
	s = s[idx .. $];
	return ret;
}

/// private
private string skipJsonString(R)(ref R s, string filename, int* line)
{
	// TODO: count or disallow any newlines inside of the string
	enforceJson(!s.empty && s.front == '"', "Expected '\"' to start string.", filename, line);
	s.popFront();
	string ret = jsonUnescape(s, filename, line);
	enforceJson(!s.empty && s.front == '"', "Expected '\"' to terminate string.", filename, line);
	s.popFront();
	return ret;
}

/// private
private void skipWhitespace(R)(ref R s, int* line = null)
{
	while (!s.empty) {
		switch (s.front) {
			default: return;
			case ' ', '\t': s.popFront(); break;
			case '\n':
				s.popFront();
				if (!s.empty && s.front == '\r') s.popFront();
				if (line) (*line)++;
				break;
			case '\r':
				s.popFront();
				if (!s.empty && s.front == '\n') s.popFront();
				if (line) (*line)++;
				break;
		}
	}
}

private bool isDigit(dchar ch) { return ch >= '0' && ch <= '9'; }

private string underscoreStrip(string field_name)
{
	if( field_name.length < 1 || field_name[$-1] != '_' ) return field_name;
	else return field_name[0 .. $-1];
}

/// private
package template isJsonSerializable(T) { enum isJsonSerializable = is(typeof(T.init.toJson()) == Json) && is(typeof(T.fromJson(Json())) == T); }

private void enforceJson(string file = __FILE__, size_t line = __LINE__)(bool cond, lazy string message = "JSON exception")
{
	static if (__VERSION__ >= 2079)
		enforce!JSONException(cond, message, file, line);
	else
		enforceEx!JSONException(cond, message, file, line);
}

private void enforceJson(string file = __FILE__, size_t line = __LINE__)(bool cond, lazy string message, string err_file, int err_line)
{
	auto errmsg() { return format("%s(%s): Error: %s", err_file, err_line+1, message); }
	static if (__VERSION__ >= 2079)
		enforce!JSONException(cond, errmsg, file, line);
	else
		enforceEx!JSONException(cond, errmsg, file, line);
}

private void enforceJson(string file = __FILE__, size_t line = __LINE__)(bool cond, lazy string message, string err_file, int* err_line)
{
	enforceJson!(file, line)(cond, message, err_file, err_line ? *err_line : -1);
}
