// JSON.java package org.bson; import org.bson.types.*; import org.bson.util.Bytes; import java.text.*; import java.util.*; import java.util.regex.*; /** * Helper methods for JSON serialization and de-serialization */ public class JSON { public static void main(String[] args){ Object object = parse("{'j':{'$in': [2,4,6]},'s':\"asdf-1werqwer\"}"); System.out.println(object); } /** * Serializes an object into it's JSON form * * @param o object to serialize * @return String containing JSON form of the object */ public static String serialize( Object o ){ StringBuilder buf = new StringBuilder(); serialize( o , buf ); return buf.toString(); } static void string( StringBuilder a , String s ){ a.append("\""); for(int i = 0; i < s.length(); ++i){ char c = s.charAt(i); if (c == '\\') a.append("\\\\"); else if(c == '"') a.append("\\\""); else if(c == '\n') a.append("\\n"); else if(c == '\r') a.append("\\r"); else if(c == '\t') a.append("\\t"); else if(c == '\b') a.append("\\b"); else if ( c < 32 ) continue; else a.append(c); } a.append("\""); } public static void serialize( Object o , StringBuilder buf ){ o = Bytes.applyEncodingHooks( o ); if ( o == null ){ buf.append( " null " ); return; } if ( o instanceof Number ){ buf.append( o ); return; } if ( o instanceof String ){ string( buf , o.toString() ); return; } if ( o instanceof Collection){ boolean first = true; buf.append( "[ " ); for ( Object n : (Collection)o ){ if ( first ) first = false; else buf.append( " , " ); serialize( n , buf ); } buf.append( "]" ); return; } if ( o instanceof ObjectId) { serialize(new BasicDBObject("$oid", o.toString()), buf); return; } if ( o instanceof DBObject){ boolean first = true; buf.append( "{ " ); DBObject dbo = (DBObject)o; for ( String name : dbo.keySet() ){ if ( first ) first = false; else buf.append( " , " ); string( buf , name ); buf.append( " : " ); serialize( dbo.get( name ) , buf ); } buf.append( "}" ); return; } if ( o instanceof Map ){ boolean first = true; buf.append( "{ " ); Map m = (Map)o; for ( Map.Entry entry : (Set<Map.Entry>)m.entrySet() ){ if ( first ) first = false; else buf.append( " , " ); string( buf , entry.getKey().toString() ); buf.append( " : " ); serialize( entry.getValue() , buf ); } buf.append( "}" ); return; } if (o instanceof Date) { Date d = (Date) o; SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"); format.setCalendar(new GregorianCalendar(new SimpleTimeZone(0, "GMT"))); serialize(new BasicDBObject("$date", format.format(d)), buf); return; } /* if (o instanceof DBRefBase) { buf.append(o); return; }*/ if (o instanceof Boolean) { buf.append(o); return; } if (o instanceof byte[]) { buf.append("<Binary Data>"); return; } if (o instanceof Pattern) { DBObject externalForm = new BasicDBObject(); externalForm.put("$regex", o.toString()); externalForm.put("$options", Bytes.regexFlags( ((Pattern)o).flags() ) ); serialize(externalForm, buf); return; } if ( o.getClass().isArray() ){ Object[] arr = (Object[])o; buf.append( "[ " ); for ( int i=0; i<arr.length; i++) { if ( i > 0 ) buf.append( " , " ); serialize( arr[i] , buf ); } buf.append( "]" ); return; } if ( o instanceof BSONTimestamp ){ BSONTimestamp t = (BSONTimestamp)o; buf.append( t.getTime() + "|" + t.getInc() ); return; } if( o instanceof CodeWScope){ CodeWScope scope = (CodeWScope)o; buf.append( "{ " ); buf.append("\"").append(scope.getCode()).append("\" : "); BSONObject dbo = (BSONObject)scope.getScope(); serialize(dbo,buf); buf.append( "}" ); return; } if(o instanceof org.bson.types.Binary){ buf.append("<binary>"); return; } throw new RuntimeException( "json can't serialize type : " + o.getClass() ); } /** * Parses a JSON string into a DBObject. * * @param s the string to serialize * @return DBObject the object */ public static Object parse( String s ){ return parse( s, null ); } /** * Parses a JSON string into a DBObject. * * @param s the string to serialize * @return DBObject the object */ public static Object parse( String s, BSONCallback c ){ if (s == null || (s=s.trim()).equals("")) { return (DBObject)null; } JSONParser p = new JSONParser(s, c); return p.parse(); } } /** * Parser for JSON objects. * * Supports all types described at www.json.org, except for * numbers with "e" or "E" in them. */ class JSONParser { String s; int pos = 0; BSONCallback _callback; /** * Create a new parser. */ public JSONParser(String s) { this(s, null); } /** * Create a new parser. */ public JSONParser(String s, BSONCallback callback) { this.s = s; _callback = (callback == null) ? new JSONCallback() : callback; } /** * Parse an unknown type. * * @return Object the next item * @throws JSONParseException if invalid JSON is found */ public Object parse() { return parse(null); } /** * Parse an unknown type. * * @return Object the next item * @throws JSONParseException if invalid JSON is found */ protected Object parse(String name) { Object value = null; char current = get(); switch(current) { // null case 'n': read('n'); read('u'); read('l'); read('l'); value = null; break; // true case 't': read('t'); read('r'); read('u'); read('e'); value = true; break; // false case 'f': read('f'); read('a'); read('l'); read('s'); read('e'); value = false; break; // string case '\'': case '\"': value = parseString(); break; // number case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': case '+': case '-': value = parseNumber(); break; // array case '[': value = parseArray(name); break; // object case '{': value = parseObject(name); break; default: throw new JSONParseException(s, pos); } return value; } /** * Parses an object for the form <i>{}</i> and <i>{ members }</i>. * * @return DBObject the next object * @throws JSONParseException if invalid JSON is found */ public Object parseObject() { return parseObject(null); } /** * Parses an object for the form <i>{}</i> and <i>{ members }</i>. * * @return DBObject the next object * @throws JSONParseException if invalid JSON is found */ protected Object parseObject(String name){ if (name != null) { _callback.objectStart(name); } else { _callback.objectStart(); } read('{'); char current = get(); while(get() != '}') { String key = parseString(); read(':'); Object value = parse(key); doCallback(key, value); if((current = get()) == ',') { read(','); } else { break; } } read('}'); return _callback.objectDone(); } protected void doCallback(String name, Object value) { if (value == null) { _callback.gotNull(name); } else if (value instanceof String) { _callback.gotString(name, (String)value); } else if (value instanceof Boolean) { _callback.gotBoolean(name, (Boolean)value); } else if (value instanceof Integer) { _callback.gotInt(name, (Integer)value); } else if (value instanceof Long) { _callback.gotLong(name, (Long)value); } else if (value instanceof Double) { _callback.gotDouble(name, (Double)value); } } /** * Read the current character, making sure that it is the expected character. * Advances the pointer to the next character. * * @param ch the character expected * * @throws JSONParseException if the current character does not match the given character */ public void read(char ch) { if(!check(ch)) { throw new JSONParseException(s, pos); } pos++; } public char read(){ if ( pos >= s.length() ) throw new IllegalStateException( "string done" ); return s.charAt( pos++ ); } /** * Read the current character, making sure that it is a hexidecimal character. * * @throws JSONParseException if the current character is not a hexidecimal character */ public void readHex() { if (pos < s.length() && ((s.charAt(pos) >= '0' && s.charAt(pos) <= '9') || (s.charAt(pos) >= 'A' && s.charAt(pos) <= 'F') || (s.charAt(pos) >= 'a' && s.charAt(pos) <= 'f'))) { pos++; } else { throw new JSONParseException(s, pos); } } /** * Checks the current character, making sure that it is the expected character. * * @param ch the character expected * * @throws JSONParseException if the current character does not match the given character */ public boolean check(char ch) { return get() == ch; } /** * Advances the position in the string past any whitespace. */ public void skipWS() { while(pos < s.length() && Character.isWhitespace(s.charAt(pos))) { pos++; } } /** * Returns the current character. * Returns -1 if there are no more characters. * * @return the next character */ public char get() { skipWS(); if(pos < s.length()) return s.charAt(pos); return (char)-1; } /** * Parses a string. * * @return the next string. * @throws JSONParseException if invalid JSON is found */ public String parseString() { char quot; if(check('\'')) quot = '\''; else if(check('\"')) quot = '\"'; else throw new JSONParseException(s, pos); char current; read(quot); StringBuilder buf = new StringBuilder(); int start = pos; while(pos < s.length() && (current = s.charAt(pos)) != quot) { if(current == '\\') { pos++; char x = get(); char special = 0; switch ( x ){ case 'u': { // decode unicode buf.append(s.substring(start, pos-1)); pos++; int tempPos = pos; readHex(); readHex(); readHex(); readHex(); int codePoint = Integer.parseInt(s.substring(tempPos, tempPos+4), 16); buf.append((char)codePoint); start = pos; continue; } case 'n': special = '\n'; break; case 'r': special = '\r'; break; case 't': special = '\t'; break; case 'b': special = '\b'; break; case '"': special = '\"'; break; case '\\': special = '\\'; break; } if ( special != 0 ){ buf.append(s.substring(start, pos-1)); pos++; buf.append( special ); start = pos; continue; } } pos++; } read(quot); buf.append(s.substring(start, pos-1)); return buf.toString(); } /** * Parses a number. * * @return the next number (int or double). * @throws JSONParseException if invalid JSON is found */ public Number parseNumber() { char current = get(); int start = this.pos; boolean isDouble = false; if(check('-') || check('+')) { pos++; } outer: while(pos < s.length()) { switch(s.charAt(pos)) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': pos++; break; case '.': isDouble = true; parseFraction(); break; default: break outer; } } if (isDouble) return Double.valueOf(s.substring(start, pos)); if ( pos - start >= 10 ) return Long.valueOf(s.substring(start, pos)); return Integer.valueOf(s.substring(start, pos)); } /** * Advances the pointed through <i>.digits</i>. */ public void parseFraction() { // get past . pos++; outer: while(pos < s.length()) { switch(s.charAt(pos)) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': pos++; break; default: break outer; } } } /** * Parses the next array. * * @return the array * @throws JSONParseException if invalid JSON is found */ public Object parseArray() { return parseArray(null); } /** * Parses the next array. * * @return the array * @throws JSONParseException if invalid JSON is found */ protected Object parseArray(String name) { if (name != null) { _callback.arrayStart(name); } else { _callback.arrayStart(); } read('['); int i = 0; char current = get(); while( current != ']' ) { String elemName = "" + i++; Object elem = parse(elemName); doCallback(elemName, elem); if((current = get()) == ',') { read(','); } else if(current == ']') { break; } else { throw new JSONParseException(s, pos); } } read(']'); return _callback.arrayDone(); } } /** * Exeception throw when invalid JSON is passed to JSONParser. * * This exception creates a message that points to the first * offending character in the JSON string: * <pre> * { "x" : 3, "y" : 4, some invalid json.... } * ^ * </pre> */ class JSONParseException extends RuntimeException { String s; int pos; public String getMessage() { StringBuilder sb = new StringBuilder(); sb.append("\n"); sb.append(s); sb.append("\n"); for(int i=0;i<pos;i++) { sb.append(" "); } sb.append("^"); return sb.toString(); } public JSONParseException(String s, int pos) { this.s = s; this.pos = pos; } }