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 }