1 module siryul.json;
2 private import siryul.common;
3 private import std.json : JSONValue, JSON_TYPE, parseJSON, toJSON;
4 private import std.range.primitives : ElementType, isInfinite, isInputRange;
5 private import std.traits : isSomeChar;
6 private import std.typecons;
7 /++
8  + JSON (JavaScript Object Notation) serialization format
9  +
10  + Note that only strings are supported for associative array keys in this format.
11  +/
12 struct JSON {
13 	private import std.meta : AliasSeq;
14 	package alias types = AliasSeq!".json";
15 	package enum emptyObject = "{}";
16 	package static T parseInput(T, DeSiryulize flags, U)(U data) if (isInputRange!U && isSomeChar!(ElementType!U)) {
17 		return fromJSON!(T,BitFlags!DeSiryulize(flags))(parseJSON(data), T.stringof);
18 	}
19 	package static string asString(Siryulize flags, T)(T data) {
20 		const json = data.toJSON!(BitFlags!Siryulize(flags));
21 		return toJSON(json, true);
22 	}
23 }
24 
25 private T fromJSON(T, BitFlags!DeSiryulize flags)(JSONValue node, string path = "") if (!isInfinite!T) {
26 	import std.conv : text, to;
27 	import std.datetime : Date, DateTime, SysTime, TimeOfDay;
28 	import std.exception : enforce;
29 	import std.meta : AliasSeq;
30 	import std.range.primitives : front;
31 	import std.range : enumerate, isOutputRange;
32 	import std.traits : arity, FieldNameTuple, ForeachType, getUDAs, hasIndirections, hasUDA, isAssociativeArray, isFloatingPoint, isIntegral, isPointer, isSomeChar, isSomeString, isStaticArray, OriginalType, Parameters, PointerTarget, TemplateArgsOf, ValueType;
33 	import std.utf : byCodeUnit;
34 	static if (is(T == struct) && hasDeserializationMethod!T) {
35 		return deserializationMethod!T(fromJSON!(Parameters!(deserializationMethod!T), flags)(node, path));
36 	} else static if (is(T == enum)) {
37 		import std.conv : to;
38 		if (node.type == JSON_TYPE.STRING)
39 			return node.str.to!T;
40 		else
41 			return fromJSON!(OriginalType!T, flags)(node, path).to!T;
42 	} else static if (isIntegral!T) {
43 		expect(node, JSON_TYPE.INTEGER, JSON_TYPE.STRING);
44 		if (node.type == JSON_TYPE.STRING)
45 			return node.str.to!T;
46 		return node.integer.to!T;
47 	} else static if (isNullable!T) {
48 		T output;
49 		if (node.type == JSON_TYPE.NULL)
50 			output.nullify();
51 		else
52 			output = fromJSON!(typeof(output.get), flags)(node, path);
53 		return output;
54 	} else static if (isFloatingPoint!T) {
55 		expect(node, JSON_TYPE.FLOAT, JSON_TYPE.INTEGER, JSON_TYPE.STRING);
56 		if (node.type == JSON_TYPE.STRING) {
57 			return node.str.to!T;
58 		}
59 		if (node.type == JSON_TYPE.INTEGER) {
60 			return node.integer.to!T;
61 		}
62 		return node.floating.to!T;
63 	} else static if (isSomeString!T) {
64 		expect(node, JSON_TYPE.STRING, JSON_TYPE.INTEGER, JSON_TYPE.NULL);
65 		if (node.type == JSON_TYPE.INTEGER)
66 			return node.integer.to!T;
67 		if (node.type == JSON_TYPE.NULL)
68 			return T.init;
69 		return node.str.to!T;
70 	} else static if (isSomeChar!T) {
71 		expect(node, JSON_TYPE.STRING, JSON_TYPE.NULL);
72 		if (node.type == JSON_TYPE.NULL)
73 			return T.init;
74 		return node.str.front.to!T;
75 	} else static if (is(T == SysTime) || is(T == DateTime) || is(T == Date) || is(T == TimeOfDay)) {
76 		return T.fromISOExtString(fromJSON!(string, flags)(node, path));
77 	} else static if (is(T == struct) || (isPointer!T && is(PointerTarget!T == struct))) {
78 		expect(node, JSON_TYPE.OBJECT);
79 		T output;
80 		static if (isPointer!T) {
81 			output = new PointerTarget!T;
82 			alias Undecorated = PointerTarget!T;
83 		} else {
84 			alias Undecorated = T;
85 		}
86 		foreach (member; FieldNameTuple!Undecorated) {
87 			static if (__traits(getProtection, __traits(getMember, Undecorated, member)) == "public") {
88 				debug string newPath = path~"."~member;
89 				else string newPath = "";
90 				alias field = AliasSeq!(__traits(getMember, Undecorated, member));
91 				enum memberName = getMemberName!field;
92 				static if ((hasUDA!(field, Optional) || (!!(flags & DeSiryulize.optionalByDefault))) || hasIndirections!(typeof(field))) {
93 					if ((memberName !in node.object) || (node.object[memberName].type == JSON_TYPE.NULL))
94 						continue;
95 				} else {
96 					enforce!JSONDException(memberName in node.object, "Missing non-@Optional "~memberName~" in node");
97 				}
98 				alias fromFunc = getConvertFromFunc!(T, field);
99 				try {
100 					static if (hasUDA!(field, IgnoreErrors)) {
101 						try {
102 							__traits(getMember, output, member) = fromFunc(fromJSON!(Parameters!(fromFunc)[0], flags)(node[memberName], newPath));
103 						} catch (UnexpectedTypeException) {} //just skip it
104 					} else {
105 						__traits(getMember, output, member) = fromFunc(fromJSON!(Parameters!(fromFunc)[0], flags)(node[memberName], newPath));
106 					}
107 				} catch (Exception e) {
108 					e.msg = "Error deserializing "~newPath~": "~e.msg;
109 					throw e;
110 				}
111 			}
112 		}
113 		return output;
114 	} else static if(isOutputRange!(T, ElementType!T)) {
115 		import std.algorithm : copy, map;
116 		expect(node, JSON_TYPE.ARRAY);
117 		T output = new T(node.array.length);
118 		copy(node.array.map!(x => fromJSON!(ElementType!T, flags)(x, path)), output);
119 		return output;
120 	} else static if (isStaticArray!T && isSomeChar!(ElementType!T)) {
121 		expect(node, JSON_TYPE.STRING);
122 		T output;
123 		foreach (i, chr; fromJSON!((ForeachType!T)[], flags)(node, path).byCodeUnit.enumerate(0))
124 			output[i] = chr;
125 		return output;
126 	} else static if(isStaticArray!T) {
127 		expect(node, JSON_TYPE.ARRAY);
128 		enforce!JSONDException(node.array.length == T.length, "Static array length mismatch");
129 		T output;
130 		foreach (i, JSONValue newNode; node.array)
131 			output[i] = fromJSON!(ForeachType!T, flags)(newNode, path);
132 		return output;
133 	} else static if(isAssociativeArray!T) {
134 		expect(node, JSON_TYPE.OBJECT);
135 		T output;
136 		foreach (string key, JSONValue value; node.object)
137 			output[key] = fromJSON!(ValueType!T, flags)(value, path);
138 		return output;
139 	} else static if (is(T == bool)) {
140 		expect(node, JSON_TYPE.TRUE, JSON_TYPE.FALSE);
141 		if (node.type == JSON_TYPE.TRUE)
142 			return true;
143 		else if (node.type == JSON_TYPE.FALSE)
144 			return false;
145 		assert(false);
146 	} else
147 		static assert(false, "Cannot read type "~T.stringof~" from JSON"); //unreachable, hopefully.
148 }
149 private void expect(T...)(JSONValue node, T types) {
150 	import std.algorithm : among;
151 	import std.exception : enforce;
152 	enforce(node.type.among(types), new UnexpectedTypeException(types[0], node.type));
153 }
154 private @property JSONValue toJSON(BitFlags!Siryulize flags, T)(T type) if (!isInfinite!T) {
155 	import std.conv : text, to;
156 	import std.meta : AliasSeq;
157 	import std.range : isInputRange;
158 	import std.traits : arity, FieldNameTuple, getSymbolsByUDA, getUDAs, hasUDA, isArray, isAssociativeArray, isPointer, isSomeChar, isSomeString, isStaticArray, PointerTarget, Unqual;
159 	JSONValue output;
160 	static if (isPointer!T) {
161 		alias Undecorated = Unqual!(PointerTarget!T);
162 	} else {
163 		alias Undecorated = Unqual!T;
164 	}
165 	static if (is(T == struct) && hasSerializationMethod!T) {
166 		output = toJSON!flags(mixin("type."~__traits(identifier, serializationMethod!T)));
167 	} else static if (hasUDA!(type, AsString) || is(Undecorated == enum)) {
168 		output = JSONValue(type.text);
169 	} else static if (isNullable!Undecorated) {
170 		if (type.isNull && !(flags & Siryulize.omitNulls)) {
171 			output = JSONValue();
172 		} else {
173 			output = type.get().toJSON!flags;
174 		}
175 	} else static if (isTimeType!Undecorated) {
176 		output = JSONValue(type.toISOExtString());
177 	} else static if (canStoreUnchanged!Undecorated) {
178 		output = JSONValue(type.to!Undecorated);
179 	} else static if (isSomeString!Undecorated || (isStaticArray!Undecorated && isSomeChar!(ElementType!Undecorated))) {
180 		import std.utf : toUTF8;
181 		output = JSONValue(type[].toUTF8);
182 	} else static if (isSomeChar!Undecorated) {
183 		output = [type].idup.toJSON!flags;
184 	} else static if (isAssociativeArray!Undecorated) {
185 		string[string] arr;
186 		output = JSONValue(arr);
187 		foreach (key, value; type) {
188 			output.object[key.text] = value.toJSON!flags;
189 		}
190 	} else static if (isSimpleList!Undecorated) {
191 		string[] arr;
192 		output = JSONValue(arr);
193 		foreach (value; type) {
194 			output.array ~= value.toJSON!flags;
195 		}
196 	} else static if (is(Undecorated == struct)) {
197 		string[string] arr;
198 		output = JSONValue(arr);
199 		foreach (member; FieldNameTuple!Undecorated) {
200 			static if (__traits(getProtection, __traits(getMember, Undecorated, member)) == "public") {
201 				static if (!!(flags & Siryulize.omitInits)) {
202 					static if (isNullable!(typeof(__traits(getMember, T, member)))) {
203 						if (__traits(getMember, type, member).isNull) {
204 							continue;
205 						}
206 					} else {
207 						if (__traits(getMember, type, member) == __traits(getMember, type, member).init) {
208 							continue;
209 						}
210 					}
211 				}
212 				enum memberName = getMemberName!(__traits(getMember, Undecorated, member));
213 				output.object[memberName] = getConvertToFunc!(T, __traits(getMember, Undecorated, member))(mixin("type."~member)).toJSON!flags;
214 			}
215 		}
216 	} else {
217 		static assert(false, "Cannot write type "~T.stringof~" to JSON"); //unreachable, hopefully
218 	}
219 	return output;
220 }
221 private template isTimeType(T) {
222 	import std.datetime : DateTime, Date, SysTime, TimeOfDay;
223 	enum isTimeType = is(T == SysTime) || is(T == DateTime) || is(T == Date) || is(T == TimeOfDay);
224 }
225 private template canStoreUnchanged(T) {
226 	import std.traits : isFloatingPoint, isIntegral;
227 	enum canStoreUnchanged = isIntegral!T || is(T == string) || is(T == bool) || isFloatingPoint!T;
228 }
229 /++
230  + Thrown on JSON deserialization errors
231  +/
232 class JSONDException : DeserializeException {
233 	this(string msg, string file = __FILE__, size_t line = __LINE__) @safe pure nothrow {
234 		super(msg, file, line);
235 	}
236 }
237 /++
238 + Thrown when a JSON value has an unexpected type.
239 +/
240 class UnexpectedTypeException : JSONDException {
241 	package this(JSON_TYPE expectedType, JSON_TYPE unexpectedType, string file = __FILE__, size_t line = __LINE__) @safe pure nothrow {
242 		import std.conv : text;
243 		import std.exception : assumeWontThrow, ifThrown;
244 		super("Expecting JSON type "~assumeWontThrow(expectedType.text.ifThrown("Unknown"))~", got "~assumeWontThrow(unexpectedType.text.ifThrown("Unknown")), file, line);
245 	}
246 }