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: 8.92 KB  |  views: 216  |  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.json;
  14.  
  15. import std.conv;
  16. import std.range;
  17. import std.ctype;
  18. import std.uni;
  19. import std.utf;
  20.  
  21. // Prevent conflicts from these generic names
  22. alias std.utf.stride UTFStride;
  23. alias std.utf.decode toUnicode;
  24.  
  25. /**
  26.  JSON value types
  27. */
  28. enum JSON_TYPE : byte {
  29.         STRING,
  30.         INTEGER,
  31.         FLOAT,
  32.         OBJECT,
  33.         ARRAY,
  34.         TRUE,
  35.         FALSE,
  36.         NULL
  37. }
  38.  
  39. /**
  40.  JSON value node
  41. */
  42. struct JSONValue {
  43.         union {
  44.                 string                          str;
  45.                 long                            integer;
  46.                 real                            floating;
  47.                 JSONValue[string]       object;
  48.                 JSONValue[]                     array;
  49.         }
  50.         JSON_TYPE                               type;
  51. }
  52.  
  53. /**
  54.  Parses a serialized string and returns a tree of JSON values.
  55. */
  56. JSONValue parseJSON(T)(in T json, int maxDepth = -1) if(isInputRange!T) {
  57.         JSONValue root = void;
  58.         root.type = JSON_TYPE.NULL;
  59.  
  60.         if(json.empty()) return root;
  61.  
  62.         int depth = -1;
  63.         char next = 0;
  64.         int line = 1, pos = 1;
  65.  
  66.         void error(string msg) {
  67.                 throw new JSONException(msg, line, pos);
  68.         }
  69.  
  70.         char peekChar() {
  71.                 if(!next) {
  72.                         if(json.empty()) return '\0';
  73.                         next = json.front();
  74.                         json.popFront();
  75.                 }
  76.                 return next;
  77.         }
  78.  
  79.         void skipWhitespace() {
  80.                 while(isspace(peekChar())) next = 0;
  81.         }
  82.  
  83.         char getChar(bool SkipWhitespace = false)() {
  84.                 static if(SkipWhitespace) skipWhitespace();
  85.  
  86.                 char c = void;
  87.                 if(next) {
  88.                         c = next;
  89.                         next = 0;
  90.                 }
  91.                 else {
  92.                         if(json.empty()) error("Unexpected end of data.");
  93.                         c = json.front();
  94.                         json.popFront();
  95.                 }
  96.  
  97.                 if(c == '\n' || (c == '\r' && peekChar() != '\n')) {
  98.                         line++;
  99.                         pos = 1;
  100.                 }
  101.                 else {
  102.                         pos++;
  103.                 }
  104.  
  105.                 return c;
  106.         }
  107.  
  108.         void checkChar(bool SkipWhitespace = true, bool CaseSensitive = true)(char c) {
  109.                 static if(SkipWhitespace) skipWhitespace();
  110.                 char c2 = getChar();
  111.                 static if(!CaseSensitive) c2 = cast(char)tolower(c2);
  112.  
  113.                 if(c2 != c) error(text("Found '", c2, "' when expecting '", c, "'."));
  114.         }
  115.  
  116.         bool testChar(bool SkipWhitespace = true, bool CaseSensitive = true)(char c) {
  117.                 static if(SkipWhitespace) skipWhitespace();
  118.                 char c2 = peekChar();
  119.                 static if(!CaseSensitive) c2 = cast(char)tolower(c2);
  120.  
  121.                 if(c2 != c) return false;
  122.  
  123.                 getChar();
  124.                 return true;
  125.         }
  126.  
  127.         string parseString() {
  128.                 Appender!string str;
  129.  
  130.         Next:
  131.                 switch(peekChar()) {
  132.                 case '"':
  133.                         getChar();
  134.                         break;
  135.  
  136.                 case '\\':
  137.                         getChar();
  138.                         char c = getChar();
  139.                         switch(c) {
  140.                         case '"':       str.put('"');   break;
  141.                         case '\\':      str.put('\\');  break;
  142.                         case '/':       str.put('/');   break;
  143.                         case 'b':       str.put('\b');  break;
  144.                         case 'f':       str.put('\f');  break;
  145.                         case 'n':       str.put('\n');  break;
  146.                         case 'r':       str.put('\r');  break;
  147.                         case 't':       str.put('\t');  break;
  148.                         case 'u':
  149.                                 dchar val = 0;
  150.                                 foreach_reverse(i; 0 .. 4) {
  151.                                         char hex = cast(char)toupper(getChar());
  152.                                         if(!isxdigit(hex)) error("Expecting hex character");
  153.                                         val += (isdigit(hex) ? hex - '0' : hex - 'A') << (4 * i);
  154.                                 }
  155.                                 char[4] buf = void;
  156.                                 str.put(toUTF8(buf, val));
  157.                                 break;
  158.  
  159.                         default:
  160.                                 error(text("Invalid escape sequence '\\", c, "'."));
  161.                         }
  162.                         goto Next;
  163.  
  164.                 default:
  165.                         char c = getChar();
  166.                         appendJSONChar(&str, c, getChar(), &error);
  167.                         goto Next;
  168.                 }
  169.  
  170.                 return str.data;
  171.         }
  172.  
  173.         void parseValue(JSONValue* value) {
  174.                 depth++;
  175.  
  176.                 if(maxDepth != -1 && depth > maxDepth) error("Nesting too deep.");
  177.  
  178.                 char c = getChar!true();
  179.  
  180.                 switch(c) {
  181.                 case '{':
  182.                         value.type = JSON_TYPE.OBJECT;
  183.                         value.object = null;
  184.  
  185.                         if(testChar('}')) break;
  186.  
  187.                         do {
  188.                                 checkChar('"');
  189.                                 string name = parseString();
  190.                                 checkChar(':');
  191.                                 JSONValue member = void;
  192.                                 parseValue(&member);
  193.                                 value.object[name] = member;
  194.                         } while(testChar(','));
  195.  
  196.                         checkChar('}');
  197.                         break;
  198.  
  199.                 case '[':
  200.                         value.type = JSON_TYPE.ARRAY;
  201.                         value.array = null;
  202.  
  203.                         if(testChar(']')) break;
  204.  
  205.                         do {
  206.                                 JSONValue element = void;
  207.                                 parseValue(&element);
  208.                                 value.array ~= element;
  209.                         } while(testChar(','));
  210.  
  211.                         checkChar(']');
  212.                         break;
  213.  
  214.                 case '"':
  215.                         value.type = JSON_TYPE.STRING;
  216.                         value.str = parseString();
  217.                         break;
  218.  
  219.                 case '0': .. case '9':
  220.                 case '-':
  221.                         Appender!string number;
  222.                         bool isFloat;
  223.  
  224.                         void readInteger() {
  225.                                 if(!isdigit(c)) error("Digit expected");
  226.  
  227.                                 Next: number.put(c);
  228.  
  229.                                 if(isdigit(peekChar())) {
  230.                                         c = getChar();
  231.                                         goto Next;
  232.                                 }
  233.                         }
  234.  
  235.                         if(c == '-') {
  236.                                 number.put('-');
  237.                                 c = getChar();
  238.                         }
  239.  
  240.                         readInteger();
  241.  
  242.                         if(testChar('.')) {
  243.                                 isFloat = true;
  244.                                 number.put('.');
  245.                                 c = getChar();
  246.                                 readInteger();
  247.                         }
  248.                         if(testChar!(false, false)('e')) {
  249.                                 isFloat = true;
  250.                                 number.put('e');
  251.                                 if(testChar('+')) number.put('+');
  252.                                 else if(testChar('-')) number.put('-');
  253.                                 c = getChar();
  254.                                 readInteger();
  255.                         }
  256.  
  257.                         string data = number.data;
  258.                         if(isFloat) {
  259.                                 value.type = JSON_TYPE.FLOAT;
  260.                                 value.floating = parse!real(data);
  261.                         }
  262.                         else {
  263.                                 value.type = JSON_TYPE.INTEGER;
  264.                                 value.integer = parse!long(data);
  265.                         }
  266.                         break;
  267.  
  268.                 case 't':
  269.                 case 'T':
  270.                         value.type = JSON_TYPE.TRUE;
  271.                         checkChar!(false, false)('r');
  272.                         checkChar!(false, false)('u');
  273.                         checkChar!(false, false)('e');
  274.                         break;
  275.  
  276.                 case 'f':
  277.                 case 'F':
  278.                         value.type = JSON_TYPE.FALSE;
  279.                         checkChar!(false, false)('a');
  280.                         checkChar!(false, false)('l');
  281.                         checkChar!(false, false)('s');
  282.                         checkChar!(false, false)('e');
  283.                         break;
  284.  
  285.                 case 'n':
  286.                 case 'N':
  287.                         value.type = JSON_TYPE.NULL;
  288.                         checkChar!(false, false)('u');
  289.                         checkChar!(false, false)('l');
  290.                         checkChar!(false, false)('l');
  291.                         break;
  292.  
  293.                 default:
  294.                         error(text("Unexpected character '", c, "'."));
  295.                 }
  296.  
  297.                 depth--;
  298.         }
  299.  
  300.         parseValue(&root);
  301.         return root;
  302. }
  303.  
  304. /**
  305.  Takes a tree of JSON values and returns the serialized string.
  306. */
  307. string toJSON(in JSONValue* root) {
  308.         Appender!string json;
  309.  
  310.         void toString(string str) {
  311.                 json.put('"');
  312.  
  313.                 for(int i; i != str.length; i++) {
  314.                         switch(str[i]) {
  315.                         case '"':       json.put("\\\"");       break;
  316.                         case '\\':      json.put("\\\\");       break;
  317.                         case '/':       json.put("\\/");        break;
  318.                         case '\b':      json.put("\\b");        break;
  319.                         case '\f':      json.put("\\f");        break;
  320.                         case '\n':      json.put("\\n");        break;
  321.                         case '\r':      json.put("\\r");        break;
  322.                         case '\t':      json.put("\\t");        break;
  323.                         default:
  324.                                 appendJSONChar(&json, str[i], str[++i],
  325.                                         (string msg){throw new JSONException(msg);});
  326.                         }
  327.                 }
  328.  
  329.                 json.put('"');
  330.         }
  331.  
  332.         void toValue(in JSONValue* value) {
  333.                 final switch(value.type) {
  334.                 case JSON_TYPE.OBJECT:
  335.                         json.put('{');
  336.                         bool first = true;
  337.                         foreach(name, member; value.object) {
  338.                                 if(first) first = false;
  339.                                 else json.put(',');
  340.                                 toString(name);
  341.                                 json.put(':');
  342.                                 toValue(&member);
  343.                         }
  344.                         json.put('}');
  345.                         break;
  346.  
  347.                 case JSON_TYPE.ARRAY:
  348.                         json.put('[');
  349.                         int length = value.array.length;
  350.                         for(int i; i != length; i++) {
  351.                                 if(i) json.put(',');
  352.                                 toValue(&value.array[i]);
  353.                         }
  354.                         json.put(']');
  355.                         break;
  356.  
  357.                 case JSON_TYPE.STRING:
  358.                         toString(value.str);
  359.                         break;
  360.  
  361.                 case JSON_TYPE.INTEGER:
  362.                         json.put(to!string(value.integer));
  363.                         break;
  364.  
  365.                 case JSON_TYPE.FLOAT:
  366.                         json.put(to!string(value.floating));
  367.                         break;
  368.  
  369.                 case JSON_TYPE.TRUE:
  370.                         json.put("true");
  371.                         break;
  372.  
  373.                 case JSON_TYPE.FALSE:
  374.                         json.put("false");
  375.                         break;
  376.  
  377.                 case JSON_TYPE.NULL:
  378.                         json.put("null");
  379.                         break;
  380.                 }
  381.         }
  382.  
  383.         toValue(root);
  384.         return json.data;
  385. }
  386.  
  387. private void appendJSONChar(Appender!string* dst, char c, lazy char next,
  388.         scope void delegate(string) error)
  389. {
  390.         int stride = UTFStride((&c)[0 .. 1], 0);
  391.         if(stride == 1) {
  392.                 if(iscntrl(c)) error("Illegal control character.");
  393.                 dst.put(c);
  394.         }
  395.         else {
  396.                 char[6] utf = void;
  397.                 utf[0] = c;
  398.                 foreach(i; 1 .. stride) utf[i] = next;
  399.                 size_t index = 0;
  400.                 if(isUniControl(toUnicode(utf[0 .. stride], index)))
  401.                         error("Illegal control character");
  402.                 dst.put(utf[0 .. stride]);
  403.         }
  404. }
  405.  
  406. /**
  407.  Exception thrown on JSON errors
  408. */
  409. class JSONException : Exception {
  410.         this(string msg, int line = 0, int pos = 0) {
  411.                 if(line) super(text(msg, " (Line ", line, ":", pos, ")"));
  412.                 else super(msg);
  413.         }
  414. }
  415.  
  416. version(unittest) import std.stdio;
  417.  
  418. unittest {
  419.         // An overly simple test suite, if it can parse a serializated string and
  420.         // then use the resulting values tree to generate an identical
  421.         // serialization, both the decoder and encoder works.
  422.  
  423.         immutable jsons = [
  424.                 `null`,
  425.                 `true`,
  426.                 `false`,
  427.                 `0`,
  428.                 `123`,
  429.                 `-4321`,
  430.                 `0.23`,
  431.                 `-0.23`,
  432.                 `""`,
  433.                 `1.223e+24`,
  434.                 `"hello\nworld"`,
  435.                 `"\"\\\/\b\f\n\r\t"`,
  436.                 `[]`,
  437.                 `[12,"foo",true,false]`,
  438.                 `{}`,
  439.                 `{"a":1,"b":null}`,
  440.                 `{"hello":{"json":"is great","array":[12,null,{}]},"goodbye":[true,"or",false,["test",42,{"nested":{"a":23.54,"b":0.0012}}]]}`
  441.         ];
  442.  
  443.         JSONValue val;
  444.         string result;
  445.         foreach(json; jsons) {
  446.                 try {
  447.                         val = parseJSON(json);
  448.                         result = toJSON(&val);
  449.                         assert(result == json, text(result, " should be ", json));
  450.                 }
  451.                 catch(JSONException e) {
  452.                         writefln(text(json, "\n", e.toString()));
  453.                 }
  454.         }
  455. }
clone this paste RAW Paste Data