1 // Written in the D programming language. 2 3 /** 4 * Yajl Encoder 5 * 6 * Example: 7 * ----- 8 * Encoder encoder; 9 * assert(encoder.encode(["foo":"bar"] == `{"foo":"bar"}`)); 10 * ----- 11 * 12 * with option: 13 * ----- 14 * Encoder.Option opt; 15 * opt.beautify = true; 16 * opt.indentString = " "; 17 * Encoder encoder = Encoder(opt); 18 * assert(encoder.encode(["foo":"bar"] == 19 * `{ 20 * "foo": "bar" 21 * } 22 * `)); 23 * ----- 24 * 25 * See_Also: 26 * $(LINK2 http://lloyd.github.com/yajl/yajl-2.0.1/yajl__gen_8h.html, Yajl gen header)$(BR) 27 * 28 * Copyright: Copyright Masahiro Nakagawa 2013-. 29 * License: <a href="http://www.boost.org/LICENSE_1_0.txt">Boost License 1.0</a>. 30 * Authors: Masahiro Nakagawa 31 */ 32 module yajl.encoder; 33 34 import yajl.c.gen; 35 import yajl.common; 36 37 import std.traits; 38 39 40 /** 41 * Encoder provides the method for serializing a D object into JSON format. 42 */ 43 struct Encoder 44 { 45 //alias yajl_print_t PrintCallback; 46 47 /// See: http://lloyd.github.io/yajl/yajl-2.1.0/yajl__gen_8h.html#a57c29080044a7231ac0cf1fead4de4b0 48 static struct Option 49 { 50 bool beautify; 51 bool validateUTF8; 52 bool escapeSolidus; 53 string indentString; 54 //PrintCallback printCallback; // TODO: Fix "killed by signal 11" 55 } 56 57 private: 58 yajl_gen _gen; 59 60 public: 61 /** 62 * Constructs an Encoder object with $(D_PARAM opt). 63 */ 64 @trusted 65 this(ref Option opt) 66 { 67 initialize(); 68 setEncoderConfig(_gen, opt); 69 } 70 71 @trusted 72 ~this() 73 { 74 clear(); 75 } 76 77 /** 78 * Encodes an argument and returns the JSON. 79 */ 80 @trusted 81 string encode(T)(auto ref T value) 82 { 83 initialize(); 84 85 yajlGenerate(_gen, value); 86 87 ubyte* resultBuffer; 88 size_t resultLength; 89 90 yajl_gen_get_buf(_gen, &resultBuffer, &resultLength); 91 92 string result = cast(string)resultBuffer[0..resultLength].dup; 93 94 // reset state and internal buffer for next encode 95 yajl_gen_reset(_gen, null); 96 yajl_gen_clear(_gen); 97 98 return result; 99 } 100 101 private: 102 void initialize() 103 { 104 if (_gen is null) 105 _gen = yajl_gen_alloc(&yajlAllocFuncs); 106 } 107 108 void clear() 109 { 110 if (_gen !is null) { 111 yajl_gen_free(_gen); 112 _gen = null; 113 } 114 } 115 116 @trusted 117 static void setEncoderConfig(yajl_gen gen, ref Encoder.Option opt) 118 { 119 import std.string : toStringz; 120 121 if (opt.beautify) 122 yajl_gen_config(gen, yajl_gen_option.yajl_gen_beautify, 1); 123 if (opt.validateUTF8) 124 yajl_gen_config(gen, yajl_gen_option.yajl_gen_validate_utf8, 1); 125 if (opt.escapeSolidus) 126 yajl_gen_config(gen, yajl_gen_option.yajl_gen_escape_solidus, 1); 127 if (opt.indentString) 128 yajl_gen_config(gen, yajl_gen_option.yajl_gen_indent_string, toStringz(opt.indentString)); 129 //if (opt.printCallback) 130 // yajl_gen_config(gen, yajl_gen_option.yajl_gen_print_callback, opt.printCallback); 131 } 132 } 133 134 unittest 135 { 136 static struct Handa 137 { 138 ulong id; 139 string name; 140 double height; 141 } 142 143 Handa handa = Handa(1000, "shinobu", 169.5); 144 145 { 146 Encoder encoder; 147 assert(encoder.encode(handa) == `{"id":1000,"name":"shinobu","height":169.5}`); 148 } 149 { // opt 150 Encoder.Option opt; 151 opt.beautify = true; 152 opt.indentString = " "; 153 assert(Encoder(opt).encode(handa) == `{ 154 "id": 1000, 155 "name": "shinobu", 156 "height": 169.5 157 } 158 `); 159 } 160 } 161 162 unittest 163 { 164 static struct JSONNameTest 165 { 166 @JSONName("body") string _body; 167 } 168 169 JSONNameTest jnt = JSONNameTest("body!"); 170 171 { 172 Encoder encoder; 173 assert(encoder.encode(jnt) == `{"body":"body!"}`); 174 } 175 } 176 177 private: 178 179 @trusted 180 void yajlGenerate(T)(yajl_gen gen, auto ref T value) 181 { 182 import std.conv : to; 183 import std.typecons : isTuple; 184 185 static if (isBoolean!T) 186 { 187 checkStatus(yajl_gen_bool(gen, value ? 1 : 0)); 188 } 189 else static if (isIntegral!T) 190 { 191 checkStatus(yajl_gen_integer(gen, value)); 192 } 193 else static if (isFloatingPoint!T) 194 { 195 checkStatus(yajl_gen_double(gen, value)); 196 } 197 else static if (isSomeString!T) 198 { 199 checkStatus(yajl_gen_string(gen, cast(const(ubyte)*)value.ptr, value.length)); 200 } 201 else static if (isArray!T) 202 { 203 if (value is null) { 204 checkStatus(yajl_gen_null(gen)); 205 } else { 206 checkStatus(yajl_gen_array_open(gen)); 207 foreach (ref v; value) 208 yajlGenerate(gen, v); 209 checkStatus(yajl_gen_array_close(gen)); 210 } 211 } 212 else static if (isAssociativeArray!T) 213 { 214 if (value is null) { 215 checkStatus(yajl_gen_null(gen)); 216 } else { 217 checkStatus(yajl_gen_map_open(gen)); 218 foreach (k, ref v; value) { 219 yajlGenerate(gen, to!string(k)); 220 yajlGenerate(gen, v); 221 } 222 checkStatus(yajl_gen_map_close(gen)); 223 } 224 } 225 else static if (isTuple!T) 226 { 227 checkStatus(yajl_gen_array_open(gen)); 228 foreach (i, Type; T.Types) 229 yajlGenerate(gen, value[i]); 230 checkStatus(yajl_gen_array_close(gen)); 231 } 232 else static if (is(T == struct) || is(T == class)) 233 { 234 static if (is(T == class)) 235 { 236 if (value is null) { 237 checkStatus(yajl_gen_null(gen)); 238 return; 239 } 240 } 241 242 checkStatus(yajl_gen_map_open(gen)); 243 foreach (i, ref v; value.tupleof) { 244 static if (isNullable!(typeof(v))) 245 { 246 if (!v.isNull) { 247 yajlGenerate(gen, getFieldName!(T, i)); 248 yajlGenerate(gen, v.get); 249 } 250 } 251 else 252 { 253 yajlGenerate(gen, getFieldName!(T, i)); 254 yajlGenerate(gen, v); 255 } 256 } 257 checkStatus(yajl_gen_map_close(gen)); 258 } 259 260 return; 261 } 262 263 // status check is needed? 264 @safe 265 void checkStatus(yajl_gen_status status) pure 266 { 267 if (status != yajl_gen_status.yajl_gen_status_ok) 268 throw new YajlException(formatStatus(status)); 269 } 270 271 @safe 272 string formatStatus(in yajl_gen_status status) pure 273 { 274 final switch (status) { 275 case yajl_gen_status.yajl_gen_status_ok: 276 return null; 277 case yajl_gen_status.yajl_gen_keys_must_be_strings: 278 return "A map key is generated, a function other than yajl_gen_string was called"; 279 case yajl_gen_status.yajl_max_depth_exceeded: 280 return "YAJL's maximum generation depth was exceeded"; 281 case yajl_gen_status.yajl_gen_in_error_state: 282 return "A generator function was called while in an error state"; 283 case yajl_gen_status.yajl_gen_generation_complete: 284 return "A complete JSON document has been generated"; 285 case yajl_gen_status.yajl_gen_invalid_number: 286 return "Invalid floating point value (infinity or NaN)"; 287 case yajl_gen_status.yajl_gen_no_buf: 288 return "There is no internal buffer to get from print callback"; 289 case yajl_gen_status.yajl_gen_invalid_string: 290 return "Returned invalid utf8 string from yajl_gen_string"; 291 } 292 }