1 module vibrato.json;
2 
3 
4 import std.format;
5 import std.range;
6 import std.traits;
7 
8 
9 void escapeJSONString(Appender, T)(ref Appender app, T value) if (isSomeString!T) {
10 	app.put('"');
11 	auto start = value.ptr;
12 	auto end = value.ptr + value.length;
13 	auto ptr = start;
14 	while (ptr != end) {
15 		switch(*ptr) {
16 			case '"':
17 				app.put(start[0..ptr-start]);
18 				start = ptr + 1;
19 				app.put('\\');
20 				app.put('"');
21 				break;
22 			case '\\':
23 				app.put(start[0..ptr-start+1]);
24 				start = ptr + 1;
25 				app.put('\\');
26 				break;
27 			case '\n':
28 				app.put(start[0..ptr-start]);
29 				start = ptr + 1;
30 				app.put('\\');
31 				app.put('n');
32 				break;
33 			case '\r':
34 				app.put(start[0..ptr-start]);
35 				start = ptr + 1;
36 				app.put('\\');
37 				app.put('r');
38 				break;
39 			case '\t':
40 				app.put(start[0..ptr-start]);
41 				start = ptr + 1;
42 				app.put('\\');
43 				app.put('t');
44 				break;
45 			case '\b':
46 				app.put(start[0..ptr-start]);
47 				start = ptr + 1;
48 				app.put('\\');
49 				app.put('b');
50 				break;
51 			case '\f':
52 				app.put(start[0..ptr-start]);
53 				start = ptr + 1;
54 				app.put('\\');
55 				app.put('f');
56 				break;
57 			default:
58 				break;
59 		}
60 
61 		++ptr;
62 	}
63 	app.put(start[0..ptr-start]);
64 	app.put('"');
65 }
66 
67 
68 private struct JSONFragment {
69 	private this(size_t alloc) {
70 		json_.reserve(alloc);
71 		states_.reserve(8);
72 		states_ ~= StateInfo(States.Ready, 0);
73 	}
74 
75 	auto ref clear() {
76 		json_.clear;
77 		states_.length = 0;
78 		states_ ~= StateInfo(States.Ready, 0);
79 
80 		return this;
81 	}
82 
83 	auto ref beginArray() {
84 		assert((states_.back.state != States.Finished) && (states_.back.state != States.InObject));
85 
86 		if (states_.back.state != States.InField) {
87 			if (states_.back.count > 0)
88 				json_.put(',');
89 			++states_.back.count;
90 		}
91 
92 		states_ ~= StateInfo(States.InArray);
93 		json_.put('[');
94 
95 		return this;
96 	}
97 
98 	auto ref endArray() {
99 		assert(states_.back.state == States.InArray);
100 
101 		--states_.length;
102 		json_.put(']');
103 
104 		if (states_.back.state == States.Ready) {
105 			states_.back.state = States.Finished;
106 		} else if (states_.back.state == States.InField) {
107 			states_.back.state = States.InObject;
108 		}
109 
110 		return this;
111 	}
112 
113 	auto ref beginObject() {
114 		assert((states_.back.state != States.Finished) && (states_.back.state != States.InObject));
115 
116 		if (states_.back.state != States.InField) {
117 			if (states_.back.count > 0)
118 				json_.put(',');
119 			++states_.back.count;
120 		}
121 
122 		states_ ~= StateInfo(States.InObject);
123 		json_.put('{');
124 
125 		return this;
126 	}
127 
128 	auto ref endObject() {
129 		assert(states_.back.state == States.InObject);
130 
131 		--states_.length;
132 		json_.put('}');
133 
134 		if (states_.back.state == States.Ready) {
135 			states_.back.state = States.Finished;
136 		} else if (states_.back.state == States.InField) {
137 			states_.back.state = States.InObject;
138 		}
139 
140 		return this;
141 	}
142 
143 	auto ref value(T)(in T x) {
144 		assert((states_.back.state != States.Finished) && (states_.back.state != States.InObject));
145 
146 		if (states_.back.state != States.InField) {
147 			if (states_.back.count > 0)
148 				json_.put(',');
149 			++states_.back.count;
150 		}
151 
152 		static if (isSomeString!T) {
153 			json_.escapeJSONString(x);
154 		} else static if (isArray!T) {
155 			beginArray();
156 			static if (!is(Unqual!(typeof(T.init[0])) == void)) {
157 				foreach(ref item; x)
158 					value(item);
159 			}
160 			endArray();
161 		} else static if (isAssociativeArray!T) {
162 			beginObject();
163 			foreach(ref key, ref item; x) {
164 				field(key);
165 				value(item);
166 			}
167 			endObject();
168 		} else static if (is(T == bool)) {
169 			json_.put(x ? "true" : "false");
170 		} else static if (is(T == struct)) {
171 			beginObject();
172 			static if (__traits(allMembers, T).length) {
173 				foreach(member; __traits(allMembers, T)) {
174 					field(member);
175 					value(__traits(getMember, x, member));
176 				}
177 			}
178 			endObject();
179 		} else {
180 			json_.formattedWrite("%s", x);
181 		}
182 
183 		if (states_.back.state == States.Ready) {
184 			states_.back.state = States.Finished;
185 		} else if (states_.back.state == States.InField) {
186 			states_.back.state = States.InObject;
187 		}
188 
189 		return this;
190 	}
191 
192 	auto ref field(T)(in T name) {
193 		assert(states_.back.state == States.InObject);
194 
195 		if (states_.back.count > 0)
196 			json_.put(',');
197 		++states_.back.count;
198 
199 		states_.back.state = States.InField;
200 
201 		static if (isSomeString!T) {
202 			json_.escapeJSONString(name);
203 		} else {
204 			json_.formattedWrite("\"%s\"", name);
205 		}
206 		json_.put(':');
207 
208 
209 		return this;
210 	}
211 
212 	@property const(char)[] json() {
213 		assert((states_.length == 1) && (states_.back.state == States.Finished));
214 		return json_.data;
215 	}
216 
217 	enum States : uint {
218 		Ready = 0,
219 		InArray,
220 		InObject,
221 		InField,
222 		Finished,
223 	}
224 
225 	struct StateInfo {
226 		States state;
227 		uint count;
228 	}
229 
230 	private Appender!(char[]) json_;
231 	private StateInfo[] states_;
232 }
233 
234 
235 auto ref jsonWriter(size_t alloc = 2048) {
236 	auto frag = JSONFragment(alloc);
237 	return frag;
238 }