module Mailer.json; import std.string; import std.ctype; /** * * Should be full working version... * - * test compile * gdc json.d -fversion=jsontest -o/tmp/jsontest * * usage: * * * ----------------- * ENCODING * * x = jsonA(jArray); * x.add(int) * x.add(str) * .... * --> encoding object 'a' * x = jsonO(); * x.add("key1", a.strval); * x.add("key2", a.intval); * x.encode(); * x.encodeP(); // pretty version.. * * ---------------- * DECODING: * x = jsonDecode(std.file.read(...filename...)); * a = new ..... * x.get("key1", a.strval); * x.get("key2", a.intval); * * * * * does not handle null very well.. * */ JsonValue jsonV(int x) { return new JsonNumber(cast(real)x); } JsonValue jsonV(real x) { return new JsonNumber(x); } JsonValue jsonV(ulong x) { return new JsonNumber( x); } JsonValue jsonV(double x) { return new JsonNumber( x); } JsonValue jsonV(char[] x) { return new JsonString(x); } JsonValue jsonV(bool x) { return new JsonBool(x); } JsonValue jsonN() { return new JsonNull(); } JsonValue jsonA() { return new JsonArray(); } JsonValue jsonO() { return new JsonObject(); } public enum jType { Null, Number, Bool, String, Object, Array}; class JsonValue { jType type; real num = 0; char[] str = ""; bool bol = false; JsonValue[] ar; JsonValue[char[]] obj; // int. this (jType type) { this.type = type; } void add(JsonValue v) { assert(type==jType.Array); ar ~= v; } void add(int l) { add(jsonV(l)); } void add(ulong l) { add(jsonV(l)); } void add(real l) { add(jsonV(l)); } void add(char[] l) { add(jsonV(l)); } void add(bool l) { add(jsonV(l)); } void add(char[] k, JsonValue v) { assert(type==jType.Object); obj[k] = v; } void add(char[] k,int l) { add(k,jsonV(l)); } void add(char[] k,ulong l) { add(k,jsonV(l)); } void add(char[] k,char[] l) { add(k,jsonV(l)); } void add(char[] k,bool l) { add(k,jsonV(l)); } ubyte[] encode() { switch(type) { case jType.Null: return cast(ubyte[]) "null"; case jType.Bool: return cast(ubyte[]) (bol ? "true" : "false"); case jType.Number: if (num > 0 && num < ulong.max) return cast(ubyte[]) std.string.toString(cast(ulong) num); // what about -ve long numbers... return cast(ubyte[]) std.string.toString(num); default: throw new Exception("type not supported"); } } ubyte[] encodeP(int indent) { return encode(); } int length() { if (type!=jType.Array) { std.stdio.writefln("%s", cast(char[]) encode()); throw new Exception("I'm not an array"); } return ar.length; } // array fetchers JsonValue _get(int k, jType t) { assert(type==jType.Array); assert(k < ar.length); assert(ar[k].type == t); return ar[k]; } jType getType(int k) { if (type!=jType.Array) { throw new Exception("Expecting Array\nGOT : " ~ cast(char[])encode()); } assert(k < ar.length); return ar[k].type; } real getN(int k) { return _get(k, jType.Number).num; } bool getB(int k) { return _get(k, jType.Bool).bol; } char[] getS(int k) { return _get(k, jType.String).str; } JsonValue getA(int k) { return _get(k, jType.Array); } JsonValue getO(int k) { return _get(k, jType.Object); } // object fetchers // object fetchers -- may want to be able to iterate keys...??? JsonValue _get(char[] k, jType t) { if (type!=jType.Object) { throw new Exception("Expecting object\nGOT : " ~ cast(char[])encode()); } if (k in obj) { assert(obj[k].type == t); return obj[k]; } return new JsonNull(); } jType getType(char[] k) { if (type!=jType.Object) { throw new Exception("Expecting object\nGOT : " ~ cast(char[])encode()); } return (k in obj) ? obj[k].type : jType.Null; } bool hasKey(char[] key) { return (key in obj) ? true : false; } /* request Object value */ real getN(char[] k) { return _get(k, jType.Number).num; } bool getB(char[] k) { return _get(k, jType.Bool).bol; } char[] getS(char[] k) { return _get(k, jType.String).str; } JsonValue getA(char[] k) { return _get(k, jType.Array); } JsonValue getO(char[] k) { return _get(k, jType.Object); } /* request Object value into ... */ void get(char[] k, out int l) { if (hasKey(k)) { l = cast(int)_get(k, jType.Number).num; }} void get(char[] k, out uint l) { if (hasKey(k)) { l = cast(uint)_get(k, jType.Number).num; }} void get(char[] k, out ulong l) { if (hasKey(k)) { l = cast(ulong)_get(k, jType.Number).num; }} void get(char[] k, out bool l) { if (hasKey(k)) { l = _get(k, jType.Bool).bol; }} void get(char[] k, out char[] l) { if (hasKey(k)) { l = _get(k, jType.String).str; }} } class JsonNumber : JsonValue { this(real i ) { num = i; super(jType.Number); } } class JsonBool: JsonValue { this(bool i ) { bol = i; super(jType.Bool); } } class JsonNull: JsonValue { this() { super(jType.Null); } } class JsonString : JsonValue { this(char[] i ) { str = i; super(jType.String); } ubyte[] encode() { char[] zpad = "0000"; char[] r = new char[str.length+2]; //allocate min size.. r = "\""; wchar c; //ubyte[] xr; for (uint i = 0; i < str.length;i++) { c = str[i]; switch(c) { case '\\': r ~= "\\"; continue; case '"': r ~= "\\\""; continue; case '/': r ~= "\\/"; continue; case '\b': r ~= "\\b"; continue; case '\f': r ~= "\\f"; continue; case '\n': r ~= "\\n"; continue; case '\r': r ~= "\\r"; continue; case '\t': r ~= "\\t"; continue; default : if ((c >= 0x20) && (c <= 0x7f)) { r ~= c; continue; } if (c <= 0x7F) { //... } else { try { c = std.utf.decode(str, i); i--; // our loop does the extra ++; } catch(Exception e) { r ~= '?'; continue; // shoudl be ok about shifting I } } // c shoudl not contain utf16 char char[] h = std.string.toString(cast(ulong)c, cast(uint)16); // pad it... char[] bl = "\\u0000"; r ~= bl[0..6-h.length] ~ h; continue; /* // use x encoding -- not json standard!!! r~= cast(ubyte[]) "\\x"; xr = cast(ubyte[]) std.string.toString(cast(ulong)c, cast(uint)16); r ~= xr.length > 1 ? xr : cast(ubyte[]) "0" ~ xr; continue; if ((c & 0xE0) == 0xC0) { // characters U-00000080 - U-000007FF, mask 110XXXXX // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 if (i+1 >= str.length) { i++; r ~= cast(ubyte[])"??"; continue; } r ~= toU2(str[i],str[i+1]); i++; continue; } if ((c & 0xF0) == 0xE0) { // characters U-00000800 - U-0000FFFF, mask 1110XXXX // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 if (i+2 >= str.length) { i+=2; r ~= cast(ubyte[])"???"; continue; } r ~= toU3(str[i],str[i+1],str[i+2]); i+=2; break; } if ((c & 0xF8) == 0xF0) { i+=3; r ~= cast(ubyte[])"????"; continue; } if ((c & 0xFC) == 0xF8) { i+=4; r ~= cast(ubyte[])"?????"; continue; } if ((c & 0xFE) == 0xFC) { i+=5; r ~= cast(ubyte[])"??????"; continue; } r ~= cast(ubyte[])"?"; /// escape control? continue; */ } } // end foreach r ~= "\""; return cast(ubyte[]) r; } } class JsonArray : JsonValue { this() { super(jType.Array); } ubyte[] encode() { ubyte[] r = cast(ubyte[]) "["; foreach(i,jv; ar) { r ~= (i > 0) ? cast(ubyte[]) "," : cast(ubyte[]) ""; r ~= jv.encode(); } r ~= cast(ubyte[]) "]"; return r; } ubyte[] encodeP(int indent) { if (!ar.length) { return cast(ubyte[])"[]"; } ubyte[] tb; for (int i=0;i < indent; i++) { tb ~= '\t'; } ubyte[] tb2 = tb ~ cast(ubyte) '\t'; ubyte[] r = cast(ubyte[]) "[\n"; foreach(i,jv; ar) { r ~= (i > 0) ? cast(ubyte[]) ",\n" : cast(ubyte[]) ""; r ~= tb2; r ~= jv.encodeP(indent+1); } r ~= cast(ubyte)'\n'; r ~= tb; r ~= cast(ubyte[]) "]"; return r; } } class JsonObject : JsonValue { this() { super(jType.Object); } ubyte[] encode() { // indentation.... // {\n\t*k...v\n\t*} ubyte[] r = cast(ubyte[]) "{"; int n = 0; foreach(k,jv; obj) { r ~= (n > 0) ? cast(ubyte[]) "," : cast(ubyte[]) ""; r ~= jsonV(k).encode() ~ (cast(ubyte[])":") ~ jv.encode(); n++; } r ~= cast(ubyte[]) "}"; return r; } ubyte[] encodeP(int indent) // pretty version. { // indentation.... // {\n\t*k...v\n\t*} ubyte[] tb; for (int i=0;i < indent; i++) { tb ~= '\t'; } ubyte[] tb2 = tb ~ cast(ubyte) '\t'; ubyte[] r = cast(ubyte[]) "{\n"; int n = 0; foreach(k,jv; obj) { r ~= (n > 0) ? cast(ubyte[]) ",\n" : cast(ubyte[]) ""; r ~= tb2; r ~= jsonV(k).encode() ~ (cast(ubyte[])": ") ~ jv.encodeP(indent+1); n++; } if (n < 1) { return cast(ubyte[]) "{}"; } r ~= cast(ubyte)'\n'; r ~= tb; r ~= cast(ubyte[]) "}"; return r; } } public enum jToken { OSTART, OEND, ASTART, AEND, COLON, STRING, COMMA, NUMBER, TRUE, FALSE, NULL, }; class JsonParserToken { jToken type; ubyte[] str ; real uval; //double rval; this(jToken type) { this.type = type; //std.stdio.writefln("GOT TOKEN %d", cast(int)type); } this(jToken type, ubyte[] s) { //std.stdio.writefln("GOT TOKEN STRING %s", cast(char[])s); this.type = type; this.str = s; } this(jToken type , long v) { //std.stdio.writefln("GOT TOKEN Number %d", v); uval = cast(double)v; this.type = type; } this(jToken type , real v) { //std.stdio.writefln("GOT TOKEN Number %d", v); uval = v; this.type = type; } } JsonValue jsonDecode(ubyte[] str) { return (new JsonParser(str)).parse(); } class JsonParser { ubyte[] str; uint pos; this(ubyte[] str) { this.str = str; pos = 0; } JsonValue parse() { JsonParserToken t; t = getToken(); switch(t.type) { // if we are in an array.. these are valid.. case jToken.AEND: return null; case jToken.NUMBER: return jsonV(t.uval); case jToken.STRING: return jsonV(cast(char[])t.str); case jToken.TRUE: return jsonV(true); case jToken.FALSE: return jsonV(false); case jToken.NULL: return jsonN(); case jToken.OSTART: { JsonValue r = jsonO(); JsonValue v; char[] k; t = getToken(); if (t.type == jToken.OEND) { return r; } while (true) { assert(t.type == jToken.STRING); k = cast(char[]) t.str; t = getToken(); if (t.type != jToken.COLON) { std.stdio.writefln("%s<<<", cast(char[]) str[0..pos]); throw new Exception("Expecting COLON after object key"); } v = parse(); r.add(k,v); t = getToken(); if (t.type == jToken.OEND) { return r; } if (t.type != jToken.COMMA) { std.stdio.writefln("%s<<<", cast(char[]) str[0..pos]); throw new Exception("Expecting COMMA or end object"); } t = getToken(); } } case jToken.ASTART: { JsonValue r = jsonA(); JsonValue v = parse(); if (v is null) { // got OEND! - only applies to empty arrys... return r; } while (true) { assert(!(v is null)); r.add(v); t = getToken(); if (t.type == jToken.AEND) { return r; } if (t.type != jToken.COMMA) { throw new Exception("Expecting COMMA OR end array"); } v = parse(); } } default: throw new Exception("invalid token"); } throw new Exception("nothing found!"); } JsonParserToken getToken() { ubyte c; while (pos < str.length) { c = str[pos]; pos++; switch (c) { case ' ': // whitespace.. case '\t': case '\r': case '\n': continue; case '/': if (str[pos] == '/') { pos++; while (pos < str.length) { if (str[pos] == '\n') { break; } pos++; } continue; } if (str[pos] == '*') { pos++; while (pos < str.length) { if ((str[pos] == '*') && (str[pos+1] == '/')) { pos+=2; break; } pos++; } continue; } std.stdio.writefln("%s<<<", cast(char[]) str[0..pos]); throw new Exception("invalid char after slash(/): "); case '[': return new JsonParserToken(jToken.ASTART); case ']': return new JsonParserToken(jToken.AEND); case '{': return new JsonParserToken(jToken.OSTART); case '}': return new JsonParserToken(jToken.OEND); case ',': return new JsonParserToken(jToken.COMMA); case ':': return new JsonParserToken(jToken.COLON); case '"': return getStringToken('"'); case '\'': return getStringToken('\''); case 't': assert(pos+3 < str.length); assert(str[pos-1..pos+3] == cast(ubyte[])"true"); pos+=3; return new JsonParserToken(jToken.TRUE,str[pos-1..pos+3]); case 'f': assert(pos+4 < str.length); assert(str[pos-1..pos+4] == cast(ubyte[])"false"); pos+=4; return new JsonParserToken(jToken.FALSE,str[pos-1..pos+4]); case 'n': assert(pos+4 < str.length); assert(str[pos-1..pos+3] == cast(ubyte[])"null"); pos+=3; return new JsonParserToken(jToken.NULL,str[pos-1..pos+3]); // what about raw strings for objects...!?!??! case '-': case '0': case '1':case '2':case '3':case '4': case '5': case '6':case '7':case '8':case '9': pos--; return getNumberToken(); default: std.stdio.writefln("%s<<<", cast(char[]) str[0..pos]); throw new Exception("invalid char : " ~ cast(char[]) str[0..pos]); } } return null; } JsonParserToken getStringToken(char endChar) { // already in '"' int spos = pos; char[] r = ""; ubyte c; bool ishexdigit(char c) { return isdigit(c) || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f'); } while (pos < str.length) { c = str[pos]; pos++; if (c == endChar) { return new JsonParserToken(jToken.STRING,cast(ubyte[])r); } if (c != '\\') { r~=c; continue; } c = str[pos]; pos++; ubyte n; switch(c) { case '\\': case '"': case '/': r ~= c; continue; case 'b': r ~= '\b'; continue; case 'f': r ~= '\f'; continue; case 'n': r ~= '\n'; continue; case 'r': r ~= '\r'; continue; case 't': r ~= '\t'; continue; case 'u': { wchar val = 0; for (uint i=0;i < 4; i++) { c = str[pos]; pos++; if (c >= '0' && c <= '9') { c -= '0'; } else if (c >= 'A' && c <= 'F') { c -= ('A' - 10) ; } else if (c >= 'a' && c <= 'f') { c -= ('a' - 10); } else { throw new Exception("unicode parse- invalid character"); } val <<= 4; val |= c; } std.utf.encode(r, val); continue; } case 'x': // non standard \x n = 0; c = str[pos]; assert(ishexdigit(c)); n = ((isdigit(c)) ? (c - '0') : (0xA + (c | 0x20) - 'a')) *0x10; pos++; c = str[pos]; assert(ishexdigit(c)); n += ((isdigit(c)) ? (c - '0') : (0xA + (c | 0x20) - 'a')); pos++; r ~= n; continue; /* case 'u': assert(pos+4 < str.length); ulong n; for (i =0;i<4;i++) { c = str[pos]; pos++; assert(ishexdigit(c)); n *= 0x10; if (isdigit(c)) n += c - '0'; else n += 0xA + (c | 0x20) - 'a'; } // 4 cars.. */ default: // just ignore \( etc. escaped??? r ~= c; continue; std.stdio.writefln("%s<<<", str[0..pos]); throw new Exception("invalid char :" ~ cast(char[]) str[0..pos]); } } // end while throw new Exception("hit end of file in string"); } JsonParserToken getNumberToken() { // very simple version!!!! long v = 0; ubyte nn = 0; ubyte c = 0; real nve = 1; if ('-' == str[pos]) { nve = -1; pos++; }/* while (pos < str.length) { c = str[pos]; if (!isdigit(c)) { return new JsonParserToken(jToken.NUMBER,v); } nn = (c - '0') ; v = v * 10; v+=nn;; pos++; } // should be error? return new JsonParserToken(jToken.NUMBER,v); */ bool ishex(ubyte c) { return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f'); } long ivalue; real rvalue; int base = 10; int pstart = pos; //ubyte c = 0; while (pos < str.length) { c = str[pos]; pos++; //std.stdio.writefln("NUM: GOT %s", str[pos-1..length]); if (c == '0' && (pstart+1 == pos)) { base = 8; } if ((c == '8' || c == '9') && base == 8) { base = 10; } if (c >= '0' && c <= '9') { // 0..9 continue; } if (c == 'x' || c == 'X') { // 0X 0x if (pstart+2 != pos) { throw new Exception("X only allowed at char2 of number"); } if (str[pstart] != '0') { throw new Exception("0 must precede X in number"); } if (!ishex(str[pos])) { throw new Exception("hex digit must follow 0x in number"); } pstart += 2; while(ishex(str[pos])) { pos++; } base = 16; break; // do calc... } if (c == '.') { while(isdigit(str[pos])) { pos++; } c = str[pos]; if (c == 'e' || c == 'E') { } else { return new JsonParserToken(jToken.NUMBER, nve * std.c.stdlib.strtod(toStringz(cast(char[])str[pstart .. pos]), null) ); continue; } // otherwise keep going.... - it'll be a double... } if (c == 'e' || c == 'E') { // 0X 0x pos++; c = str[pos]; if (c == '+' || c == '-') { pos++; } if (!isdigit(str[pos])) { throw new Exception("digit must follow E in number"); } while (isdigit(str[pos])) { pos++; } return new JsonParserToken(jToken.NUMBER, std.c.stdlib.strtod(toStringz(cast(char[])str[pstart .. pos]), null) ); } pos--; break; } /// LNUM? base = (base == 0) ? 10 : base; rvalue = 0; ivalue = 0; bool toobig = false; for (int i = pstart; i < pos; i++) { c= str[i]; // std.stdio.writefln("GOT C %s", ""~(cast(char)c)); if (c >= '0' && c <= '9') { c -= '0'; } else if (c >= 'A' && c <= 'F') { c -= ('A' - 10); } else if (c >= 'a' && c <= 'f') { c -= ('a' - 10); } else { throw new Exception("this should not happen"); } if (c > base) { throw new Exception("Number is to big given expected base"); } if (!toobig && (ivalue < (long.max - c) / base)) { ivalue *= base; ivalue += c; } else { toobig = true; } // too big... rvalue *= base; rvalue += c; } return toobig ? new JsonParserToken(jToken.NUMBER, rvalue * nve) :new JsonParserToken(jToken.NUMBER, ivalue * cast(int)nve) ; } } version(jsontest) { import std.stdio; void main() { auto o = jsonO(); auto a = jsonA(); a.add(123); a.add("123"); a.add(-123); a.add(123.3333); a.add(cast(ulong)999999999999999); auto ev = o.encode(); std.stdio.writefln("%s", o.getS("key")); std.stdio.writefln("%s", cast(char[])ev); o = jsonDecode(ev); ev = o.encode(); std.stdio.writefln("%s", o.getS("key")); std.stdio.writefln("%s", cast(char[])ev); } }