This week only. Pastebin PRO Accounts Christmas Special! Don't miss out!Want more features on Pastebin? Sign Up, it's FREE!
Guest

Jeremie Pelletier

By: a guest on Oct 11th, 2009  |  syntax: D  |  size: 7.86 KB  |  views: 145  |  expires: Never
download  |  raw  |  embed  |  report abuse  |  print
Text below is selected. Please press Ctrl+C to copy to your clipboard. (⌘+C on Mac)
  1. /**
  2.  * JavaScript Object Notation
  3.  *
  4.  * Authors:
  5.  *      Jeremie Pelletier
  6.  *
  7.  * References:
  8.  *      $(LINK http://json.org/)
  9.  *
  10.  * License:
  11.  *      Public Domain
  12.  */
  13. module std.text.JSON;
  14.  
  15. import std.generic.Conv;
  16. import std.generic.Range;
  17. import std.text.CType;
  18. import std.text.Unicode;
  19. import std.text.UTF;
  20.  
  21. /**
  22.  JSON value types
  23. */
  24. enum JSON_TYPE : byte {
  25.         STRING,
  26.         INTEGER,
  27.         FLOAT,
  28.         OBJECT,
  29.         ARRAY,
  30.         TRUE,
  31.         FALSE,
  32.         NULL
  33. }
  34.  
  35. /**
  36.  JSON value node
  37. */
  38. struct JSONValue {
  39.         union {
  40.                 string                          str;
  41.                 long                            integer;
  42.                 real                            floating;
  43.                 JSONValue[string]       object;
  44.                 JSONValue[]                     array;
  45.         }
  46.         JSON_TYPE                               type;
  47. }
  48.  
  49. /**
  50.  Parses a serialized string and returns a tree of JSON values.
  51. */
  52. JSONValue parseJSON(T)(in T json, int maxDepth = -1) if(isInputRange!T) {
  53.         JSONValue root = void;
  54.         root.type = JSON_TYPE.NULL;
  55.  
  56.         if(json.empty()) return root;
  57.  
  58.         int depth = -1;
  59.         char next = 0;
  60.         int line = 1, pos = 1;
  61.  
  62.         void error(string msg) {
  63.                 throw new JSONException(msg, line, pos);
  64.         }
  65.  
  66.         char peekChar() {
  67.                 if(!next) {
  68.                         if(json.empty()) error("Unexpected end of data.");
  69.                         next = json.front();
  70.                         json.popFront();
  71.                 }
  72.                 return next;
  73.         }
  74.  
  75.         void skipWhitespace() {
  76.                 while(isSpace(peekChar())) next = 0;
  77.         }
  78.  
  79.         char getChar(bool SkipWhitespace = false)() {
  80.                 static if(SkipWhitespace) skipWhitespace();
  81.  
  82.                 char c = void;
  83.                 if(next) {
  84.                         c = next;
  85.                         next = 0;
  86.                 }
  87.                 else {
  88.                         if(json.empty()) error("Unexpected end of data.");
  89.                         c = json.front();
  90.                         json.popFront();
  91.                 }
  92.  
  93.                 if(c == '\n' || (c == '\r' && peekChar() != '\n')) {
  94.                         line++;
  95.                         pos = 1;
  96.                 }
  97.                 else {
  98.                         pos++;
  99.                 }
  100.  
  101.                 return c;
  102.         }
  103.  
  104.         void checkChar(bool SkipWhitespace = true, bool CaseSensitive = true)(char c) {
  105.                 static if(SkipWhitespace) skipWhitespace();
  106.                 char c2 = getChar();
  107.                 static if(!CaseSensitive) c2 = cast(char)toLower(c2);
  108.  
  109.                 if(c2 != c) error(text("Found '", c2, "' when expecting '", c, "'."));
  110.         }
  111.  
  112.         bool testChar(bool SkipWhitespace = true, bool CaseSensitive = true)(char c) {
  113.                 static if(SkipWhitespace) skipWhitespace();
  114.                 char c2 = peekChar();
  115.                 static if(!CaseSensitive) c2 = cast(char)toLower(c2);
  116.  
  117.                 if(c2 != c) return false;
  118.  
  119.                 getChar();
  120.                 return true;
  121.         }
  122.  
  123.         string parseString() {
  124.                 Appender!string str;
  125.  
  126.         Next:
  127.                 switch(peekChar()) {
  128.                 case '"':
  129.                         getChar();
  130.                         break;
  131.  
  132.                 case '\\':
  133.                         getChar();
  134.                         char c = getChar();
  135.                         switch(c) {
  136.                         case '"':       str ~= '"';             break;
  137.                         case '\\':      str ~= '\\';    break;
  138.                         case '/':       str ~= '/';             break;
  139.                         case 'b':       str ~= '\b';    break;
  140.                         case 'f':       str ~= '\f';    break;
  141.                         case 'n':       str ~= '\n';    break;
  142.                         case 'r':       str ~= '\r';    break;
  143.                         case 't':       str ~= '\t';    break;
  144.                         case 'u':
  145.                                 dchar val = 0;
  146.                                 foreach_reverse(i; 0 .. 4) {
  147.                                         char hex = cast(char)toUpper(getChar());
  148.                                         if(!isXDigit(hex)) error("Expecting hex character");
  149.                                         val += (isDigit(hex) ? hex - '0' : hex - 'A') << (4 * i);
  150.                                 }
  151.                                 toUTF8(val, str);
  152.                                 break;
  153.  
  154.                         default:
  155.                                 error(text("Invalid escape sequence '\\", c, "'."));
  156.                         }
  157.                         goto Next;
  158.  
  159.                 default:
  160.                         char c = getChar();
  161.                         appendJSONChar(&str, c, getChar(), &error);
  162.                         goto Next;
  163.                 }
  164.  
  165.                 return str.data;
  166.         }
  167.  
  168.         void parseValue(JSONValue* value) {
  169.                 depth++;
  170.  
  171.                 if(maxDepth != -1 && depth > maxDepth) error("Nesting too deep.");
  172.  
  173.                 char c = getChar!true();
  174.  
  175.                 switch(c) {
  176.                 case '{':
  177.                         value.type = JSON_TYPE.OBJECT;
  178.                         value.object = null;
  179.  
  180.                         if(testChar('}')) break;
  181.  
  182.                         do {
  183.                                 checkChar('"');
  184.                                 string name = parseString();
  185.                                 checkChar(':');
  186.                                 JSONValue member = void;
  187.                                 parseValue(&member);
  188.                                 value.object[name] = member;
  189.                         } while(testChar(','));
  190.  
  191.                         checkChar('}');
  192.                         break;
  193.  
  194.                 case '[':
  195.                         value.type = JSON_TYPE.ARRAY;
  196.                         value.array = null;
  197.  
  198.                         if(testChar(']')) break;
  199.  
  200.                         do {
  201.                                 JSONValue element = void;
  202.                                 parseValue(&element);
  203.                                 value.array ~= element;
  204.                         } while(testChar(','));
  205.  
  206.                         checkChar(']');
  207.                         break;
  208.  
  209.                 case '"':
  210.                         value.type = JSON_TYPE.STRING;
  211.                         value.str = parseString();
  212.                         break;
  213.  
  214.                 case '0': .. case '9':
  215.                 case '-':
  216.                         Appender!string number;
  217.                         bool isFloat;
  218.  
  219.                         void readInteger() {
  220.                                 if(!isDigit(c)) error("Digit expected");
  221.  
  222.                                 Next: number ~= c;
  223.  
  224.                                 if(isDigit(peekChar())) {
  225.                                         c = getChar();
  226.                                         goto Next;
  227.                                 }
  228.                         }
  229.  
  230.                         if(c == '-') {
  231.                                 number ~= '-';
  232.                                 c = getChar();
  233.                         }
  234.  
  235.                         readInteger();
  236.  
  237.                         if(testChar('.')) {
  238.                                 isFloat = true;
  239.                                 number ~= '.';
  240.                                 c = getChar();
  241.                                 readInteger();
  242.                         }
  243.                         if(testChar!(false, false)('e')) {
  244.                                 isFloat = true;
  245.                                 number ~= 'e';
  246.                                 if(testChar('+')) number ~= '+';
  247.                                 else if(testChar('-')) number ~= '-';
  248.                                 c = getChar();
  249.                                 readInteger();
  250.                         }
  251.  
  252.                         string data = number.data;
  253.                         size_t offset;
  254.  
  255.                         if(isFloat) {
  256.                                 value.type = JSON_TYPE.FLOAT;
  257.                                 value.floating = parse!real(data, offset);
  258.                         }
  259.                         else {
  260.                                 value.type = JSON_TYPE.INTEGER;
  261.                                 value.integer = parse!long(data, offset);
  262.                         }
  263.                         break;
  264.  
  265.                 case 't':
  266.                 case 'T':
  267.                         value.type = JSON_TYPE.TRUE;
  268.                         checkChar!(false, false)('r');
  269.                         checkChar!(false, false)('u');
  270.                         checkChar!(false, false)('e');
  271.                         break;
  272.  
  273.                 case 'f':
  274.                 case 'F':
  275.                         value.type = JSON_TYPE.FALSE;
  276.                         checkChar!(false, false)('a');
  277.                         checkChar!(false, false)('l');
  278.                         checkChar!(false, false)('s');
  279.                         checkChar!(false, false)('e');
  280.                         break;
  281.  
  282.                 case 'n':
  283.                 case 'N':
  284.                         value.type = JSON_TYPE.NULL;
  285.                         checkChar!(false, false)('u');
  286.                         checkChar!(false, false)('l');
  287.                         checkChar!(false, false)('l');
  288.                         break;
  289.  
  290.                 default:
  291.                         error(text("Unexpected character '", c, "'."));
  292.                 }
  293.  
  294.                 depth--;
  295.         }
  296.  
  297.         parseValue(&root);
  298.         return root;
  299. }
  300.  
  301. /**
  302.  Takes a tree of JSON values and returns the serialized string.
  303. */
  304. string toJSON(in JSONValue* root) {
  305.         Appender!string json;
  306.  
  307.         void toString(string str) {
  308.                 json ~= '"';
  309.  
  310.                 for(int i; i != str.length; i++) {
  311.                         switch(str[i]) {
  312.                         case '"':       json ~= "\\\""; break;
  313.                         case '\\':      json ~= "\\\\"; break;
  314.                         case '\b':      json ~= "\\b";  break;
  315.                         case '\f':      json ~= "\\f";  break;
  316.                         case '\n':      json ~= "\\n";  break;
  317.                         case '\r':      json ~= "\\r";  break;
  318.                         case '\t':      json ~= "\\t";  break;
  319.                         default:
  320.                                 appendJSONChar(&json, str[i], str[++i],
  321.                                         (string msg){throw new JSONException(msg);});
  322.                         }
  323.                 }
  324.  
  325.                 json ~= '"';
  326.         }
  327.  
  328.         void toValue(in JSONValue* value) {
  329.                 final switch(value.type) {
  330.                 case JSON_TYPE.OBJECT:
  331.                         json ~= '{';
  332.                         bool first = true;
  333.                         foreach(name, member; value.object) {
  334.                                 if(first) first = false;
  335.                                 else json ~= ',';
  336.                                 toString(name);
  337.                                 json ~= ':';
  338.                                 toValue(&member);
  339.                         }
  340.                         json ~= '}';
  341.                         break;
  342.  
  343.                 case JSON_TYPE.ARRAY:
  344.                         json ~= '[';
  345.                         int length = value.array.length;
  346.                         for(int i; i != length; i++) {
  347.                                 if(i) json ~= ',';
  348.                                 toValue(&value.array[i]);
  349.                         }
  350.                         json ~= ']';
  351.                         break;
  352.  
  353.                 case JSON_TYPE.STRING:
  354.                         toString(value.str);
  355.                         break;
  356.  
  357.                 case JSON_TYPE.INTEGER:
  358.                         json ~= to!string(value.integer);
  359.                         break;
  360.  
  361.                 case JSON_TYPE.FLOAT:
  362.                         json ~= to!string(value.floating);
  363.                         break;
  364.  
  365.                 case JSON_TYPE.TRUE:
  366.                         json ~= "true";
  367.                         break;
  368.  
  369.                 case JSON_TYPE.FALSE:
  370.                         json ~= "false";
  371.                         break;
  372.  
  373.                 case JSON_TYPE.NULL:
  374.                         json ~= "null";
  375.                         break;
  376.                 }
  377.         }
  378.  
  379.         toValue(root);
  380.         return json.data;
  381. }
  382.  
  383. private void appendJSONChar(Appender!string* dst, char c, lazy char next,
  384.         scope void delegate(string) error)
  385. {
  386.         int stride = UTFStride(c);
  387.         if(stride == 1) {
  388.                 if(isCntrl(c)) error("Illegal control character.");
  389.                 *dst ~= c;
  390.         }
  391.         else {
  392.                 char[6] utf = void;
  393.                 utf[0] = c;
  394.                 foreach(i; 1 .. stride) utf[i] = next;
  395.                 size_t index = 0;
  396.                 if(isUniControl(toUnicode(utf[0 .. stride], index)))
  397.                         error("Illegal control character");
  398.                 *dst ~= utf[0 .. stride];
  399.         }
  400. }
  401.  
  402. /**
  403.  Exception thrown on JSON errors
  404. */
  405. class JSONException : Exception {
  406.         this(string msg, int line = 0, int pos = 0) {
  407.                 if(line) super(text(msg, " (Line ", line, " @ ", pos, ")"));
  408.                 else super(msg);
  409.         }
  410. }
clone this paste RAW Paste Data