Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- /**
- * JavaScript Object Notation
- *
- * Authors:
- * Jeremie Pelletier
- *
- * References:
- * $(LINK http://json.org/)
- *
- * License:
- * Public Domain
- */
- module std.text.JSON;
- import std.generic.Conv;
- import std.generic.Range;
- import std.text.CType;
- import std.text.Unicode;
- import std.text.UTF;
- /**
- JSON value types
- */
- enum JSON_TYPE : byte {
- STRING,
- INTEGER,
- FLOAT,
- OBJECT,
- ARRAY,
- TRUE,
- FALSE,
- NULL
- }
- /**
- JSON value node
- */
- struct JSONValue {
- union {
- string str;
- long integer;
- real floating;
- JSONValue[string] object;
- JSONValue[] array;
- }
- JSON_TYPE type;
- }
- /**
- Parses a serialized string and returns a tree of JSON values.
- */
- JSONValue parseJSON(T)(in T json, int maxDepth = -1) if(isInputRange!T) {
- JSONValue root = void;
- root.type = JSON_TYPE.NULL;
- if(json.empty()) return root;
- int depth = -1;
- char next = 0;
- int line = 1, pos = 1;
- void error(string msg) {
- throw new JSONException(msg, line, pos);
- }
- char peekChar() {
- if(!next) {
- if(json.empty()) error("Unexpected end of data.");
- next = json.front();
- json.popFront();
- }
- return next;
- }
- void skipWhitespace() {
- while(isSpace(peekChar())) next = 0;
- }
- char getChar(bool SkipWhitespace = false)() {
- static if(SkipWhitespace) skipWhitespace();
- char c = void;
- if(next) {
- c = next;
- next = 0;
- }
- else {
- if(json.empty()) error("Unexpected end of data.");
- c = json.front();
- json.popFront();
- }
- if(c == '\n' || (c == '\r' && peekChar() != '\n')) {
- line++;
- pos = 1;
- }
- else {
- pos++;
- }
- return c;
- }
- void checkChar(bool SkipWhitespace = true, bool CaseSensitive = true)(char c) {
- static if(SkipWhitespace) skipWhitespace();
- char c2 = getChar();
- static if(!CaseSensitive) c2 = cast(char)toLower(c2);
- if(c2 != c) error(text("Found '", c2, "' when expecting '", c, "'."));
- }
- bool testChar(bool SkipWhitespace = true, bool CaseSensitive = true)(char c) {
- static if(SkipWhitespace) skipWhitespace();
- char c2 = peekChar();
- static if(!CaseSensitive) c2 = cast(char)toLower(c2);
- if(c2 != c) return false;
- getChar();
- return true;
- }
- string parseString() {
- Appender!string str;
- Next:
- switch(peekChar()) {
- case '"':
- getChar();
- break;
- case '\\':
- getChar();
- char c = getChar();
- switch(c) {
- case '"': str ~= '"'; break;
- case '\\': str ~= '\\'; break;
- case '/': str ~= '/'; break;
- case 'b': str ~= '\b'; break;
- case 'f': str ~= '\f'; break;
- case 'n': str ~= '\n'; break;
- case 'r': str ~= '\r'; break;
- case 't': str ~= '\t'; break;
- case 'u':
- dchar val = 0;
- foreach_reverse(i; 0 .. 4) {
- char hex = cast(char)toUpper(getChar());
- if(!isXDigit(hex)) error("Expecting hex character");
- val += (isDigit(hex) ? hex - '0' : hex - 'A') << (4 * i);
- }
- toUTF8(val, str);
- break;
- default:
- error(text("Invalid escape sequence '\\", c, "'."));
- }
- goto Next;
- default:
- char c = getChar();
- appendJSONChar(&str, c, getChar(), &error);
- goto Next;
- }
- return str.data;
- }
- void parseValue(JSONValue* value) {
- depth++;
- if(maxDepth != -1 && depth > maxDepth) error("Nesting too deep.");
- char c = getChar!true();
- switch(c) {
- case '{':
- value.type = JSON_TYPE.OBJECT;
- value.object = null;
- if(testChar('}')) break;
- do {
- checkChar('"');
- string name = parseString();
- checkChar(':');
- JSONValue member = void;
- parseValue(&member);
- value.object[name] = member;
- } while(testChar(','));
- checkChar('}');
- break;
- case '[':
- value.type = JSON_TYPE.ARRAY;
- value.array = null;
- if(testChar(']')) break;
- do {
- JSONValue element = void;
- parseValue(&element);
- value.array ~= element;
- } while(testChar(','));
- checkChar(']');
- break;
- case '"':
- value.type = JSON_TYPE.STRING;
- value.str = parseString();
- break;
- case '0': .. case '9':
- case '-':
- Appender!string number;
- bool isFloat;
- void readInteger() {
- if(!isDigit(c)) error("Digit expected");
- Next: number ~= c;
- if(isDigit(peekChar())) {
- c = getChar();
- goto Next;
- }
- }
- if(c == '-') {
- number ~= '-';
- c = getChar();
- }
- readInteger();
- if(testChar('.')) {
- isFloat = true;
- number ~= '.';
- c = getChar();
- readInteger();
- }
- if(testChar!(false, false)('e')) {
- isFloat = true;
- number ~= 'e';
- if(testChar('+')) number ~= '+';
- else if(testChar('-')) number ~= '-';
- c = getChar();
- readInteger();
- }
- string data = number.data;
- size_t offset;
- if(isFloat) {
- value.type = JSON_TYPE.FLOAT;
- value.floating = parse!real(data, offset);
- }
- else {
- value.type = JSON_TYPE.INTEGER;
- value.integer = parse!long(data, offset);
- }
- break;
- case 't':
- case 'T':
- value.type = JSON_TYPE.TRUE;
- checkChar!(false, false)('r');
- checkChar!(false, false)('u');
- checkChar!(false, false)('e');
- break;
- case 'f':
- case 'F':
- value.type = JSON_TYPE.FALSE;
- checkChar!(false, false)('a');
- checkChar!(false, false)('l');
- checkChar!(false, false)('s');
- checkChar!(false, false)('e');
- break;
- case 'n':
- case 'N':
- value.type = JSON_TYPE.NULL;
- checkChar!(false, false)('u');
- checkChar!(false, false)('l');
- checkChar!(false, false)('l');
- break;
- default:
- error(text("Unexpected character '", c, "'."));
- }
- depth--;
- }
- parseValue(&root);
- return root;
- }
- /**
- Takes a tree of JSON values and returns the serialized string.
- */
- string toJSON(in JSONValue* root) {
- Appender!string json;
- void toString(string str) {
- json ~= '"';
- for(int i; i != str.length; i++) {
- switch(str[i]) {
- case '"': json ~= "\\\""; break;
- case '\\': json ~= "\\\\"; break;
- case '\b': json ~= "\\b"; break;
- case '\f': json ~= "\\f"; break;
- case '\n': json ~= "\\n"; break;
- case '\r': json ~= "\\r"; break;
- case '\t': json ~= "\\t"; break;
- default:
- appendJSONChar(&json, str[i], str[++i],
- (string msg){throw new JSONException(msg);});
- }
- }
- json ~= '"';
- }
- void toValue(in JSONValue* value) {
- final switch(value.type) {
- case JSON_TYPE.OBJECT:
- json ~= '{';
- bool first = true;
- foreach(name, member; value.object) {
- if(first) first = false;
- else json ~= ',';
- toString(name);
- json ~= ':';
- toValue(&member);
- }
- json ~= '}';
- break;
- case JSON_TYPE.ARRAY:
- json ~= '[';
- int length = value.array.length;
- for(int i; i != length; i++) {
- if(i) json ~= ',';
- toValue(&value.array[i]);
- }
- json ~= ']';
- break;
- case JSON_TYPE.STRING:
- toString(value.str);
- break;
- case JSON_TYPE.INTEGER:
- json ~= to!string(value.integer);
- break;
- case JSON_TYPE.FLOAT:
- json ~= to!string(value.floating);
- break;
- case JSON_TYPE.TRUE:
- json ~= "true";
- break;
- case JSON_TYPE.FALSE:
- json ~= "false";
- break;
- case JSON_TYPE.NULL:
- json ~= "null";
- break;
- }
- }
- toValue(root);
- return json.data;
- }
- private void appendJSONChar(Appender!string* dst, char c, lazy char next,
- scope void delegate(string) error)
- {
- int stride = UTFStride(c);
- if(stride == 1) {
- if(isCntrl(c)) error("Illegal control character.");
- *dst ~= c;
- }
- else {
- char[6] utf = void;
- utf[0] = c;
- foreach(i; 1 .. stride) utf[i] = next;
- size_t index = 0;
- if(isUniControl(toUnicode(utf[0 .. stride], index)))
- error("Illegal control character");
- *dst ~= utf[0 .. stride];
- }
- }
- /**
- Exception thrown on JSON errors
- */
- class JSONException : Exception {
- this(string msg, int line = 0, int pos = 0) {
- if(line) super(text(msg, " (Line ", line, " @ ", pos, ")"));
- else super(msg);
- }
- }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement