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 }