All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.mortbay.util.ajax.JSON Maven / Gradle / Ivy

There is a newer version: 7.0.0.pre5
Show newest version
//========================================================================
//Copyright 2004-2008 Mort Bay Consulting Pty. Ltd.
//------------------------------------------------------------------------
//Licensed under the Apache License, Version 2.0 (the "License");
//you may not use this file except in compliance with the License.
//You may obtain a copy of the License at 
//http://www.apache.org/licenses/LICENSE-2.0
//Unless required by applicable law or agreed to in writing, software
//distributed under the License is distributed on an "AS IS" BASIS,
//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//See the License for the specific language governing permissions and
//limitations under the License.
//========================================================================

package org.mortbay.util.ajax;

import java.io.Externalizable;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import org.mortbay.log.Log;
import org.mortbay.util.IO;
import org.mortbay.util.Loader;
import org.mortbay.util.QuotedStringTokenizer;
import org.mortbay.util.TypeUtil;

/** JSON Parser and Generator.
 * 
 * 

This class provides some static methods to convert POJOs to and from JSON * notation. The mapping from JSON to java is:

 *   object ==> Map
 *   array  ==> Object[]
 *   number ==> Double or Long
 *   string ==> String
 *   null   ==> null
 *   bool   ==> Boolean
 * 
*

* The java to JSON mapping is:

 *   String --> string
 *   Number --> number
 *   Map    --> object
 *   List   --> array
 *   Array  --> array
 *   null   --> null
 *   Boolean--> boolean
 *   Object --> string (dubious!)
 * 
*

* The interface {@link JSON.Convertible} may be implemented by classes that wish to externalize and * initialize specific fields to and from JSON objects. Only directed acyclic graphs of objects are supported. *

*

* The interface {@link JSON.Generator} may be implemented by classes that know how to render themselves as JSON and * the {@link #toString(Object)} method will use {@link JSON.Generator#addJSON(StringBuffer)} to generate the JSON. * The class {@link JSON.Literal} may be used to hold pre-gnerated JSON object. *

* The interface {@link Convertor} may be implemented to provide static convertors for objects that may be registered * with {@link #registerConvertor(Class, org.mortbay.util.ajax.JSON.Convertor)}. These convertors are looked up by class, interface and * super class by {@link #getConvertor(Class)}. *

* @author gregw * */ public class JSON { private static JSON __default = new JSON(); private Map _convertors=Collections.synchronizedMap(new HashMap()); private int _stringBufferSize=256; public JSON() { } /* ------------------------------------------------------------ */ /** * @return the initial stringBuffer size to use when creating JSON strings (default 256) */ public int getStringBufferSize() { return _stringBufferSize; } /* ------------------------------------------------------------ */ /** * @param stringBufferSize the initial stringBuffer size to use when creating JSON strings (default 256) */ public void setStringBufferSize(int stringBufferSize) { _stringBufferSize=stringBufferSize; } /** * Register a {@link Convertor} for a class or interface. * @param forClass The class or interface that the convertor applies to * @param convertor the convertor */ public static void registerConvertor(Class forClass, Convertor convertor) { __default.addConvertor(forClass,convertor); } public static JSON getDefault() { return __default; } public static void setDefault(JSON json) { __default=json; } public static String toString(Object object) { StringBuffer buffer=new StringBuffer(__default.getStringBufferSize()); synchronized (buffer) { __default.append(buffer,object); return buffer.toString(); } } public static String toString(Map object) { StringBuffer buffer=new StringBuffer(__default.getStringBufferSize()); synchronized (buffer) { __default.appendMap(buffer,object); return buffer.toString(); } } public static String toString(Object[] array) { StringBuffer buffer=new StringBuffer(__default.getStringBufferSize()); synchronized (buffer) { __default.appendArray(buffer,array); return buffer.toString(); } } /** * @param s String containing JSON object or array. * @return A Map, Object array or primitive array parsed from the JSON. */ public static Object parse(String s) { return __default.parse(new StringSource(s),false); } /** * @param s String containing JSON object or array. * @param stripOuterComment If true, an outer comment around the JSON is ignored. * @return A Map, Object array or primitive array parsed from the JSON. */ public static Object parse(String s, boolean stripOuterComment) { return __default.parse(new StringSource(s),stripOuterComment); } /** * @param in Reader containing JSON object or array. * @return A Map, Object array or primitive array parsed from the JSON. */ public static Object parse(Reader in) throws IOException { return __default.parse(new ReaderSource(in),false); } /** * @param s Stream containing JSON object or array. * @param stripOuterComment If true, an outer comment around the JSON is ignored. * @return A Map, Object array or primitive array parsed from the JSON. */ public static Object parse(Reader in, boolean stripOuterComment) throws IOException { return __default.parse(new ReaderSource(in),stripOuterComment); } /** * @deprecated use {@link #parse(Reader)} * @param in Reader containing JSON object or array. * @return A Map, Object array or primitive array parsed from the JSON. */ public static Object parse(InputStream in) throws IOException { return __default.parse(new StringSource(IO.toString(in)),false); } /** * @deprecated use {@link #parse(Reader, boolean)} * @param s Stream containing JSON object or array. * @param stripOuterComment If true, an outer comment around the JSON is ignored. * @return A Map, Object array or primitive array parsed from the JSON. */ public static Object parse(InputStream in, boolean stripOuterComment) throws IOException { return __default.parse(new StringSource(IO.toString(in)),stripOuterComment); } /* ------------------------------------------------------------ */ /** Convert Object to JSON * @param object The object to convert * @return The JSON String */ public String toJSON(Object object) { StringBuffer buffer=new StringBuffer(getStringBufferSize()); synchronized (buffer) { append(buffer,object); return buffer.toString(); } } /* ------------------------------------------------------------ */ /** Convert JSON to Object * @param json The json to convert * @return The object */ public Object fromJSON(String json) { Source source = new StringSource(json); return parse(source); } /** * Append object as JSON to string buffer. * @param buffer * @param object */ public void append(StringBuffer buffer, Object object) { if (object==null) buffer.append("null"); else if (object instanceof Convertible) appendJSON(buffer,(Convertible)object); else if (object instanceof Generator) appendJSON(buffer,(Generator)object); else if (object instanceof Map) appendMap(buffer,(Map)object); else if (object instanceof Collection) appendArray(buffer,(Collection)object); else if (object.getClass().isArray()) appendArray(buffer,object); else if (object instanceof Number) appendNumber(buffer,(Number)object); else if (object instanceof Boolean) appendBoolean(buffer,(Boolean)object); else if (object instanceof String) appendString(buffer,(String)object); else { Convertor convertor=getConvertor(object.getClass()); if (convertor!=null) appendJSON(buffer,convertor,object); else appendString(buffer,object.toString()); } } public void appendNull(StringBuffer buffer) { buffer.append("null"); } public void appendJSON(final StringBuffer buffer, final Convertor convertor, final Object object) { appendJSON(buffer,new Convertible() { public void fromJSON(Map object) { } public void toJSON(Output out) { convertor.toJSON(object,out); } }); } public void appendJSON(final StringBuffer buffer, Convertible converter) { final char[] c= { '{' }; converter.toJSON(new Output() { public void add(Object obj) { if (c[0]==0) throw new IllegalStateException(); append(buffer,obj); c[0]=0; } public void addClass(Class type) { if (c[0]==0) throw new IllegalStateException(); buffer.append(c); buffer.append("\"class\":"); append(buffer,type.getName()); c[0]=','; } public void add(String name, Object value) { if (c[0]==0) throw new IllegalStateException(); buffer.append(c); QuotedStringTokenizer.quote(buffer,name); buffer.append(':'); append(buffer,value); c[0]=','; } public void add(String name, double value) { if (c[0]==0) throw new IllegalStateException(); buffer.append(c); QuotedStringTokenizer.quote(buffer,name); buffer.append(':'); appendNumber(buffer,new Double(value)); c[0]=','; } public void add(String name, long value) { if (c[0]==0) throw new IllegalStateException(); buffer.append(c); QuotedStringTokenizer.quote(buffer,name); buffer.append(':'); appendNumber(buffer,TypeUtil.newLong(value)); c[0]=','; } public void add(String name, boolean value) { if (c[0]==0) throw new IllegalStateException(); buffer.append(c); QuotedStringTokenizer.quote(buffer,name); buffer.append(':'); appendBoolean(buffer,value?Boolean.TRUE:Boolean.FALSE); c[0]=','; } }); if (c[0]=='{') buffer.append("{}"); else if (c[0]!=0) buffer.append("}"); } public void appendJSON(StringBuffer buffer, Generator generator) { generator.addJSON(buffer); } public void appendMap(StringBuffer buffer, Map object) { if (object==null) { appendNull(buffer); return; } buffer.append('{'); Iterator iter=object.entrySet().iterator(); while (iter.hasNext()) { Map.Entry entry=(Map.Entry)iter.next(); QuotedStringTokenizer.quote(buffer,entry.getKey().toString()); buffer.append(':'); append(buffer,entry.getValue()); if (iter.hasNext()) buffer.append(','); } buffer.append('}'); } public void appendArray(StringBuffer buffer, Collection collection) { if (collection==null) { appendNull(buffer); return; } buffer.append('['); Iterator iter=collection.iterator(); boolean first=true; while (iter.hasNext()) { if (!first) buffer.append(','); first=false; append(buffer,iter.next()); } buffer.append(']'); } public void appendArray(StringBuffer buffer, Object array) { if (array==null) { appendNull(buffer); return; } buffer.append('['); int length=Array.getLength(array); for (int i=0; i * If no match is found for the class, then the interfaces for the class are tried. If still no * match is found, then the super class and it's interfaces are tried recursively. * @param forClass The class * @return a {@link Convertor} or null if none were found. */ protected Convertor getConvertor(Class forClass) { Class cls=forClass; Convertor convertor=(Convertor)_convertors.get(cls.getName()); if (convertor==null && this!=__default) convertor=__default.getConvertor(cls); while (convertor==null&&cls!=null&&cls!=Object.class) { Class[] ifs=cls.getInterfaces(); int i=0; while (convertor==null&&ifs!=null&&i1) { switch (c) { case '*': comment_state=3; break; case '/': if (comment_state==3) { comment_state=0; if (strip_state==2) return o; } else comment_state=2; break; default: comment_state=2; } } // handle // comment else if (comment_state<0) { switch (c) { case '\r': case '\n': comment_state=0; default: break; } } // handle unknown else { if (!Character.isWhitespace(c)) { if (c=='/') comment_state=1; else if (c=='*') comment_state=3; else if (o==null) { o=parse(source); continue; } } } source.next(); } return o; } public Object parse(Source source) { int comment_state=0; // 0=no comment, 1="/", 2="/*", 3="/* *" -1="//" while (source.hasNext()) { char c=source.peek(); // handle // or /* comment if (comment_state==1) { switch (c) { case '/': comment_state=-1; break; case '*': comment_state=2; } } // handle /* */ comment else if (comment_state>1) { switch (c) { case '*': comment_state=3; break; case '/': if (comment_state==3) comment_state=0; else comment_state=2; break; default: comment_state=2; } } // handle // comment else if (comment_state<0) { switch (c) { case '\r': case '\n': comment_state=0; break; default: break; } } // handle unknown else { switch (c) { case '{': return parseObject(source); case '[': return parseArray(source); case '"': return parseString(source); case '-': return parseNumber(source); case 'n': complete("null",source); return null; case 't': complete("true",source); return Boolean.TRUE; case 'f': complete("false",source); return Boolean.FALSE; case 'u': complete("undefined",source); return null; case '/': comment_state=1; break; default: if (Character.isDigit(c)) return parseNumber(source); else if (Character.isWhitespace(c)) break; return handleUnknown(source, c); } } source.next(); } return null; } protected Object handleUnknown(Source source, char c) { throw new IllegalStateException("unknown char '"+c+"'("+(int)c+") in "+source); } protected Object parseObject(Source source) { if (source.next()!='{') throw new IllegalStateException(); Map map=newMap(); char next=seekTo("\"}",source); while (source.hasNext()) { if (next=='}') { source.next(); break; } String name=parseString(source); seekTo(':',source); source.next(); Object value=contextFor(name).parse(source); map.put(name,value); seekTo(",}",source); next=source.next(); if (next=='}') break; else next=seekTo("\"}",source); } String classname=(String)map.get("class"); if (classname!=null) { try { Class c=Loader.loadClass(JSON.class,classname); return convertTo(c,map); } catch (ClassNotFoundException e) { e.printStackTrace(); } } return map; } protected Object parseArray(Source source) { if (source.next()!='[') throw new IllegalStateException(); int size=0; ArrayList list=null; Object item=null; boolean coma=true; while (source.hasNext()) { char c=source.peek(); switch (c) { case ']': source.next(); switch(size) { case 0: return newArray(0); case 1: Object array = newArray(1); Array.set(array,0,item); return array; default: return list.toArray(newArray(list.size())); } case ',': if (coma) throw new IllegalStateException(); coma=true; source.next(); break; default: if (Character.isWhitespace(c)) source.next(); else { coma=false; if (size++==0) item=contextForArray().parse(source); else if (list==null) { list=new ArrayList(); list.add(item); item=contextForArray().parse(source); list.add(item); item=null; } else { item=contextForArray().parse(source); list.add(item); item=null; } } } } throw new IllegalStateException("unexpected end of array"); } protected String parseString(Source source) { if (source.next()!='"') throw new IllegalStateException(); boolean escape=false; StringBuffer b=null; final char[] scratch=source.scratchBuffer(); if (scratch!=null) { int i=0; while (source.hasNext()) { if(i>=scratch.length) { // we have filled the scratch buffer, so we must // use the StringBuffer for a large string b=new StringBuffer(scratch.length*2); b.append(scratch,0,i); break; } char c=source.next(); if (escape) { escape=false; switch (c) { case '"': scratch[i++]='"'; break; case '\\': scratch[i++]='\\'; break; case '/': scratch[i++]='/'; break; case 'b': scratch[i++]='\b'; break; case 'f': scratch[i++]='\f'; break; case 'n': scratch[i++]='\n'; break; case 'r': scratch[i++]='\r'; break; case 't': scratch[i++]='\t'; break; case 'u': char uc=(char)((TypeUtil.convertHexDigit((byte)source.next())<<12)+ (TypeUtil.convertHexDigit((byte)source.next())<<8)+ (TypeUtil.convertHexDigit((byte)source.next())<<4)+ (TypeUtil.convertHexDigit((byte)source.next()))); scratch[i++]=uc; break; default: scratch[i++]=c; } } else if (c=='\\') { escape=true; continue; } else if (c=='\"') { // Return string that fits within scratch buffer return toString(scratch,0,i); } else scratch[i++]=c; } // Missing end quote, but return string anyway ? if (b==null) return toString(scratch,0,i); } else b=new StringBuffer(getStringBufferSize()); // parse large string into string buffer synchronized (b) { while (source.hasNext()) { char c=source.next(); if (escape) { escape=false; switch (c) { case '"': b.append('"'); break; case '\\': b.append('\\'); break; case '/': b.append('/'); break; case 'b': b.append('\b'); break; case 'f': b.append('\f'); break; case 'n': b.append('\n'); break; case 'r': b.append('\r'); break; case 't': b.append('\t'); break; case 'u': char uc=(char)((TypeUtil.convertHexDigit((byte)source.next())<<12)+ (TypeUtil.convertHexDigit((byte)source.next())<<8)+ (TypeUtil.convertHexDigit((byte)source.next())<<4)+ (TypeUtil.convertHexDigit((byte)source.next()))); b.append(uc); break; default: b.append(c); } } else if (c=='\\') { escape=true; continue; } else if (c=='\"') break; else b.append(c); } return b.toString(); } } public Number parseNumber(Source source) { boolean minus=false; long number=0; StringBuffer buffer=null; longLoop: while (source.hasNext()) { char c=source.peek(); switch (c) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': number=number*10+(c-'0'); source.next(); break; case '-': case '+': if (number!=0) throw new IllegalStateException("bad number"); minus=true; source.next(); break; case '.': case 'e': case 'E': buffer=new StringBuffer(16); if(minus) buffer.append('-'); buffer.append(number); buffer.append(c); source.next(); break longLoop; default: break longLoop; } } if (buffer==null) return TypeUtil.newLong(minus?-1*number:number); synchronized (buffer) { doubleLoop: while (source.hasNext()) { char c=source.peek(); switch (c) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': case '-': case '.': case '+': case 'e': case 'E': buffer.append(c); source.next(); break; default: break doubleLoop; } } return new Double(buffer.toString()); } } protected void seekTo(char seek, Source source) { while (source.hasNext()) { char c=source.peek(); if (c==seek) return; if (!Character.isWhitespace(c)) throw new IllegalStateException("Unexpected '"+c+" while seeking '"+seek+"'"); source.next(); } throw new IllegalStateException("Expected '"+seek+"'"); } protected char seekTo(String seek, Source source) { while (source.hasNext()) { char c=source.peek(); if (seek.indexOf(c)>=0) { return c; } if (!Character.isWhitespace(c)) throw new IllegalStateException("Unexpected '"+c+"' while seeking one of '"+seek+"'"); source.next(); } throw new IllegalStateException("Expected one of '"+seek+"'"); } protected static void complete(String seek, Source source) { int i=0; while (source.hasNext()&&i * A JSON.Convertible object may be written to a JSONObject * or initialized from a Map of field names to values. *

* If the JSON is to be convertible back to an Object, then * the method {@link Output#addClass(Class)} must be called from within toJSON() * */ public interface Convertible { public void toJSON(Output out); public void fromJSON(Map object); } /* ------------------------------------------------------------ */ /** Static JSON Convertor. *

* may be implemented to provide static convertors for objects that may be registered * with {@link JSON#registerConvertor(Class, org.mortbay.util.ajax.JSON.Convertor). * These convertors are looked up by class, interface and * super class by {@link JSON#getConvertor(Class)}. Convertors should be used when the * classes to be converted cannot implement {@link Convertible} or {@link Generator}. */ public interface Convertor { public void toJSON(Object obj, Output out); public Object fromJSON(Map object); } /* ------------------------------------------------------------ */ /** JSON Generator. * A class that can add it's JSON representation directly to a StringBuffer. * This is useful for object instances that are frequently converted and wish to * avoid multiple Conversions */ public interface Generator { public void addJSON(StringBuffer buffer); } /* ------------------------------------------------------------ */ /** A Literal JSON generator * A utility instance of {@link JSON.Generator} that holds a pre-generated string on JSON text. */ public static class Literal implements Generator { private String _json; /* ------------------------------------------------------------ */ /** Construct a literal JSON instance for use by {@link JSON#toString(Object)}. * If {@link Log#isDebugEnabled()} is true, the JSON will be parsed to check validity * @param json A literal JSON string. */ public Literal(String json) { if (Log.isDebugEnabled()) parse(json); _json=json; } public String toString() { return _json; } public void addJSON(StringBuffer buffer) { buffer.append(_json); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy