diff --git a/AUTHORS.txt b/AUTHORS.txt index e896fc1..9fda0a2 100644 --- a/AUTHORS.txt +++ b/AUTHORS.txt @@ -1 +1,2 @@ -Fang Yidong \ No newline at end of file +Yidong Fang +Chris Nokleberg \ No newline at end of file diff --git a/ChangeLog.txt b/ChangeLog.txt index fa4dbb2..e6eb6ab 100644 --- a/ChangeLog.txt +++ b/ChangeLog.txt @@ -1,5 +1,14 @@ ChangeLog +Version 1.1 (2009/01/23) +* Supports stoppable SAX-like content handler for streaming of JSON text +* Added JSONStreamAware to support streaming JSON text +* Added ContainerFactory to support creating arbitrary Map and List as JSON object and JSON array container during decoding +* Supports any Map and List as JSON object and JSON array container during encoding +* Added interface JSONAware +* Added ParseException to get detail error report while parsing +* Added escaping for Unicode characters that cause problems for browser JS eval + Version 1.02 (2009/01/10) * Updated json.lex to improve performance of the lexer * Removed Rope.java and related junit test diff --git a/README.txt b/README.txt index ec4d4ca..e31e82c 100644 --- a/README.txt +++ b/README.txt @@ -1,153 +1,2 @@ -Simple Java toolkit for JSON (JSON.simple) -========================================== - -http://www.JSON.org/java/json_simple.zip - -1.Why the Simple Java toolkit (also named as JSON.simple) for JSON? - - When I use JSON as the data exchange format between the AJAX client and JSP - for the first time, what worry me mostly is how to encode Java strings and - numbers correctly in the server side so the AJAX client will receive a well - formed JSON data. When I looked into the 'JSON in Java' directory in JSON - website,I found that wrappers to JSONObject and JSONArray can be simpler, - due to the simplicity of JSON itself. So I wrote the JSON.simple package. - -2.Is it simple,really? - - I think so. Take an example: - - import org.json.simple.JSONObject; - - JSONObject obj=new JSONObject(); - obj.put("name","foo"); - obj.put("num",new Integer(100)); - obj.put("balance",new Double(1000.21)); - obj.put("is_vip",new Boolean(true)); - obj.put("nickname",null); - System.out.print(obj); - - Result: - {"nickname":null,"num":100,"balance":1000.21,"is_vip":true,"name":"foo"} - - The JSONObject.toString() will escape controls and specials correctly. - -3.How to use JSON.simple in JSP? - - Take an example in JSP: - - <%@page contentType="text/html; charset=UTF-8"%> - <%@page import="org.json.simple.JSONObject"%> - <% - JSONObject obj=new JSONObject(); - obj.put("name","foo"); - obj.put("num",new Integer(100)); - obj.put("balance",new Double(1000.21)); - obj.put("is_vip",new Boolean(true)); - obj.put("nickname",null); - out.print(obj); - out.flush(); - %> - - So the AJAX client will get the responseText. - -4.Some details about JSONObject? - - JSONObject inherits java.util.HashMap,so it don't have to worry about the - mapping things between keys and values. Feel free to use the Map methods - like get(), put(), and remove() and others. JSONObject.toString() will - combine key value pairs to get the JSON data string. Values will be escaped - into JSON quote string format if it's an instance of java.lang.String. Other - type of instance like java.lang.Number,java.lang.Boolean,null,JSONObject and - JSONArray will NOT escape, just take their java.lang.String.valueOf() result. - null value will be the JSON 'null' in the result. - - It's still correct if you put an instance of JSONObject or JSONArray into an - instance of JSONObject or JSONArray. Take the example about: - - JSONObject obj2=new JSONObject(); - obj2.put("phone","123456"); - obj2.put("zip","7890"); - obj.put("contact",obj2); - System.out.print(obj); - - Result: - {"nickname":null,"num":100,"contact":{"phone":"123456","zip":"7890"},"balance":1000.21,"is_vip":true,"name":"foo"} - - The method JSONObject.escape() is used to escape Java string into JSON quote - string. Controls and specials will be escaped correctly into \b,\f,\r,\n,\t, - \",\\,\/,\uhhhh. - -5.Some detail about JSONArray? - - org.json.simple.JSONArray inherits java.util.ArrayList. Feel free to use the - List methods like get(),add(),remove(),iterator() and so on. The rules of - JSONArray.toString() is similar to JSONObject.toString(). Here's the example: - - import org.json.simple.JSONArray; - - JSONArray array=new JSONArray(); - array.add("hello"); - array.add(new Integer(123)); - array.add(new Boolean(false)); - array.add(null); - array.add(new Double(123.45)); - array.add(obj2);//see above - System.out.print(array); - - Result: - ["hello",123,false,null,123.45,{"phone":"123456","zip":"7890"}] - -6.What is JSONValue for? - - org.json.simple.JSONValue is use to parse JSON data into Java Object. - In JSON, the topmost entity is JSON value, not the JSON object. But - it's not necessary to wrap JSON string,boolean,number and null again, - for the Java has already had the according classes: java.lang.String, - java.lang.Boolean,java.lang.Number and null. The mapping is: - - JSON Java - ------------------------------------------------ - string <=> java.lang.String - number <=> java.lang.Number - true|false <=> java.lang.Boolean - null <=> null - array <=> org.json.simple.JSONArray - object <=> org.json.simple.JSONObject - ------------------------------------------------ - - JSONValue has only one kind of method, JSONValue.parse(), which receives - a java.io.Reader or java.lang.String. Return type of JSONValue.parse() - is according to the mapping above. If the input is incorrect in syntax or - there's exceptions during the parsing, I choose to return null, ignoring - the exception: I have no idea if it's a serious implementaion, but I think - it's convenient to the user. - - Here's the example: - - String s="[0,{\"1\":{\"2\":{\"3\":{\"4\":[5,{\"6\":7}]}}}}]"; - Object obj=JSONValue.parse(s); - JSONArray array=(JSONArray)obj; - System.out.println(array.get(1)); - JSONObject obj2=(JSONObject)array.get(1); - System.out.println(obj2.get("1")); - - Result: - {"1":{"2":{"3":{"4":[5,{"6":7}]}}}} - {"2":{"3":{"4":[5,{"6":7}]}}} - -7.About the author. - - I'm a Java EE developer on Linux. - I'm working on web systems and information retrieval systems. - I also develop 3D games and Flash games. - - You can contact me through: - Fang Yidong - Fang Yidong - -8.Project home. - - http://code.google.com/p/json-simple/ - - -http://www.JSON.org/java/json_simple.zip \ No newline at end of file +Please visit: +http://code.google.com/p/json-simple/ \ No newline at end of file diff --git a/VERSION.txt b/VERSION.txt index e6d5cb8..b123147 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1 +1 @@ -1.0.2 \ No newline at end of file +1.1 \ No newline at end of file diff --git a/build.xml b/build.xml index 49a0a1e..a14dcb0 100644 --- a/build.xml +++ b/build.xml @@ -1,5 +1,5 @@ - + @@ -14,7 +14,6 @@ - @@ -24,30 +23,6 @@ target="1.2" source="1.2"/> - - - - - - - - - - - - - - - - - - - - \" { yybegin(YYINITIAL);return new Yytoken(Yytoken.TYPE_VALUE,sb.toString());} + \" { yybegin(YYINITIAL);return new Yytoken(Yytoken.TYPE_VALUE, sb.toString());} {UNESCAPED_CH}+ { sb.append(yytext());} \\\" {sb.append('"');} \\\\ {sb.append('\\');} @@ -28,15 +36,21 @@ UNESCAPED_CH = [^\"\\] \\n {sb.append('\n');} \\r {sb.append('\r');} \\t {sb.append('\t');} - \\u{HEX_D}{HEX_D}{HEX_D}{HEX_D} { int ch=Integer.parseInt(yytext().substring(2),16); - sb.append((char)ch); + \\u{HEX_D}{HEX_D}{HEX_D}{HEX_D} { try{ + int ch=Integer.parseInt(yytext().substring(2),16); + sb.append((char)ch); + } + catch(Exception e){ + throw new ParseException(yychar, ParseException.ERROR_UNEXPECTED_EXCEPTION, e); + } } + \\ {sb.append('\\');} \" { sb.delete(0, sb.length());yybegin(STRING_BEGIN);} - {INT} { Long val=Long.valueOf(yytext()); return new Yytoken(Yytoken.TYPE_VALUE,val);} - {DOUBLE} { Double val=Double.valueOf(yytext()); return new Yytoken(Yytoken.TYPE_VALUE,val);} - "true"|"false" { Boolean val=Boolean.valueOf(yytext()); return new Yytoken(Yytoken.TYPE_VALUE,val);} - "null" { return new Yytoken(Yytoken.TYPE_VALUE,null);} + {INT} { Long val=Long.valueOf(yytext()); return new Yytoken(Yytoken.TYPE_VALUE, val);} + {DOUBLE} { Double val=Double.valueOf(yytext()); return new Yytoken(Yytoken.TYPE_VALUE, val);} + "true"|"false" { Boolean val=Boolean.valueOf(yytext()); return new Yytoken(Yytoken.TYPE_VALUE, val);} + "null" { return new Yytoken(Yytoken.TYPE_VALUE, null);} "{" { return new Yytoken(Yytoken.TYPE_LEFT_BRACE,null);} "}" { return new Yytoken(Yytoken.TYPE_RIGHT_BRACE,null);} "[" { return new Yytoken(Yytoken.TYPE_LEFT_SQUARE,null);} @@ -44,3 +58,4 @@ UNESCAPED_CH = [^\"\\] "," { return new Yytoken(Yytoken.TYPE_COMMA,null);} ":" { return new Yytoken(Yytoken.TYPE_COLON,null);} {WS}+ {} + {FALLBACK_CH} { throw new ParseException(yychar, ParseException.ERROR_UNEXPECTED_CHAR, new Character(yycharat(0)));} \ No newline at end of file diff --git a/lib/json_simple-1.1.jar b/lib/json_simple-1.1.jar new file mode 100644 index 0000000..840ead2 Binary files /dev/null and b/lib/json_simple-1.1.jar differ diff --git a/src/org/json/simple/ItemList.java b/src/org/json/simple/ItemList.java new file mode 100644 index 0000000..07231e6 --- /dev/null +++ b/src/org/json/simple/ItemList.java @@ -0,0 +1,147 @@ +/* + * $Id: ItemList.java,v 1.1 2006/04/15 14:10:48 platform Exp $ + * Created on 2006-3-24 + */ +package org.json.simple; + +import java.util.ArrayList; +import java.util.List; +import java.util.StringTokenizer; + +/** + * |a:b:c| => |a|,|b|,|c| + * |:| => ||,|| + * |a:| => |a|,|| + * @author FangYidong + */ +public class ItemList { + private String sp=","; + List items=new ArrayList(); + + + public ItemList(){} + + + public ItemList(String s){ + this.split(s,sp,items); + } + + public ItemList(String s,String sp){ + this.sp=s; + this.split(s,sp,items); + } + + public ItemList(String s,String sp,boolean isMultiToken){ + split(s,sp,items,isMultiToken); + } + + public List getItems(){ + return this.items; + } + + public String[] getArray(){ + return (String[])this.items.toArray(); + } + + public void split(String s,String sp,List append,boolean isMultiToken){ + if(s==null || sp==null) + return; + if(isMultiToken){ + StringTokenizer tokens=new StringTokenizer(s,sp); + while(tokens.hasMoreTokens()){ + append.add(tokens.nextToken().trim()); + } + } + else{ + this.split(s,sp,append); + } + } + + public void split(String s,String sp,List append){ + if(s==null || sp==null) + return; + int pos=0; + int prevPos=0; + do{ + prevPos=pos; + pos=s.indexOf(sp,pos); + if(pos==-1) + break; + append.add(s.substring(prevPos,pos).trim()); + pos+=sp.length(); + }while(pos!=-1); + append.add(s.substring(prevPos).trim()); + } + + public void setSP(String sp){ + this.sp=sp; + } + + public void add(int i,String item){ + if(item==null) + return; + items.add(i,item.trim()); + } + + public void add(String item){ + if(item==null) + return; + items.add(item.trim()); + } + + public void addAll(ItemList list){ + items.addAll(list.items); + } + + public void addAll(String s){ + this.split(s,sp,items); + } + + public void addAll(String s,String sp){ + this.split(s,sp,items); + } + + public void addAll(String s,String sp,boolean isMultiToken){ + this.split(s,sp,items,isMultiToken); + } + + /** + * @param i 0-based + * @return + */ + public String get(int i){ + return (String)items.get(i); + } + + public int size(){ + return items.size(); + } + + public String toString(){ + return toString(sp); + } + + public String toString(String sp){ + StringBuffer sb=new StringBuffer(); + + for(int i=0;i */ -public class JSONArray extends ArrayList { - public String toString(){ - Iterator iter=iterator(); +public class JSONArray extends ArrayList implements List, JSONAware, JSONStreamAware { + private static final long serialVersionUID = 3957988303675231981L; + /** + * Encode a list into JSON text and write it to out. + * If this list is also a JSONStreamAware or a JSONAware, JSONStreamAware and JSONAware specific behaviours will be ignored at this top level. + * + * @see org.json.simple.JSONValue#writeJSONString(Object, Writer) + * + * @param list + * @param out + */ + public static void writeJSONString(List list, Writer out) throws IOException{ + if(list == null){ + out.write("null"); + return; + } + + boolean first = true; + Iterator iter=list.iterator(); + + out.write('['); + while(iter.hasNext()){ + if(first) + first = false; + else + out.write(','); + + Object value=iter.next(); + if(value == null){ + out.write("null"); + continue; + } + + JSONValue.writeJSONString(value, out); + } + out.write(']'); + } + + public void writeJSONString(Writer out) throws IOException{ + writeJSONString(this, out); + } + + /** + * Convert a list to JSON text. The result is a JSON array. + * If this list is also a JSONAware, JSONAware specific behaviours will be omitted at this top level. + * + * @see org.json.simple.JSONValue#toJSONString(Object) + * + * @param list + * @return JSON text, or "null" if list is null. + */ + public static String toJSONString(List list){ + if(list == null) + return "null"; + boolean first = true; StringBuffer sb = new StringBuffer(); + Iterator iter=list.iterator(); + sb.append('['); while(iter.hasNext()){ - if (first) { + if(first) first = false; - } else { + else sb.append(','); - } - JSONObject.escapeValue(iter.next(), sb); + + Object value=iter.next(); + if(value == null){ + sb.append("null"); + continue; + } + sb.append(JSONValue.toJSONString(value)); } sb.append(']'); - return sb.toString(); + return sb.toString(); } + + public String toJSONString(){ + return toJSONString(this); + } + + public String toString() { + return toJSONString(); + } + + } diff --git a/src/org/json/simple/JSONAware.java b/src/org/json/simple/JSONAware.java new file mode 100644 index 0000000..89f1525 --- /dev/null +++ b/src/org/json/simple/JSONAware.java @@ -0,0 +1,12 @@ +package org.json.simple; + +/** + * Beans that support customized output of JSON text shall implement this interface. + * @author FangYidong + */ +public interface JSONAware { + /** + * @return JSON text + */ + String toJSONString(); +} diff --git a/src/org/json/simple/JSONObject.java b/src/org/json/simple/JSONObject.java index d6fe95d..d4401e1 100644 --- a/src/org/json/simple/JSONObject.java +++ b/src/org/json/simple/JSONObject.java @@ -4,121 +4,126 @@ */ package org.json.simple; +import java.io.IOException; +import java.io.Writer; import java.util.HashMap; import java.util.Iterator; import java.util.Map; /** + * A JSON object. Key value pairs are unordered. JSONObject supports java.util.Map interface. + * * @author FangYidong */ -public class JSONObject extends HashMap{ - - public String toString(){ - Iterator iter=entrySet().iterator(); +public class JSONObject extends HashMap implements Map, JSONAware, JSONStreamAware{ + private static final long serialVersionUID = -503443796854799292L; - boolean first = true; + /** + * Encode a map into JSON text and write it to out. + * If this map is also a JSONAware or JSONStreamAware, JSONAware or JSONStreamAware specific behaviours will be ignored at this top level. + * + * @see org.json.simple.JSONValue#writeJSONString(Object, Writer) + * + * @param map + * @param out + */ + public static void writeJSONString(Map map, Writer out) throws IOException { + if(map == null){ + out.write("null"); + return; + } + + boolean first = true; + Iterator iter=map.entrySet().iterator(); + + out.write('{'); + while(iter.hasNext()){ + if(first) + first = false; + else + out.write(','); + Map.Entry entry=(Map.Entry)iter.next(); + out.write('\"'); + out.write(escape(String.valueOf(entry.getKey()))); + out.write('\"'); + out.write(':'); + JSONValue.writeJSONString(entry.getValue(), out); + } + out.write('}'); + } + + public void writeJSONString(Writer out) throws IOException{ + writeJSONString(this, out); + } + + /** + * Convert a map to JSON text. The result is a JSON object. + * If this map is also a JSONAware, JSONAware specific behaviours will be omitted at this top level. + * + * @see org.json.simple.JSONValue#toJSONString(Object) + * + * @param map + * @return JSON text, or "null" if map is null. + */ + public static String toJSONString(Map map){ + if(map == null) + return "null"; + StringBuffer sb = new StringBuffer(); + boolean first = true; + Iterator iter=map.entrySet().iterator(); + sb.append('{'); while(iter.hasNext()){ - if (first) { + if(first) first = false; - } else { + else sb.append(','); - } + Map.Entry entry=(Map.Entry)iter.next(); - toString(entry.getKey().toString(), entry.getValue(), sb); + toJSONString(String.valueOf(entry.getKey()),entry.getValue(), sb); } sb.append('}'); + return sb.toString(); + } + + public String toJSONString(){ + return toJSONString(this); + } + + private static String toJSONString(String key,Object value, StringBuffer sb){ + sb.append('\"'); + if(key == null) + sb.append("null"); + else + JSONValue.escape(key, sb); + sb.append('\"').append(':'); + + sb.append(JSONValue.toJSONString(value)); + + return sb.toString(); + } + + public String toString(){ + return toJSONString(); + } + + public static String toString(String key,Object value){ + StringBuffer sb = new StringBuffer(); + toJSONString(key, value, sb); return sb.toString(); } - public static String toString(String key, Object value){ - return toString(key, value, new StringBuffer()).toString(); - } - - private static StringBuffer toString(String key, Object value, StringBuffer sb){ - sb.append('\"'); - escape(key, sb); - sb.append("\":"); - escapeValue(value, sb); - return sb; - } - - // Package-protected for JSONArray - static void escapeValue(Object value, StringBuffer sb) { - if (value instanceof String){ - sb.append('\"'); - escape((String) value, sb); - sb.append('\"'); - } else { - sb.append(String.valueOf(value)); - } - } - /** - * " => \" , \ => \\ + * Escape quotes, \, /, \r, \n, \b, \f, \t and other control characters (U+0000 through U+001F). + * It's the same as JSONValue.escape() only for compatibility here. + * + * @see org.json.simple.JSONValue#escape(String) + * * @param s * @return */ public static String escape(String s){ - if(s==null) - return null; - StringBuffer sb = new StringBuffer(); - escape(s, sb); - return sb.toString(); - } - - // Package-protected for JSONArray - static void escape(String s, StringBuffer sb) { - for(int i=0;i='\u0000' && ch<='\u001F'){ - String ss=Integer.toHexString(ch); - sb.append("\\u"); - for(int k=0;k<4-ss.length();k++){ - sb.append('0'); - } - sb.append(ss.toUpperCase()); - } - else{ - sb.append(ch); - } - } - }//for + return JSONValue.escape(s); } } diff --git a/src/org/json/simple/JSONStreamAware.java b/src/org/json/simple/JSONStreamAware.java new file mode 100644 index 0000000..c2287c4 --- /dev/null +++ b/src/org/json/simple/JSONStreamAware.java @@ -0,0 +1,15 @@ +package org.json.simple; + +import java.io.IOException; +import java.io.Writer; + +/** + * Beans that support customized output of JSON text to a writer shall implement this interface. + * @author FangYidong + */ +public interface JSONStreamAware { + /** + * write JSON string to out. + */ + void writeJSONString(Writer out) throws IOException; +} diff --git a/src/org/json/simple/JSONValue.java b/src/org/json/simple/JSONValue.java index 662a1b9..18bde72 100644 --- a/src/org/json/simple/JSONValue.java +++ b/src/org/json/simple/JSONValue.java @@ -4,10 +4,15 @@ */ package org.json.simple; +import java.io.IOException; import java.io.Reader; import java.io.StringReader; +import java.io.Writer; +import java.util.List; +import java.util.Map; import org.json.simple.parser.JSONParser; +import org.json.simple.parser.ParseException; /** @@ -15,9 +20,21 @@ import org.json.simple.parser.JSONParser; */ public class JSONValue { /** - * parse into java object from input source. + * Parse JSON text into java object from the input source. + * Please use parseWithException() if you don't want to ignore the exception. + * + * @see org.json.simple.parser.JSONParser#parse(Reader) + * @see #parseWithException(Reader) + * * @param in - * @return instance of : JSONObject,JSONArray,String,Boolean,Long,Double or null + * @return Instance of the following: + * org.json.simple.JSONObject, + * org.json.simple.JSONArray, + * java.lang.String, + * java.lang.Number, + * java.lang.Boolean, + * null + * */ public static Object parse(Reader in){ try{ @@ -33,4 +50,231 @@ public class JSONValue { StringReader in=new StringReader(s); return parse(in); } + + /** + * Parse JSON text into java object from the input source. + * + * @see org.json.simple.parser.JSONParser + * + * @param in + * @return Instance of the following: + * org.json.simple.JSONObject, + * org.json.simple.JSONArray, + * java.lang.String, + * java.lang.Number, + * java.lang.Boolean, + * null + * + * @throws IOException + * @throws ParseException + */ + public static Object parseWithException(Reader in) throws IOException, ParseException{ + JSONParser parser=new JSONParser(); + return parser.parse(in); + } + + public static Object parseWithException(String s) throws ParseException{ + JSONParser parser=new JSONParser(); + return parser.parse(s); + } + + /** + * Encode an object into JSON text and write it to out. + *

+ * If this object is a Map or a List, and it's also a JSONStreamAware or a JSONAware, JSONStreamAware or JSONAware will be considered firstly. + *

+ * DO NOT call this method from writeJSONString(Writer) of a class that implements both JSONStreamAware and (Map or List) with + * "this" as the first parameter, use JSONObject.writeJSONString(Map, Writer) or JSONArray.writeJSONString(List, Writer) instead. + * + * @see org.json.simple.JSONObject#writeJSONString(Map, Writer) + * @see org.json.simple.JSONArray#writeJSONString(List, Writer) + * + * @param value + * @param writer + */ + public static void writeJSONString(Object value, Writer out) throws IOException { + if(value == null){ + out.write("null"); + return; + } + + if(value instanceof String){ + out.write('\"'); + out.write(escape((String)value)); + out.write('\"'); + return; + } + + if(value instanceof Double){ + if(((Double)value).isInfinite() || ((Double)value).isNaN()) + out.write("null"); + else + out.write(value.toString()); + return; + } + + if(value instanceof Float){ + if(((Float)value).isInfinite() || ((Float)value).isNaN()) + out.write("null"); + else + out.write(value.toString()); + return; + } + + if(value instanceof Number){ + out.write(value.toString()); + return; + } + + if(value instanceof Boolean){ + out.write(value.toString()); + return; + } + + if((value instanceof JSONStreamAware)){ + ((JSONStreamAware)value).writeJSONString(out); + return; + } + + if((value instanceof JSONAware)){ + out.write(((JSONAware)value).toJSONString()); + return; + } + + if(value instanceof Map){ + JSONObject.writeJSONString((Map)value, out); + return; + } + + if(value instanceof List){ + JSONArray.writeJSONString((List)value, out); + return; + } + + out.write(value.toString()); + } + + /** + * Convert an object to JSON text. + *

+ * If this object is a Map or a List, and it's also a JSONAware, JSONAware will be considered firstly. + *

+ * DO NOT call this method from toJSONString() of a class that implements both JSONAware and Map or List with + * "this" as the parameter, use JSONObject.toJSONString(Map) or JSONArray.toJSONString(List) instead. + * + * @see org.json.simple.JSONObject#toJSONString(Map) + * @see org.json.simple.JSONArray#toJSONString(List) + * + * @param value + * @return JSON text, or "null" if value is null or it's an NaN or an INF number. + */ + public static String toJSONString(Object value){ + if(value == null) + return "null"; + + if(value instanceof String) + return "\""+escape((String)value)+"\""; + + if(value instanceof Double){ + if(((Double)value).isInfinite() || ((Double)value).isNaN()) + return "null"; + else + return value.toString(); + } + + if(value instanceof Float){ + if(((Float)value).isInfinite() || ((Float)value).isNaN()) + return "null"; + else + return value.toString(); + } + + if(value instanceof Number) + return value.toString(); + + if(value instanceof Boolean) + return value.toString(); + + if((value instanceof JSONAware)) + return ((JSONAware)value).toJSONString(); + + if(value instanceof Map) + return JSONObject.toJSONString((Map)value); + + if(value instanceof List) + return JSONArray.toJSONString((List)value); + + return value.toString(); + } + + /** + * Escape quotes, \, /, \r, \n, \b, \f, \t and other control characters (U+0000 through U+001F). + * @param s + * @return + */ + public static String escape(String s){ + if(s==null) + return null; + StringBuffer sb = new StringBuffer(); + escape(s, sb); + return sb.toString(); + } + + /** + * @param s - Must not be null. + * @param sb + */ + static void escape(String s, StringBuffer sb) { + for(int i=0;i='\u0000' && ch<='\u001F'){ + String ss=Integer.toHexString(ch); + sb.append("\\u"); + for(int k=0;k<4-ss.length();k++){ + sb.append('0'); + } + sb.append(ss.toUpperCase()); + } + else{ + sb.append(ch); + } + } + }//for + } + } diff --git a/src/org/json/simple/parser/ContainerFactory.java b/src/org/json/simple/parser/ContainerFactory.java new file mode 100644 index 0000000..366ac4d --- /dev/null +++ b/src/org/json/simple/parser/ContainerFactory.java @@ -0,0 +1,23 @@ +package org.json.simple.parser; + +import java.util.List; +import java.util.Map; + +/** + * Container factory for creating containers for JSON object and JSON array. + * + * @see org.json.simple.parser.JSONParser#parse(java.io.Reader, ContainerFactory) + * + * @author FangYidong + */ +public interface ContainerFactory { + /** + * @return A Map instance to store JSON object, or null if you want to use org.json.simple.JSONObject. + */ + Map createObjectContainer(); + + /** + * @return A List instance to store JSON array, or null if you want to use org.json.simple.JSONArray. + */ + List creatArrayContainer(); +} diff --git a/src/org/json/simple/parser/ContentHandler.java b/src/org/json/simple/parser/ContentHandler.java new file mode 100644 index 0000000..a898ba0 --- /dev/null +++ b/src/org/json/simple/parser/ContentHandler.java @@ -0,0 +1,99 @@ +package org.json.simple.parser; + +import java.io.IOException; + +/** + * A simplified and stoppable SAX-like content handler for stream processing of JSON text. + * + * @see org.xml.sax.ContentHandler + * + * @author FangYidong + */ +public interface ContentHandler { + /** + * Receive notification of the beginning of JSON processing. + * + * User can throw a ParseException with errorType ParseException.INFO_USER_STOP to let the parser stop. + * + * @throws ParseException + * JSONParser will stop and throw the same exception to the caller when receiving this exception. + */ + void startJSON() throws ParseException, IOException; + + /** + * Receive notification of the end of JSON processing. + * + * @return false if the handler wants to stop parsing after return. + * @throws ParseException + */ + void endJSON() throws ParseException, IOException; + + /** + * Receive notification of the beginning of a JSON object. + * + * @return false if the handler wants to stop parsing after return. + * @throws ParseException + */ + boolean startObject() throws ParseException, IOException; + + /** + * Receive notification of the end of a JSON object. + * + * @return false if the handler wants to stop parsing after return. + * @throws ParseException + */ + boolean endObject() throws ParseException, IOException; + + /** + * Receive notification of the beginning of a JSON object key value pair. + * + * @param key - Key of a JSON object key value pair. + * + * @return false if the handler wants to stop parsing after return. + * @throws ParseException + */ + boolean startObjectEntry(String key) throws ParseException, IOException; + + /** + * Receive notification of the end of the value of previous object entry. + * + * @return false if the handler wants to stop parsing after return. + * @throws ParseException + */ + boolean endObjectEntry() throws ParseException, IOException; + + /** + * Receive notification of the beginning of a JSON array. + * + * @return false if the handler wants to stop parsing after return. + * @throws ParseException + */ + boolean startArray() throws ParseException, IOException; + + /** + * Receive notification of the end of a JSON array. + * + * @return false if the handler wants to stop parsing after return. + * @throws ParseException + */ + boolean endArray() throws ParseException, IOException; + + /** + * Receive notification of the JSON primitive values: + * java.lang.String, + * java.lang.Number, + * java.lang.Boolean + * null + * + * @param value - Instance of the following: + * java.lang.String, + * java.lang.Number, + * java.lang.Boolean + * null + * + * @return false if the handler wants to stop parsing after return. + * @throws ParseException + */ + boolean primitive(Object value) throws ParseException, IOException; + +} diff --git a/src/org/json/simple/parser/JSONParser.java b/src/org/json/simple/parser/JSONParser.java index 392adfc..9acaa37 100644 --- a/src/org/json/simple/parser/JSONParser.java +++ b/src/org/json/simple/parser/JSONParser.java @@ -4,14 +4,20 @@ */ package org.json.simple.parser; +import java.io.IOException; import java.io.Reader; +import java.io.StringReader; import java.util.LinkedList; +import java.util.List; +import java.util.Map; import org.json.simple.JSONArray; import org.json.simple.JSONObject; /** + * Parser for JSON text. Please note that JSONParser is NOT thread-safe. + * * @author FangYidong */ public class JSONParser { @@ -20,10 +26,11 @@ public class JSONParser { public static final int S_IN_OBJECT=2; public static final int S_IN_ARRAY=3; public static final int S_PASSED_PAIR_KEY=4; + public static final int S_IN_PAIR_VALUE=5; + public static final int S_END=6; public static final int S_IN_ERROR=-1; - private LinkedList statusStack = new LinkedList(); - private LinkedList valueStack = new LinkedList(); + private LinkedList handlerStatusStack; private Yylex lexer = new Yylex((Reader)null); private Yytoken token = null; private int status = S_INIT; @@ -35,22 +42,80 @@ public class JSONParser { return status.intValue(); } - private void reset(Reader in) throws Exception{ - statusStack.clear(); - valueStack.clear(); + /** + * Reset the parser to the initial state without resetting the underlying reader. + * + */ + public void reset(){ + token = null; + status = S_INIT; + handlerStatusStack = null; + } + + /** + * Reset the parser to the initial state with a new character reader. + * + * @param in - The new character reader. + * @throws IOException + * @throws ParseException + */ + public void reset(Reader in){ lexer.yyreset(in); - token = null; - status = S_INIT; + reset(); } - public Object parse(Reader in) throws Exception{ + /** + * @return The position of the beginning of the current token. + */ + public int getPosition(){ + return lexer.getPosition(); + } + + public Object parse(String s) throws ParseException{ + return parse(s, (ContainerFactory)null); + } + + public Object parse(String s, ContainerFactory containerFactory) throws ParseException{ + StringReader in=new StringReader(s); + try{ + return parse(in, containerFactory); + } + catch(IOException ie){ + /* + * Actually it will never happen. + */ + throw new ParseException(-1, ParseException.ERROR_UNEXPECTED_EXCEPTION, ie); + } + } + + public Object parse(Reader in) throws IOException, ParseException{ + return parse(in, (ContainerFactory)null); + } + + /** + * Parse JSON text into java object from the input source. + * + * @param in + * @param containerFactory - Use this factory to createyour own JSON object and JSON array containers. + * @return Instance of the following: + * org.json.simple.JSONObject, + * org.json.simple.JSONArray, + * java.lang.String, + * java.lang.Number, + * java.lang.Boolean, + * null + * + * @throws IOException + * @throws ParseException + */ + public Object parse(Reader in, ContainerFactory containerFactory) throws IOException, ParseException{ reset(in); - + LinkedList statusStack = new LinkedList(); + LinkedList valueStack = new LinkedList(); + try{ do{ - token=lexer.yylex(); - if(token==null) - token=new Yytoken(Yytoken.TYPE_EOF,null); + nextToken(); switch(status){ case S_INIT: switch(token.type){ @@ -62,12 +127,12 @@ public class JSONParser { case Yytoken.TYPE_LEFT_BRACE: status=S_IN_OBJECT; statusStack.addFirst(new Integer(status)); - valueStack.addFirst(new JSONObject()); + valueStack.addFirst(createObjectContainer(containerFactory)); break; case Yytoken.TYPE_LEFT_SQUARE: status=S_IN_ARRAY; statusStack.addFirst(new Integer(status)); - valueStack.addFirst(new JSONArray()); + valueStack.addFirst(createArrayContainer(containerFactory)); break; default: status=S_IN_ERROR; @@ -78,7 +143,7 @@ public class JSONParser { if(token.type==Yytoken.TYPE_EOF) return valueStack.removeFirst(); else - return null; + throw new ParseException(getPosition(), ParseException.ERROR_UNEXPECTED_TOKEN, token); case S_IN_OBJECT: switch(token.type){ @@ -118,15 +183,15 @@ public class JSONParser { case Yytoken.TYPE_VALUE: statusStack.removeFirst(); String key=(String)valueStack.removeFirst(); - JSONObject parent=(JSONObject)valueStack.getFirst(); + Map parent=(Map)valueStack.getFirst(); parent.put(key,token.value); status=peekStatus(statusStack); break; case Yytoken.TYPE_LEFT_SQUARE: statusStack.removeFirst(); key=(String)valueStack.removeFirst(); - parent=(JSONObject)valueStack.getFirst(); - JSONArray newArray=new JSONArray(); + parent=(Map)valueStack.getFirst(); + List newArray=createArrayContainer(containerFactory); parent.put(key,newArray); status=S_IN_ARRAY; statusStack.addFirst(new Integer(status)); @@ -135,8 +200,8 @@ public class JSONParser { case Yytoken.TYPE_LEFT_BRACE: statusStack.removeFirst(); key=(String)valueStack.removeFirst(); - parent=(JSONObject)valueStack.getFirst(); - JSONObject newObject=new JSONObject(); + parent=(Map)valueStack.getFirst(); + Map newObject=createObjectContainer(containerFactory); parent.put(key,newObject); status=S_IN_OBJECT; statusStack.addFirst(new Integer(status)); @@ -152,7 +217,7 @@ public class JSONParser { case Yytoken.TYPE_COMMA: break; case Yytoken.TYPE_VALUE: - JSONArray val=(JSONArray)valueStack.getFirst(); + List val=(List)valueStack.getFirst(); val.add(token.value); break; case Yytoken.TYPE_RIGHT_SQUARE: @@ -166,16 +231,16 @@ public class JSONParser { } break; case Yytoken.TYPE_LEFT_BRACE: - val=(JSONArray)valueStack.getFirst(); - JSONObject newObject=new JSONObject(); + val=(List)valueStack.getFirst(); + Map newObject=createObjectContainer(containerFactory); val.add(newObject); status=S_IN_OBJECT; statusStack.addFirst(new Integer(status)); valueStack.addFirst(newObject); break; case Yytoken.TYPE_LEFT_SQUARE: - val=(JSONArray)valueStack.getFirst(); - JSONArray newArray=new JSONArray(); + val=(List)valueStack.getFirst(); + List newArray=createArrayContainer(containerFactory); val.add(newArray); status=S_IN_ARRAY; statusStack.addFirst(new Integer(status)); @@ -186,15 +251,283 @@ public class JSONParser { }//inner switch break; case S_IN_ERROR: - return null; + throw new ParseException(getPosition(), ParseException.ERROR_UNEXPECTED_TOKEN, token); }//switch - if(status==S_IN_ERROR) - return null; + if(status==S_IN_ERROR){ + throw new ParseException(getPosition(), ParseException.ERROR_UNEXPECTED_TOKEN, token); + } }while(token.type!=Yytoken.TYPE_EOF); } - catch(Exception e){ + catch(IOException ie){ + throw ie; + } + + throw new ParseException(getPosition(), ParseException.ERROR_UNEXPECTED_TOKEN, token); + } + + private void nextToken() throws ParseException, IOException{ + token = lexer.yylex(); + if(token == null) + token = new Yytoken(Yytoken.TYPE_EOF, null); + } + + private Map createObjectContainer(ContainerFactory containerFactory){ + if(containerFactory == null) + return new JSONObject(); + Map m = containerFactory.createObjectContainer(); + + if(m == null) + return new JSONObject(); + return m; + } + + private List createArrayContainer(ContainerFactory containerFactory){ + if(containerFactory == null) + return new JSONArray(); + List l = containerFactory.creatArrayContainer(); + + if(l == null) + return new JSONArray(); + return l; + } + + public void parse(String s, ContentHandler contentHandler) throws ParseException{ + parse(s, contentHandler, false); + } + + public void parse(String s, ContentHandler contentHandler, boolean isResume) throws ParseException{ + StringReader in=new StringReader(s); + try{ + parse(in, contentHandler, isResume); + } + catch(IOException ie){ + /* + * Actually it will never happen. + */ + throw new ParseException(-1, ParseException.ERROR_UNEXPECTED_EXCEPTION, ie); + } + } + + public void parse(Reader in, ContentHandler contentHandler) throws IOException, ParseException{ + parse(in, contentHandler, false); + } + + /** + * Stream processing of JSON text. + * + * @see ContentHandler + * + * @param in + * @param contentHandler + * @param isResume - Indicates if it continues previous parsing operation. + * If set to true, resume parsing the old stream, and parameter 'in' will be ignored. + * If this method is called for the first time in this instance, isResume will be ignored. + * + * @throws IOException + * @throws ParseException + */ + public void parse(Reader in, ContentHandler contentHandler, boolean isResume) throws IOException, ParseException{ + if(!isResume){ + reset(in); + handlerStatusStack = new LinkedList(); + } + else{ + if(handlerStatusStack == null){ + isResume = false; + reset(in); + handlerStatusStack = new LinkedList(); + } + } + + LinkedList statusStack = handlerStatusStack; + + try{ + do{ + switch(status){ + case S_INIT: + contentHandler.startJSON(); + nextToken(); + switch(token.type){ + case Yytoken.TYPE_VALUE: + status=S_IN_FINISHED_VALUE; + statusStack.addFirst(new Integer(status)); + if(!contentHandler.primitive(token.value)) + return; + break; + case Yytoken.TYPE_LEFT_BRACE: + status=S_IN_OBJECT; + statusStack.addFirst(new Integer(status)); + if(!contentHandler.startObject()) + return; + break; + case Yytoken.TYPE_LEFT_SQUARE: + status=S_IN_ARRAY; + statusStack.addFirst(new Integer(status)); + if(!contentHandler.startArray()) + return; + break; + default: + status=S_IN_ERROR; + }//inner switch + break; + + case S_IN_FINISHED_VALUE: + nextToken(); + if(token.type==Yytoken.TYPE_EOF){ + contentHandler.endJSON(); + status = S_END; + return; + } + else{ + status = S_IN_ERROR; + throw new ParseException(getPosition(), ParseException.ERROR_UNEXPECTED_TOKEN, token); + } + + case S_IN_OBJECT: + nextToken(); + switch(token.type){ + case Yytoken.TYPE_COMMA: + break; + case Yytoken.TYPE_VALUE: + if(token.value instanceof String){ + String key=(String)token.value; + status=S_PASSED_PAIR_KEY; + statusStack.addFirst(new Integer(status)); + if(!contentHandler.startObjectEntry(key)) + return; + } + else{ + status=S_IN_ERROR; + } + break; + case Yytoken.TYPE_RIGHT_BRACE: + if(statusStack.size()>1){ + statusStack.removeFirst(); + status=peekStatus(statusStack); + } + else{ + status=S_IN_FINISHED_VALUE; + } + if(!contentHandler.endObject()) + return; + break; + default: + status=S_IN_ERROR; + break; + }//inner switch + break; + + case S_PASSED_PAIR_KEY: + nextToken(); + switch(token.type){ + case Yytoken.TYPE_COLON: + break; + case Yytoken.TYPE_VALUE: + statusStack.removeFirst(); + status=peekStatus(statusStack); + if(!contentHandler.primitive(token.value)) + return; + if(!contentHandler.endObjectEntry()) + return; + break; + case Yytoken.TYPE_LEFT_SQUARE: + statusStack.removeFirst(); + statusStack.addFirst(new Integer(S_IN_PAIR_VALUE)); + status=S_IN_ARRAY; + statusStack.addFirst(new Integer(status)); + if(!contentHandler.startArray()) + return; + break; + case Yytoken.TYPE_LEFT_BRACE: + statusStack.removeFirst(); + statusStack.addFirst(new Integer(S_IN_PAIR_VALUE)); + status=S_IN_OBJECT; + statusStack.addFirst(new Integer(status)); + if(!contentHandler.startObject()) + return; + break; + default: + status=S_IN_ERROR; + } + break; + + case S_IN_PAIR_VALUE: + /* + * S_IN_PAIR_VALUE is just a marker to indicate the end of an object entry, it doesn't proccess any token, + * therefore delay consuming token until next round. + */ + statusStack.removeFirst(); + status = peekStatus(statusStack); + if(!contentHandler.endObjectEntry()) + return; + break; + + case S_IN_ARRAY: + nextToken(); + switch(token.type){ + case Yytoken.TYPE_COMMA: + break; + case Yytoken.TYPE_VALUE: + if(!contentHandler.primitive(token.value)) + return; + break; + case Yytoken.TYPE_RIGHT_SQUARE: + if(statusStack.size()>1){ + statusStack.removeFirst(); + status=peekStatus(statusStack); + } + else{ + status=S_IN_FINISHED_VALUE; + } + if(!contentHandler.endArray()) + return; + break; + case Yytoken.TYPE_LEFT_BRACE: + status=S_IN_OBJECT; + statusStack.addFirst(new Integer(status)); + if(!contentHandler.startObject()) + return; + break; + case Yytoken.TYPE_LEFT_SQUARE: + status=S_IN_ARRAY; + statusStack.addFirst(new Integer(status)); + if(!contentHandler.startArray()) + return; + break; + default: + status=S_IN_ERROR; + }//inner switch + break; + + case S_END: + return; + + case S_IN_ERROR: + throw new ParseException(getPosition(), ParseException.ERROR_UNEXPECTED_TOKEN, token); + }//switch + if(status==S_IN_ERROR){ + throw new ParseException(getPosition(), ParseException.ERROR_UNEXPECTED_TOKEN, token); + } + }while(token.type!=Yytoken.TYPE_EOF); + } + catch(IOException ie){ + status = S_IN_ERROR; + throw ie; + } + catch(ParseException pe){ + status = S_IN_ERROR; + throw pe; + } + catch(RuntimeException re){ + status = S_IN_ERROR; + throw re; + } + catch(Error e){ + status = S_IN_ERROR; throw e; } - return null; + + status = S_IN_ERROR; + throw new ParseException(getPosition(), ParseException.ERROR_UNEXPECTED_TOKEN, token); } } diff --git a/src/org/json/simple/parser/ParseException.java b/src/org/json/simple/parser/ParseException.java new file mode 100644 index 0000000..f497cd9 --- /dev/null +++ b/src/org/json/simple/parser/ParseException.java @@ -0,0 +1,90 @@ +package org.json.simple.parser; + +/** + * ParseException explains why and where the error occurs in source JSON text. + * + * @author FangYidong + * + */ +public class ParseException extends Exception { + private static final long serialVersionUID = -7880698968187728548L; + + public static final int ERROR_UNEXPECTED_CHAR = 0; + public static final int ERROR_UNEXPECTED_TOKEN = 1; + public static final int ERROR_UNEXPECTED_EXCEPTION = 2; + + private int errorType; + private Object unexpectedObject; + private int position; + + public ParseException(int errorType){ + this(-1, errorType, null); + } + + public ParseException(int errorType, Object unexpectedObject){ + this(-1, errorType, unexpectedObject); + } + + public ParseException(int position, int errorType, Object unexpectedObject){ + this.position = position; + this.errorType = errorType; + this.unexpectedObject = unexpectedObject; + } + + public int getErrorType() { + return errorType; + } + + public void setErrorType(int errorType) { + this.errorType = errorType; + } + + /** + * @see org.json.simple.parser.JSONParser#getPosition() + * + * @return The character position (starting with 0) of the input where the error occurs. + */ + public int getPosition() { + return position; + } + + public void setPosition(int position) { + this.position = position; + } + + /** + * @see org.json.simple.parser.Yytoken + * + * @return One of the following base on the value of errorType: + * ERROR_UNEXPECTED_CHAR java.lang.Character + * ERROR_UNEXPECTED_TOKEN org.json.simple.parser.Yytoken + * ERROR_UNEXPECTED_EXCEPTION java.lang.Exception + */ + public Object getUnexpectedObject() { + return unexpectedObject; + } + + public void setUnexpectedObject(Object unexpectedObject) { + this.unexpectedObject = unexpectedObject; + } + + public String toString(){ + StringBuffer sb = new StringBuffer(); + + switch(errorType){ + case ERROR_UNEXPECTED_CHAR: + sb.append("Unexpected character (").append(unexpectedObject).append(") at position ").append(position).append("."); + break; + case ERROR_UNEXPECTED_TOKEN: + sb.append("Unexpected token ").append(unexpectedObject).append(" at position ").append(position).append("."); + break; + case ERROR_UNEXPECTED_EXCEPTION: + sb.append("Unexpected exception at position ").append(position).append(": ").append(unexpectedObject); + break; + default: + sb.append("Unkown error at position ").append(position).append("."); + break; + } + return sb.toString(); + } +} diff --git a/src/org/json/simple/parser/Yylex.java b/src/org/json/simple/parser/Yylex.java index a9d103d..42ce508 100644 --- a/src/org/json/simple/parser/Yylex.java +++ b/src/org/json/simple/parser/Yylex.java @@ -28,11 +28,11 @@ class Yylex { * Translates characters to character classes */ private static final String ZZ_CMAP_PACKED = - "\11\0\2\7\2\0\1\7\22\0\1\7\1\0\1\11\10\0\1\6"+ - "\1\31\1\2\1\4\1\12\12\3\1\32\6\0\4\1\1\5\1\1"+ - "\24\0\1\27\1\10\1\30\3\0\1\22\1\13\2\1\1\21\1\14"+ - "\5\0\1\23\1\0\1\15\3\0\1\16\1\24\1\17\1\20\5\0"+ - "\1\25\1\0\1\26\uff82\0"; + "\11\0\1\7\1\7\2\0\1\7\22\0\1\7\1\0\1\11\10\0"+ + "\1\6\1\31\1\2\1\4\1\12\12\3\1\32\6\0\4\1\1\5"+ + "\1\1\24\0\1\27\1\10\1\30\3\0\1\22\1\13\2\1\1\21"+ + "\1\14\5\0\1\23\1\0\1\15\3\0\1\16\1\24\1\17\1\20"+ + "\5\0\1\25\1\0\1\26\uff82\0"; /** * Translates characters to character classes @@ -45,13 +45,14 @@ class Yylex { private static final int [] ZZ_ACTION = zzUnpackAction(); private static final String ZZ_ACTION_PACKED_0 = - "\3\0\1\1\1\2\1\3\3\0\1\4\1\5\1\6"+ - "\1\7\1\10\1\11\1\12\1\0\1\13\5\0\1\14"+ - "\1\15\1\16\1\17\1\20\1\21\1\22\1\23\1\0"+ - "\1\24\1\0\1\24\4\0\1\25\1\26\2\0\1\27"; + "\2\0\2\1\1\2\1\3\1\4\3\1\1\5\1\6"+ + "\1\7\1\10\1\11\1\12\1\13\1\14\1\15\5\0"+ + "\1\14\1\16\1\17\1\20\1\21\1\22\1\23\1\24"+ + "\1\0\1\25\1\0\1\25\4\0\1\26\1\27\2\0"+ + "\1\30"; private static int [] zzUnpackAction() { - int [] result = new int[44]; + int [] result = new int[45]; int offset = 0; offset = zzUnpackAction(ZZ_ACTION_PACKED_0, offset, result); return result; @@ -70,6 +71,111 @@ class Yylex { } + /** + * Translates a state to a row index in the transition table + */ + private static final int [] ZZ_ROWMAP = zzUnpackRowMap(); + + private static final String ZZ_ROWMAP_PACKED_0 = + "\0\0\0\33\0\66\0\121\0\154\0\207\0\66\0\242"+ + "\0\275\0\330\0\66\0\66\0\66\0\66\0\66\0\66"+ + "\0\363\0\u010e\0\66\0\u0129\0\u0144\0\u015f\0\u017a\0\u0195"+ + "\0\66\0\66\0\66\0\66\0\66\0\66\0\66\0\66"+ + "\0\u01b0\0\u01cb\0\u01e6\0\u01e6\0\u0201\0\u021c\0\u0237\0\u0252"+ + "\0\66\0\66\0\u026d\0\u0288\0\66"; + + private static int [] zzUnpackRowMap() { + int [] result = new int[45]; + int offset = 0; + offset = zzUnpackRowMap(ZZ_ROWMAP_PACKED_0, offset, result); + return result; + } + + private static int zzUnpackRowMap(String packed, int offset, int [] result) { + int i = 0; /* index in packed string */ + int j = offset; /* index in unpacked array */ + int l = packed.length(); + while (i < l) { + int high = packed.charAt(i++) << 16; + result[j++] = high | packed.charAt(i++); + } + return j; + } + + /** + * The transition table of the DFA + */ + private static final int ZZ_TRANS [] = { + 2, 2, 3, 4, 2, 2, 2, 5, 2, 6, + 2, 2, 7, 8, 2, 9, 2, 2, 2, 2, + 2, 10, 11, 12, 13, 14, 15, 16, 16, 16, + 16, 16, 16, 16, 16, 17, 18, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, 4, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, 4, 19, 20, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, 20, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, 5, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + 21, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, 22, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + 23, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, 16, 16, 16, 16, 16, 16, 16, + 16, -1, -1, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + -1, -1, -1, -1, -1, -1, -1, -1, 24, 25, + 26, 27, 28, 29, 30, 31, 32, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + 33, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, 34, 35, -1, -1, + 34, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + 36, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, 37, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, 38, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, 39, -1, 39, -1, 39, -1, -1, + -1, -1, -1, 39, 39, -1, -1, -1, -1, 39, + 39, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, 33, -1, 20, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, 20, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, 35, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, 38, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, 40, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, 41, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, 42, -1, 42, -1, 42, + -1, -1, -1, -1, -1, 42, 42, -1, -1, -1, + -1, 42, 42, -1, -1, -1, -1, -1, -1, -1, + -1, -1, 43, -1, 43, -1, 43, -1, -1, -1, + -1, -1, 43, 43, -1, -1, -1, -1, 43, 43, + -1, -1, -1, -1, -1, -1, -1, -1, -1, 44, + -1, 44, -1, 44, -1, -1, -1, -1, -1, 44, + 44, -1, -1, -1, -1, 44, 44, -1, -1, -1, + -1, -1, -1, -1, -1, + }; + /* error codes */ private static final int ZZ_UNKNOWN_ERROR = 0; private static final int ZZ_NO_MATCH = 1; @@ -82,6 +188,35 @@ class Yylex { "Error: pushback value was too large" }; + /** + * ZZ_ATTRIBUTE[aState] contains the attributes of state aState + */ + private static final int [] ZZ_ATTRIBUTE = zzUnpackAttribute(); + + private static final String ZZ_ATTRIBUTE_PACKED_0 = + "\2\0\1\11\3\1\1\11\3\1\6\11\2\1\1\11"+ + "\5\0\10\11\1\0\1\1\1\0\1\1\4\0\2\11"+ + "\2\0\1\11"; + + private static int [] zzUnpackAttribute() { + int [] result = new int[45]; + int offset = 0; + offset = zzUnpackAttribute(ZZ_ATTRIBUTE_PACKED_0, offset, result); + return result; + } + + private static int zzUnpackAttribute(String packed, int offset, int [] result) { + int i = 0; /* index in packed string */ + int j = offset; /* index in unpacked array */ + int l = packed.length(); + while (i < l) { + int count = packed.charAt(i++); + int value = packed.charAt(i++); + do result[j++] = value; while (--count > 0); + } + return j; + } + /** the input device */ private java.io.Reader zzReader; @@ -131,6 +266,10 @@ class Yylex { /* user code: */ private StringBuffer sb=new StringBuffer(); +int getPosition(){ + return yychar; +} + /** @@ -163,7 +302,7 @@ private StringBuffer sb=new StringBuffer(); char [] map = new char[0x10000]; int i = 0; /* index in packed string */ int j = 0; /* index in unpacked array */ - while (i < 88) { + while (i < 90) { int count = packed.charAt(i++); char value = packed.charAt(i++); do map[j++] = value; while (--count > 0); @@ -359,7 +498,7 @@ private StringBuffer sb=new StringBuffer(); * @return the next token * @exception java.io.IOException if any I/O-Error occurs */ - public Yytoken yylex() throws java.io.IOException { + public Yytoken yylex() throws java.io.IOException, ParseException { int zzInput; int zzAction; @@ -370,10 +509,15 @@ private StringBuffer sb=new StringBuffer(); char [] zzBufferL = zzBuffer; char [] zzCMapL = ZZ_CMAP; + int [] zzTransL = ZZ_TRANS; + int [] zzRowMapL = ZZ_ROWMAP; + int [] zzAttrL = ZZ_ATTRIBUTE; while (true) { zzMarkedPosL = zzMarkedPos; + yychar+= zzMarkedPosL-zzStartRead; + zzAction = -1; zzCurrentPosL = zzCurrentPos = zzStartRead = zzMarkedPosL; @@ -408,225 +552,15 @@ private StringBuffer sb=new StringBuffer(); zzInput = zzBufferL[zzCurrentPosL++]; } } - zzInput = zzCMapL[zzInput]; + int zzNext = zzTransL[ zzRowMapL[zzState] + zzCMapL[zzInput] ]; + if (zzNext == -1) break zzForAction; + zzState = zzNext; - boolean zzIsFinal = false; - boolean zzNoLookAhead = false; - - zzForNext: { switch (zzState) { - case 0: - switch (zzInput) { - case 2: zzState = 2; break zzForNext; - case 3: zzIsFinal = true; zzState = 3; break zzForNext; - case 7: zzIsFinal = true; zzState = 4; break zzForNext; - case 9: zzIsFinal = true; zzNoLookAhead = true; zzState = 5; break zzForNext; - case 12: zzState = 6; break zzForNext; - case 13: zzState = 7; break zzForNext; - case 15: zzState = 8; break zzForNext; - case 21: zzIsFinal = true; zzNoLookAhead = true; zzState = 9; break zzForNext; - case 22: zzIsFinal = true; zzNoLookAhead = true; zzState = 10; break zzForNext; - case 23: zzIsFinal = true; zzNoLookAhead = true; zzState = 11; break zzForNext; - case 24: zzIsFinal = true; zzNoLookAhead = true; zzState = 12; break zzForNext; - case 25: zzIsFinal = true; zzNoLookAhead = true; zzState = 13; break zzForNext; - case 26: zzIsFinal = true; zzNoLookAhead = true; zzState = 14; break zzForNext; - default: break zzForAction; - } - - case 1: - switch (zzInput) { - case 8: zzState = 16; break zzForNext; - case 9: zzIsFinal = true; zzNoLookAhead = true; zzState = 17; break zzForNext; - default: zzIsFinal = true; zzState = 15; break zzForNext; - } - - case 2: - switch (zzInput) { - case 3: zzIsFinal = true; zzState = 3; break zzForNext; - default: break zzForAction; - } - - case 3: - switch (zzInput) { - case 3: zzIsFinal = true; break zzForNext; - case 4: zzState = 18; break zzForNext; - case 5: - case 17: zzState = 19; break zzForNext; - default: break zzForAction; - } - - case 4: - switch (zzInput) { - case 7: zzIsFinal = true; break zzForNext; - default: break zzForAction; - } - - case 6: - switch (zzInput) { - case 18: zzState = 20; break zzForNext; - default: break zzForAction; - } - - case 7: - switch (zzInput) { - case 16: zzState = 21; break zzForNext; - default: break zzForAction; - } - - case 8: - switch (zzInput) { - case 14: zzState = 22; break zzForNext; - default: break zzForAction; - } - - case 15: - switch (zzInput) { - case 8: - case 9: break zzForAction; - default: zzIsFinal = true; break zzForNext; - } - - case 16: - switch (zzInput) { - case 8: zzIsFinal = true; zzNoLookAhead = true; zzState = 23; break zzForNext; - case 9: zzIsFinal = true; zzNoLookAhead = true; zzState = 24; break zzForNext; - case 10: zzIsFinal = true; zzNoLookAhead = true; zzState = 25; break zzForNext; - case 11: zzIsFinal = true; zzNoLookAhead = true; zzState = 26; break zzForNext; - case 12: zzIsFinal = true; zzNoLookAhead = true; zzState = 27; break zzForNext; - case 13: zzIsFinal = true; zzNoLookAhead = true; zzState = 28; break zzForNext; - case 14: zzIsFinal = true; zzNoLookAhead = true; zzState = 29; break zzForNext; - case 15: zzIsFinal = true; zzNoLookAhead = true; zzState = 30; break zzForNext; - case 16: zzState = 31; break zzForNext; - default: break zzForAction; - } - - case 18: - switch (zzInput) { - case 3: zzIsFinal = true; zzState = 32; break zzForNext; - default: break zzForAction; - } - - case 19: - switch (zzInput) { - case 2: - case 6: zzState = 33; break zzForNext; - case 3: zzIsFinal = true; zzState = 34; break zzForNext; - default: break zzForAction; - } - - case 20: - switch (zzInput) { - case 19: zzState = 35; break zzForNext; - default: break zzForAction; - } - - case 21: - switch (zzInput) { - case 19: zzState = 36; break zzForNext; - default: break zzForAction; - } - - case 22: - switch (zzInput) { - case 16: zzState = 37; break zzForNext; - default: break zzForAction; - } - - case 31: - switch (zzInput) { - case 1: - case 3: - case 5: - case 11: - case 12: - case 17: - case 18: zzState = 38; break zzForNext; - default: break zzForAction; - } - - case 32: - switch (zzInput) { - case 5: - case 17: zzState = 19; break zzForNext; - case 3: zzIsFinal = true; break zzForNext; - default: break zzForAction; - } - - case 33: - switch (zzInput) { - case 3: zzIsFinal = true; zzState = 34; break zzForNext; - default: break zzForAction; - } - - case 34: - switch (zzInput) { - case 3: zzIsFinal = true; break zzForNext; - default: break zzForAction; - } - - case 35: - switch (zzInput) { - case 20: zzState = 37; break zzForNext; - default: break zzForAction; - } - - case 36: - switch (zzInput) { - case 19: zzIsFinal = true; zzNoLookAhead = true; zzState = 39; break zzForNext; - default: break zzForAction; - } - - case 37: - switch (zzInput) { - case 17: zzIsFinal = true; zzNoLookAhead = true; zzState = 40; break zzForNext; - default: break zzForAction; - } - - case 38: - switch (zzInput) { - case 1: - case 3: - case 5: - case 11: - case 12: - case 17: - case 18: zzState = 41; break zzForNext; - default: break zzForAction; - } - - case 41: - switch (zzInput) { - case 1: - case 3: - case 5: - case 11: - case 12: - case 17: - case 18: zzState = 42; break zzForNext; - default: break zzForAction; - } - - case 42: - switch (zzInput) { - case 1: - case 3: - case 5: - case 11: - case 12: - case 17: - case 18: zzIsFinal = true; zzNoLookAhead = true; zzState = 43; break zzForNext; - default: break zzForAction; - } - - default: - // if this is ever reached, there is a serious bug in JFlex - zzScanError(ZZ_UNKNOWN_ERROR); - break; - } } - - if ( zzIsFinal ) { + int zzAttributes = zzAttrL[zzState]; + if ( (zzAttributes & 1) == 1 ) { zzAction = zzState; zzMarkedPosL = zzCurrentPosL; - if ( zzNoLookAhead ) break zzForAction; + if ( (zzAttributes & 8) == 8 ) break zzForAction; } } @@ -636,99 +570,108 @@ private StringBuffer sb=new StringBuffer(); zzMarkedPos = zzMarkedPosL; switch (zzAction < 0 ? zzAction : ZZ_ACTION[zzAction]) { - case 10: + case 11: { sb.append(yytext()); } - case 24: break; - case 3: + case 25: break; + case 4: { sb.delete(0, sb.length());yybegin(STRING_BEGIN); } - case 25: break; - case 15: + case 26: break; + case 16: { sb.append('\b'); } - case 26: break; - case 5: + case 27: break; + case 6: { return new Yytoken(Yytoken.TYPE_RIGHT_BRACE,null); } - case 27: break; + case 28: break; + case 23: + { Boolean val=Boolean.valueOf(yytext()); return new Yytoken(Yytoken.TYPE_VALUE, val); + } + case 29: break; + case 22: + { return new Yytoken(Yytoken.TYPE_VALUE, null); + } + case 30: break; + case 13: + { yybegin(YYINITIAL);return new Yytoken(Yytoken.TYPE_VALUE, sb.toString()); + } + case 31: break; case 12: { sb.append('\\'); } - case 28: break; - case 20: - { Double val=Double.valueOf(yytext()); return new Yytoken(Yytoken.TYPE_VALUE,val); - } - case 29: break; - case 1: - { Long val=Long.valueOf(yytext()); return new Yytoken(Yytoken.TYPE_VALUE,val); - } - case 30: break; - case 7: - { return new Yytoken(Yytoken.TYPE_RIGHT_SQUARE,null); - } - case 31: break; - case 18: - { sb.append('\r'); - } case 32: break; case 21: - { return new Yytoken(Yytoken.TYPE_VALUE,null); + { Double val=Double.valueOf(yytext()); return new Yytoken(Yytoken.TYPE_VALUE, val); } case 33: break; - case 14: - { sb.append('/'); + case 1: + { throw new ParseException(yychar, ParseException.ERROR_UNEXPECTED_CHAR, new Character(yycharat(0))); } case 34: break; - case 9: - { return new Yytoken(Yytoken.TYPE_COLON,null); + case 8: + { return new Yytoken(Yytoken.TYPE_RIGHT_SQUARE,null); } case 35: break; - case 11: - { yybegin(YYINITIAL);return new Yytoken(Yytoken.TYPE_VALUE,sb.toString()); + case 19: + { sb.append('\r'); } case 36: break; - case 13: - { sb.append('"'); + case 15: + { sb.append('/'); } case 37: break; - case 4: - { return new Yytoken(Yytoken.TYPE_LEFT_BRACE,null); + case 10: + { return new Yytoken(Yytoken.TYPE_COLON,null); } case 38: break; - case 16: - { sb.append('\f'); + case 14: + { sb.append('"'); } case 39: break; - case 19: - { sb.append('\t'); + case 5: + { return new Yytoken(Yytoken.TYPE_LEFT_BRACE,null); } case 40: break; - case 6: - { return new Yytoken(Yytoken.TYPE_LEFT_SQUARE,null); + case 17: + { sb.append('\f'); } case 41: break; - case 23: - { int ch=Integer.parseInt(yytext().substring(2),16); - sb.append((char)ch); + case 24: + { try{ + int ch=Integer.parseInt(yytext().substring(2),16); + sb.append((char)ch); + } + catch(Exception e){ + throw new ParseException(yychar, ParseException.ERROR_UNEXPECTED_EXCEPTION, e); + } } case 42: break; - case 22: - { Boolean val=Boolean.valueOf(yytext()); return new Yytoken(Yytoken.TYPE_VALUE,val); + case 20: + { sb.append('\t'); } case 43: break; - case 17: - { sb.append('\n'); + case 7: + { return new Yytoken(Yytoken.TYPE_LEFT_SQUARE,null); } case 44: break; - case 8: - { return new Yytoken(Yytoken.TYPE_COMMA,null); + case 2: + { Long val=Long.valueOf(yytext()); return new Yytoken(Yytoken.TYPE_VALUE, val); } case 45: break; - case 2: - { + case 18: + { sb.append('\n'); } case 46: break; + case 9: + { return new Yytoken(Yytoken.TYPE_COMMA,null); + } + case 47: break; + case 3: + { + } + case 48: break; default: if (zzInput == YYEOF && zzStartRead == zzCurrentPos) { zzAtEOF = true; diff --git a/src/org/json/simple/parser/Yytoken.java b/src/org/json/simple/parser/Yytoken.java index 46bfe64..ff14e27 100644 --- a/src/org/json/simple/parser/Yytoken.java +++ b/src/org/json/simple/parser/Yytoken.java @@ -26,6 +26,33 @@ public class Yytoken { } public String toString(){ - return String.valueOf(type+"=>|"+value+"|"); + StringBuffer sb = new StringBuffer(); + switch(type){ + case TYPE_VALUE: + sb.append("VALUE(").append(value).append(")"); + break; + case TYPE_LEFT_BRACE: + sb.append("LEFT BRACE({)"); + break; + case TYPE_RIGHT_BRACE: + sb.append("RIGHT BRACE(})"); + break; + case TYPE_LEFT_SQUARE: + sb.append("LEFT SQUARE([)"); + break; + case TYPE_RIGHT_SQUARE: + sb.append("RIGHT SQUARE(])"); + break; + case TYPE_COMMA: + sb.append("COMMA(,)"); + break; + case TYPE_COLON: + sb.append("COLON(:)"); + break; + case TYPE_EOF: + sb.append("END OF FILE"); + break; + } + return sb.toString(); } } diff --git a/test/org/json/simple/Test.java b/test/org/json/simple/Test.java index 0b44f68..fb71044 100644 --- a/test/org/json/simple/Test.java +++ b/test/org/json/simple/Test.java @@ -4,10 +4,22 @@ */ package org.json.simple; +import java.io.IOException; +import java.io.StringWriter; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.LinkedList; import java.util.List; +import java.util.Map; import junit.framework.TestCase; +import org.json.simple.parser.ContainerFactory; +import org.json.simple.parser.ContentHandler; +import org.json.simple.parser.JSONParser; +import org.json.simple.parser.ParseException; /** * @author FangYidong @@ -46,6 +58,241 @@ public class Test extends TestCase{ obj=JSONValue.parse(s); assertEquals("hello\bworld\"abc\tdef\\ghi\rjkl\n123δΈ­",((List)obj).get(0).toString()); + JSONParser parser = new JSONParser(); + s="{\"name\":"; + try{ + obj = parser.parse(s); + } + catch(ParseException pe){ + assertEquals(ParseException.ERROR_UNEXPECTED_TOKEN, pe.getErrorType()); + assertEquals(8, pe.getPosition()); + } + + s="{\"name\":}"; + try{ + obj = parser.parse(s); + } + catch(ParseException pe){ + assertEquals(ParseException.ERROR_UNEXPECTED_TOKEN, pe.getErrorType()); + assertEquals(8, pe.getPosition()); + } + + + s="{\"name"; + try{ + obj = parser.parse(s); + } + catch(ParseException pe){ + assertEquals(ParseException.ERROR_UNEXPECTED_TOKEN, pe.getErrorType()); + assertEquals(6, pe.getPosition()); + } + + + s = "[[null, 123.45, \"a\\\tb c\"}, true]"; + try{ + parser.parse(s); + } + catch(ParseException pe){ + assertEquals(24, pe.getPosition()); + System.out.println("Error at character position: " + pe.getPosition()); + switch(pe.getErrorType()){ + case ParseException.ERROR_UNEXPECTED_TOKEN: + System.out.println("Unexpected token: " + pe.getUnexpectedObject()); + break; + case ParseException.ERROR_UNEXPECTED_CHAR: + System.out.println("Unexpected character: " + pe.getUnexpectedObject()); + break; + case ParseException.ERROR_UNEXPECTED_EXCEPTION: + ((Exception)pe.getUnexpectedObject()).printStackTrace(); + break; + } + } + + s = "{\"first\": 123, \"second\": [4, 5, 6], \"third\": 789}"; + ContainerFactory containerFactory = new ContainerFactory(){ + public List creatArrayContainer() { + return new LinkedList(); + } + + public Map createObjectContainer() { + return new LinkedHashMap(); + } + + }; + + try{ + Map json = (Map)parser.parse(s, containerFactory); + Iterator iter = json.entrySet().iterator(); + System.out.println("==iterate result=="); + while(iter.hasNext()){ + Map.Entry entry = (Map.Entry)iter.next(); + System.out.println(entry.getKey() + "=>" + entry.getValue()); + } + + System.out.println("==toJSONString()=="); + System.out.println(JSONValue.toJSONString(json)); + assertEquals("{\"first\":123,\"second\":[4,5,6],\"third\":789}", JSONValue.toJSONString(json)); + } + catch(ParseException pe){ + pe.printStackTrace(); + } + + s = "{\"first\": 123, \"second\": [{\"s1\":{\"s11\":\"v11\"}}, 4, 5, 6], \"third\": 789}"; + ContentHandler myHandler = new ContentHandler() { + + public boolean endArray() throws ParseException { + System.out.println("endArray()"); + return true; + } + + public void endJSON() throws ParseException { + System.out.println("endJSON()"); + } + + public boolean endObject() throws ParseException { + System.out.println("endObject()"); + return true; + } + + public boolean endObjectEntry() throws ParseException { + System.out.println("endObjectEntry()"); + return true; + } + + public boolean primitive(Object value) throws ParseException { + System.out.println("primitive(): " + value); + return true; + } + + public boolean startArray() throws ParseException { + System.out.println("startArray()"); + return true; + } + + public void startJSON() throws ParseException { + System.out.println("startJSON()"); + } + + public boolean startObject() throws ParseException { + System.out.println("startObject()"); + return true; + } + + public boolean startObjectEntry(String key) throws ParseException { + System.out.println("startObjectEntry(), key:" + key); + return true; + } + + }; + try{ + parser.parse(s, myHandler); + } + catch(ParseException pe){ + pe.printStackTrace(); + } + + class KeyFinder implements ContentHandler{ + private Object value; + private boolean found = false; + private boolean end = false; + private String key; + private String matchKey; + + public void setMatchKey(String matchKey){ + this.matchKey = matchKey; + } + + public Object getValue(){ + return value; + } + + public boolean isEnd(){ + return end; + } + + public void setFound(boolean found){ + this.found = found; + } + + public boolean isFound(){ + return found; + } + + public void startJSON() throws ParseException, IOException { + found = false; + end = false; + } + + public void endJSON() throws ParseException, IOException { + end = true; + } + + public boolean primitive(Object value) throws ParseException, IOException { + if(key != null){ + if(key.equals(matchKey)){ + found = true; + this.value = value; + key = null; + return false; + } + } + return true; + } + + public boolean startArray() throws ParseException, IOException { + return true; + } + + + public boolean startObject() throws ParseException, IOException { + return true; + } + + public boolean startObjectEntry(String key) throws ParseException, IOException { + this.key = key; + return true; + } + + public boolean endArray() throws ParseException, IOException { + return false; + } + + public boolean endObject() throws ParseException, IOException { + return true; + } + + public boolean endObjectEntry() throws ParseException, IOException { + return true; + } + }; + + s = "{\"first\": 123, \"second\": [{\"k1\":{\"id\":\"id1\"}}, 4, 5, 6, {\"id\": 123}], \"third\": 789, \"id\": null}"; + parser.reset(); + KeyFinder keyFinder = new KeyFinder(); + keyFinder.setMatchKey("id"); + int i = 0; + try{ + while(!keyFinder.isEnd()){ + parser.parse(s, keyFinder, true); + if(keyFinder.isFound()){ + i++; + keyFinder.setFound(false); + System.out.println("found id:"); + System.out.println(keyFinder.getValue()); + if(i == 1) + assertEquals("id1", keyFinder.getValue()); + if(i == 2){ + assertTrue(keyFinder.getValue() instanceof Number); + assertEquals("123", String.valueOf(keyFinder.getValue())); + } + if(i == 3) + assertTrue(null == keyFinder.getValue()); + } + } + } + catch(ParseException pe){ + pe.printStackTrace(); + } } public void testEncode() throws Exception{ @@ -78,5 +325,67 @@ public class Test extends TestCase{ System.out.println(array1); System.out.println(); assertEquals("[\"abc\\u0010a\\/\",123,222.123,true,{\"weight\":60.21,\"age\":27,\"name\":\"fang\",\"is_developer\":true}]",array1.toString()); - } + + List list = new ArrayList(); + list.add("abc\u0010a/"); + list.add(new Integer(123)); + list.add(new Double(222.123)); + list.add(new Boolean(true)); + list.add(null); + System.out.println("======list=========="); + System.out.println(JSONArray.toJSONString(list)); + System.out.println(); + assertEquals("[\"abc\\u0010a\\/\",123,222.123,true,null]",JSONArray.toJSONString(list)); + + Map map = new HashMap(); + map.put("name","fang"); + map.put("age",new Integer(27)); + map.put("is_developer",new Boolean(true)); + map.put("weight",new Double(60.21)); + map.put("array1",list); + System.out.println("======map with list==========="); + System.out.println(map); + System.out.println(); + assertEquals("{\"array1\":[\"abc\\u0010a\\/\",123,222.123,true,null],\"weight\":60.21,\"age\":27,\"name\":\"fang\",\"is_developer\":true}",JSONObject.toJSONString(map)); + + Map m1 = new LinkedHashMap(); + Map m2 = new HashMap(); + List l1 = new LinkedList(); + + m1.put("k11","v11"); + m1.put("k12","v12"); + m1.put("k13", "v13"); + m2.put("k21","v21"); + m2.put("k22","v22"); + m2.put("k23","v23"); + l1.add(m1); + l1.add(m2); + String jsonString = JSONValue.toJSONString(l1); + System.out.println(jsonString); + assertEquals("[{\"k11\":\"v11\",\"k12\":\"v12\",\"k13\":\"v13\"},{\"k22\":\"v22\",\"k21\":\"v21\",\"k23\":\"v23\"}]", jsonString); + + StringWriter out = new StringWriter(); + JSONValue.writeJSONString(l1, out); + jsonString = out.toString(); + System.out.println(jsonString); + assertEquals("[{\"k11\":\"v11\",\"k12\":\"v12\",\"k13\":\"v13\"},{\"k22\":\"v22\",\"k21\":\"v21\",\"k23\":\"v23\"}]", jsonString); + + List l2 = new LinkedList(); + Map m3 = new LinkedHashMap(); + m3.put("k31", "v3"); + m3.put("k32", new Double(123.45)); + m3.put("k33", new Boolean(false)); + m3.put("k34", null); + l2.add("vvv"); + l2.add("1.23456789123456789"); + l2.add(new Boolean(true)); + l2.add(null); + m3.put("k35", l2); + m1.put("k14", m3); + out = new StringWriter(); + JSONValue.writeJSONString(l1, out); + jsonString = out.toString(); + System.out.println(jsonString); + assertEquals("[{\"k11\":\"v11\",\"k12\":\"v12\",\"k13\":\"v13\",\"k14\":{\"k31\":\"v3\",\"k32\":123.45,\"k33\":false,\"k34\":null,\"k35\":[\"vvv\",\"1.23456789123456789\",true,null]}},{\"k22\":\"v22\",\"k21\":\"v21\",\"k23\":\"v23\"}]",jsonString); + } } diff --git a/test/org/json/simple/parser/YylexTest.java b/test/org/json/simple/parser/YylexTest.java index d0eaf40..ce31751 100644 --- a/test/org/json/simple/parser/YylexTest.java +++ b/test/org/json/simple/parser/YylexTest.java @@ -1,5 +1,6 @@ package org.json.simple.parser; +import java.io.IOException; import java.io.StringReader; import junit.framework.TestCase; @@ -38,13 +39,40 @@ public class YylexTest extends TestCase { System.out.println(s); in = new StringReader(s); lexer=new Yylex(in); - Error err=null; + ParseException err=null; try{ token=lexer.yylex(); } - catch(Error e){ + catch(ParseException e){ err=e; System.out.println("error:"+err); + assertEquals(ParseException.ERROR_UNEXPECTED_CHAR, e.getErrorType()); + assertEquals(0,e.getPosition()); + assertEquals(new Character('\b'),e.getUnexpectedObject()); + } + catch(IOException ie){ + throw ie; + } + assertTrue(err!=null); + + s="{a : b}"; + System.out.println(s); + in = new StringReader(s); + lexer=new Yylex(in); + err=null; + try{ + lexer.yylex(); + token=lexer.yylex(); + } + catch(ParseException e){ + err=e; + System.out.println("error:"+err); + assertEquals(ParseException.ERROR_UNEXPECTED_CHAR, e.getErrorType()); + assertEquals(new Character('a'),e.getUnexpectedObject()); + assertEquals(1,e.getPosition()); + } + catch(IOException ie){ + throw ie; } assertTrue(err!=null); }