Advertisement
Guest User

Jeremie Pelletier

a guest
Oct 11th, 2009
252
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
D 7.86 KB | None | 0 0
  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. }
  411.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement