1 /**
2  * Mechanism to parse JSON data into a JSON object tree. Some aspects borrowed
3  * from std.json.
4  */
5 module iopipe.json.dom;
6 import iopipe.json.parser;
7 public import iopipe.json.common;
8 import iopipe.traits;
9 import std.traits;
10 
11 enum JSONType
12 {
13     Integer,
14     Floating,
15     String,
16     Obj,
17     Array,
18     Null,
19     Bool,
20 }
21 
22 struct JSONValue(SType)
23 {
24     // basically a tagged union.
25     JSONType type;
26     union
27     {
28         long integer;
29         real floating;
30         JSONValue[] array;
31         JSONValue[immutable(SType)] object;
32         SType str;
33         bool boolean;
34     }
35 }
36 
37 private JSONValue!SType buildValue(SType, Tokenizer)(ref Tokenizer parser, JSONItem item, ReleasePolicy relPol)
38 {
39     import std.conv;
40 
41     alias JT = JSONValue!SType;
42     with(JSONToken) switch (item.token)
43     {
44     case ObjectStart:
45         return parser.buildObject!SType(relPol);
46     case ArrayStart:
47         return parser.buildArray!SType(relPol);
48     case String:
49         // See if we require copying.
50         {
51             JT result;
52             result.type = JSONType.String;
53             if(item.hint == JSONParseHint.InPlace)
54             {
55                 // get the data
56                 // TODO: need to duplicate, even if it's the same type
57                 result.str = item.data(parser.chain).to!SType;
58                 return result;
59             }
60             else
61             {
62                 // put the quotes back
63                 item.offset--;
64                 item.length += 2;
65 
66                 // re-parse, this time replacing escapes. This is so ugly...
67                 static if(is(typeof(newpipe[] = parser.chain.window[])))
68                 {
69                     auto newpipe = new Unqual!(typeof(SType.init[0]))[item.length];
70                     newpipe[] = item.data(parser.chain);
71                 }
72                 else
73                 {
74                     auto newpipe = item.data(parser.chain).to!(Unqual!(typeof(SType.init[0]))[]);
75                 }
76                 size_t pos = 0;
77                 auto len = parseString(newpipe, pos, item.hint);
78                 result.str = cast(typeof(result.str))newpipe[1 .. 1 + len];
79                 return result;
80             }
81         }
82     case Number:
83         {
84             // if it's an integer, parse as an integer. If not, parse as a float.
85             // TODO: really this should be done while parsing, not that hard.
86             import std.conv: parse;
87             JT result;
88             auto str = item.data(parser.chain);
89             if(item.hint == JSONParseHint.Int)
90             {
91                 result.type = JSONType.Integer;
92                 result.integer = parse!long(str);
93                 assert(str.length == 0);
94                 return result;
95             }
96             else
97             {
98                 // floating point or with exponent
99                 result.type = JSONType.Floating;
100                 result.floating = parse!real(str);
101                 assert(str.length == 0);
102                 return result;
103             }
104         }
105     case Null:
106         {
107             JT result;
108             result.type = JSONType.Null;
109             return result;
110         }
111     case True:
112         {
113             JT result;
114             result.type = JSONType.Bool;
115             result.boolean = true;
116             return result;
117         }
118     case False:
119         {
120             JT result;
121             result.type = JSONType.Bool;
122             result.boolean = false;
123             return result;
124         }
125     default:
126         throw new Exception("Error in JSON data");
127     }
128 }
129 
130 private JSONValue!SType buildObject(SType, Tokenizer)(ref Tokenizer parser, ReleasePolicy relPol)
131 {
132 
133     alias JT = JSONValue!SType;
134     auto item = parser.next();
135     JT obj;
136     obj.type = JSONType.Obj;
137     while(item.token != JSONToken.ObjectEnd)
138     {
139         if(item.token == JSONToken.Comma)
140         {
141             item = parser.next;
142             continue;
143         }
144         // the item must be a string
145         assert(item.token == JSONToken.String);
146         auto name = parser.buildValue!SType(item, relPol);
147         item = parser.next();
148         // should always be colon
149         assert(item.token == JSONToken.Colon);
150         item = parser.next();
151         obj.object[name.str.idup] = parser.buildValue!SType(item, relPol);
152         // release any parsed data.
153         if(relPol == ReleasePolicy.afterMembers)
154             parser.releaseParsed();
155         item = parser.next();
156     }
157     return obj;
158 }
159 
160 private JSONValue!SType buildArray(SType, Tokenizer)(ref Tokenizer parser, ReleasePolicy relPol)
161 {
162     alias JT = JSONValue!SType;
163     auto item = parser.next();
164     JT arr;
165     arr.type = JSONType.Array;
166     while(item.token != JSONToken.ArrayEnd)
167     {
168         arr.array ~= parser.buildValue!SType(item, relPol);
169         if(relPol == ReleasePolicy.afterMembers)
170             parser.releaseParsed();
171         item = parser.next();
172         if(item.token == JSONToken.Comma)
173             item = parser.next();
174     }
175     return arr;
176 }
177 
178 auto parseJSON(Tokenizer)(ref Tokenizer tokenizer, ReleasePolicy relPol = ReleasePolicy.afterMembers) if (isInstanceOf!(JSONTokenizer, Tokenizer))
179 {
180     return parseJSON!(WindowType!(typeof(tokenizer.chain)))(tokenizer, relPol);
181 }
182 
183 auto parseJSON(SType, Tokenizer)(ref Tokenizer tokenizer, ReleasePolicy relPol = ReleasePolicy.afterMembers) if (isInstanceOf!(JSONTokenizer, Tokenizer))
184 {
185     auto item = tokenizer.next();
186     auto result = tokenizer.buildValue!SType(item, relPol);
187     if(relPol == ReleasePolicy.afterMembers)
188         tokenizer.releaseParsed();
189     return result;
190 }
191 
192 auto parseJSON(SType = void, Chain)(Chain chain) if (isIopipe!Chain && is(SType == void))
193 {
194     return parseJSON!(WindowType!Chain)(chain);
195 }
196 
197 auto parseJSON(SType, Chain)(Chain chain) if (isIopipe!Chain)
198 {
199     enum shouldReplaceEscapes = is(typeof(chain.window[0] = chain.window[1]));
200     auto tokenizer = (chain).jsonTokenizer!(shouldReplaceEscapes);
201     return tokenizer.parseJSON!SType(ReleasePolicy.afterMembers);
202 }
203 
204 void printTree(JT)(JT item)
205 {
206     import std.stdio;
207     final switch(item.type) with (JSONType)
208     {
209     case Obj:
210         {
211             write("{");
212             bool first = true;
213             foreach(n, v; item.object)
214             {
215                 if(first)
216                     first = false;
217                 else
218                     write(", ");
219                 writef(`"%s" : `, n);
220                 printTree(v);
221             }
222             write("}");
223         }
224         break;
225     case Array:
226         {
227             write("[");
228             bool first = true;
229             foreach(v; item.array)
230             {
231                 if(first)
232                     first = false;
233                 else
234                     write(", ");
235                 printTree(v);
236             }
237             write("]");
238         }
239         break;
240     case Integer:
241         write(item.integer);
242         break;
243     case Floating:
244         write(item.floating);
245         break;
246     case Null:
247         write("null");
248         break;
249     case Bool:
250         write(item.boolean);
251         break;
252     case String:
253         writef(`"%s"`, item.str);
254         break;
255     }
256 }
257 
258 unittest
259 {
260     auto jt = parseJSON(q"{{"a" : [1, 2.5, "x", true, false, null]}}");
261     //printTree(jt);
262     auto jt2 = parseJSON!(wstring)(q"{{"a" : [1, 2.5, "x\ua123", true, false, null]}}");
263     //printTree(jt2);
264 }