net.sf.json.util.JSONUtils Maven / Gradle / Ivy
Show all versions of json-lib Show documentation
/*
* Copyright 2002-2007 the original author or authors.
*
* 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 net.sf.json.util;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import net.sf.ezmorph.MorphUtils;
import net.sf.ezmorph.MorpherRegistry;
import net.sf.ezmorph.bean.MorphDynaBean;
import net.sf.ezmorph.bean.MorphDynaClass;
import net.sf.json.JSON;
import net.sf.json.JSONArray;
import net.sf.json.JSONException;
import net.sf.json.JSONFunction;
import net.sf.json.JSONNull;
import net.sf.json.JSONObject;
import net.sf.json.JSONString;
import net.sf.json.JsonConfig;
import net.sf.json.regexp.RegexpMatcher;
import net.sf.json.regexp.RegexpUtils;
import org.apache.commons.beanutils.DynaBean;
/**
* Provides useful methods on java objects and JSON values.
*
* @author Andres Almiray
* @version 6
*/
public final class JSONUtils {
/** Constant for char " */
public static final String DOUBLE_QUOTE = "\"";
/** Constant for char ' */
public static final String SINGLE_QUOTE = "'";
private static RegexpMatcher FUNCTION_HEADER_MATCHER;
private static final String FUNCTION_HEADER_PATTERN = "^function[ ]?\\(.*\\)$";
private static RegexpMatcher FUNCTION_MACTHER;
private static RegexpMatcher FUNCTION_PARAMS_MATCHER;
private static final String FUNCTION_PARAMS_PATTERN = "^function[ ]?\\((.*?)\\)$";
private static final String FUNCTION_PATTERN = "^function[ ]?\\(.*\\)[ ]?\\{.*\\}$";
private static final MorpherRegistry morpherRegistry = new MorpherRegistry();
static{
FUNCTION_HEADER_MATCHER = RegexpUtils.getMatcher( FUNCTION_HEADER_PATTERN );
FUNCTION_PARAMS_MATCHER = RegexpUtils.getMatcher( FUNCTION_PARAMS_PATTERN );
FUNCTION_MACTHER = RegexpUtils.getMatcher( FUNCTION_PATTERN );
// register standard morphers
MorphUtils.registerStandardMorphers( morpherRegistry );
}
/**
* Transformes the string into a valid Java Identifier.
* The default strategy is JavaIdentifierTransformer.NOOP
*
* @throws JSONException if the string can not be transformed.
*/
public static String convertToJavaIdentifier( String key ) {
return convertToJavaIdentifier( key, new JsonConfig() );
}
/**
* Transformes the string into a valid Java Identifier.
* The default strategy is JavaIdentifierTransformer.NOOP
*
* @throws JSONException if the string can not be transformed.
*/
public static String convertToJavaIdentifier( String key, JsonConfig jsonConfig ) {
try{
return jsonConfig.getJavaIdentifierTransformer()
.transformToJavaIdentifier( key );
}catch( JSONException jsone ){
throw jsone;
}catch( Exception e ){
throw new JSONException( e );
}
}
/**
* Produce a string from a double. The string "null" will be returned if the
* number is not finite.
*
* @param d A double.
* @return A String.
*/
public static String doubleToString( double d ) {
if( Double.isInfinite( d ) || Double.isNaN( d ) ){
return "null";
}
// Shave off trailing zeros and decimal point, if possible.
String s = Double.toString( d );
if( s.indexOf( '.' ) > 0 && s.indexOf( 'e' ) < 0 && s.indexOf( 'E' ) < 0 ){
while( s.endsWith( "0" ) ){
s = s.substring( 0, s.length() - 1 );
}
if( s.endsWith( "." ) ){
s = s.substring( 0, s.length() - 1 );
}
}
return s;
}
/**
* Returns the params of a function literal.
*/
public static String getFunctionParams( String function ) {
return FUNCTION_PARAMS_MATCHER.getGroupIfMatches( function, 1 );
}
/**
* Returns the inner-most component type of an Array.
*/
public static Class getInnerComponentType( Class type ) {
if( !type.isArray() ){
return type;
}
return getInnerComponentType( type.getComponentType() );
}
/**
* Returns the singleton MorpherRegistry.
*/
public static MorpherRegistry getMorpherRegistry() {
return morpherRegistry;
}
/**
* Creates a Map with all the properties of the JSONObject.
*/
public static Map getProperties( JSONObject jsonObject ) {
Map properties = new HashMap();
for( Iterator keys = jsonObject.keys(); keys.hasNext(); ){
String key = (String) keys.next();
/*
* String parsedKey = key; if( !JSONUtils.isJavaIdentifier( parsedKey ) ){
* parsedKey = JSONUtils.convertToJavaIdentifier( key ); }
*/
properties.put( key, getTypeClass( jsonObject.get( key ) ) );
}
return properties;
}
/**
* Returns the JSON type.
* Values are Object, String, Boolean, Number(subclasses) & JSONFunction.
*/
public static Class getTypeClass( Object obj ) {
if( isNull( obj ) ){
return Object.class;
}else if( isArray( obj ) ){
return List.class;
}else if( isFunction( obj ) ){
return JSONFunction.class;
}else if( isBoolean( obj ) ){
return Boolean.class;
}else if( isNumber( obj ) ){
Number n = (Number) obj;
if( isInteger( n ) ){
return Integer.class;
}else if( isLong( n ) ){
return Long.class;
}else if( isFloat( n ) ){
return Float.class;
}else if( isBigInteger( n ) ){
return BigInteger.class;
}else if( isBigDecimal( n ) ){
return BigDecimal.class;
}else if( isDouble( n ) ){
return Double.class;
}else{
throw new JSONException( "Unsupported type" );
}
}else if( isString( obj ) ){
return String.class;
}else if( isObject( obj ) ){
return Object.class;
}else{
throw new JSONException( "Unsupported type" );
}
}
/**
* Returns the hashcode of value.
* If null it will return JSONNull.getInstance().hashCode().
* If value is JSON, JSONFunction or String, value.hashCode is returned,
* otherwise the value is transformed to a String an its hashcode is
* returned.
*/
public static int hashCode( Object value ) {
if( value == null ){
return JSONNull.getInstance()
.hashCode();
}else if( value instanceof JSON || value instanceof String || value instanceof JSONFunction ){
return value.hashCode();
}else{
return String.valueOf( value )
.hashCode();
}
}
/**
* Tests if a Class represents an array or Collection.
*/
public static boolean isArray( Class clazz ) {
return clazz != null
&& (clazz.isArray() || Collection.class.isAssignableFrom( clazz ) || (JSONArray.class.isAssignableFrom( clazz )));
}
/**
* Tests if obj is an array or Collection.
*/
public static boolean isArray( Object obj ) {
if( (obj != null && obj.getClass()
.isArray()) || (obj instanceof Collection) || (obj instanceof JSONArray) ){
return true;
}
return false;
}
/**
* Tests if Class represents a Boolean or primitive boolean
*/
public static boolean isBoolean( Class clazz ) {
return clazz != null
&& (Boolean.TYPE.isAssignableFrom( clazz ) || Boolean.class.isAssignableFrom( clazz ));
}
/**
* Tests if obj is a Boolean or primitive boolean
*/
public static boolean isBoolean( Object obj ) {
if( (obj instanceof Boolean) || (obj != null && obj.getClass() == Boolean.TYPE) ){
return true;
}
return false;
}
/**
* Tests if Class represents a primitive double or wrapper.
*/
public static boolean isDouble( Class clazz ) {
return clazz != null
&& (Double.TYPE.isAssignableFrom( clazz ) || Double.class.isAssignableFrom( clazz ));
}
/**
* Tests if obj is javaScript function.
* Obj must be a non-null String and match "^function[ ]?\\(.*\\)[
* ]?\\{.*\\}$"
*/
public static boolean isFunction( Object obj ) {
if( obj instanceof String ){
String str = (String) obj;
return FUNCTION_MACTHER.matches( str );
}
if( obj instanceof JSONFunction ){
return true;
}
return false;
}
/**
* Tests if obj is javaScript function header.
* Obj must be a non-null String and match "^function[ ]?\\(.*\\)$"
*/
public static boolean isFunctionHeader( Object obj ) {
if( obj instanceof String ){
String str = (String) obj;
return FUNCTION_HEADER_MATCHER.matches( str );
}
return false;
}
/**
* Returns trus if str represents a valid Java identifier.
*/
public static boolean isJavaIdentifier( String str ) {
if( str.length() == 0 || !Character.isJavaIdentifierStart( str.charAt( 0 ) ) ){
return false;
}
for( int i = 1; i < str.length(); i++ ){
if( !Character.isJavaIdentifierPart( str.charAt( i ) ) ){
return false;
}
}
return true;
}
/**
* Tests if the obj is a javaScript null.
*/
public static boolean isNull( Object obj ) {
if( obj instanceof JSONObject ){
return ((JSONObject) obj).isNullObject();
}
return JSONNull.getInstance()
.equals( obj );
}
/**
* Tests if Class represents a primitive number or wrapper.
*/
public static boolean isNumber( Class clazz ) {
return clazz != null
&& (Byte.TYPE.isAssignableFrom( clazz ) || Short.TYPE.isAssignableFrom( clazz )
|| Integer.TYPE.isAssignableFrom( clazz ) || Long.TYPE.isAssignableFrom( clazz )
|| Float.TYPE.isAssignableFrom( clazz ) || Double.TYPE.isAssignableFrom( clazz ) || Number.class.isAssignableFrom( clazz ));
}
/**
* Tests if obj is a primitive number or wrapper.
*/
public static boolean isNumber( Object obj ) {
if( (obj != null && obj.getClass() == Byte.TYPE)
|| (obj != null && obj.getClass() == Short.TYPE)
|| (obj != null && obj.getClass() == Integer.TYPE)
|| (obj != null && obj.getClass() == Long.TYPE)
|| (obj != null && obj.getClass() == Float.TYPE)
|| (obj != null && obj.getClass() == Double.TYPE) ){
return true;
}
return obj instanceof Number;
}
/**
* Tests if obj is not a boolean, number, string or array.
*/
public static boolean isObject( Object obj ) {
return !isNumber( obj ) && !isString( obj ) && !isBoolean( obj ) && !isArray( obj )
&& !isFunction( obj ) || isNull( obj );
}
/**
* Tests if Class represents a String or a char
*/
public static boolean isString( Class clazz ) {
return clazz != null
&& (String.class.isAssignableFrom( clazz ) || (Character.TYPE.isAssignableFrom( clazz ) || Character.class.isAssignableFrom( clazz )));
}
/**
* Tests if obj is a String or a char
*/
public static boolean isString( Object obj ) {
if( (obj instanceof String)
|| (obj instanceof Character)
|| (obj != null && (obj.getClass() == Character.TYPE || String.class.isAssignableFrom( obj.getClass() ))) ){
return true;
}
return false;
}
/**
* Tests if the String possibly represents a valid JSON String.
* Valid JSON strings are:
*
* - "null"
* - starts with "[" and ends with "]"
* - starts with "{" and ends with "}"
*
*/
public static boolean mayBeJSON( String string ) {
return string != null
&& ("null".equalsIgnoreCase( string )
|| (string.startsWith( "[" ) && string.endsWith( "]" )) || (string.startsWith( "{" ) && string.endsWith( "}" )));
}
/**
* Creates a new MorphDynaBean from a JSONObject. The MorphDynaBean will have
* all the properties of the original JSONObject with the most accurate type.
* Values of properties are not copied.
*/
public static DynaBean newDynaBean( JSONObject jsonObject ) {
return newDynaBean( jsonObject, new JsonConfig() );
}
/**
* Creates a new MorphDynaBean from a JSONObject. The MorphDynaBean will have
* all the properties of the original JSONObject with the most accurate type.
* Values of properties are not copied.
*/
public static DynaBean newDynaBean( JSONObject jsonObject, JsonConfig jsonConfig ) {
Map props = getProperties( jsonObject );
for( Iterator entries = props.entrySet()
.iterator(); entries.hasNext(); ){
Map.Entry entry = (Map.Entry) entries.next();
String key = (String) entry.getKey();
if( !JSONUtils.isJavaIdentifier( key ) ){
String parsedKey = JSONUtils.convertToJavaIdentifier( key, jsonConfig );
if( parsedKey.compareTo( key ) != 0 ){
props.put( parsedKey, props.remove( key ) );
}
}
}
MorphDynaClass dynaClass = new MorphDynaClass( props );
MorphDynaBean dynaBean = null;
try{
dynaBean = (MorphDynaBean) dynaClass.newInstance();
dynaBean.setDynaBeanClass( dynaClass );
}catch( Exception e ){
throw new JSONException( e );
}
return dynaBean;
}
/**
* Produce a string from a Number.
*
* @param n A Number
* @return A String.
* @throws JSONException If n is a non-finite number.
*/
public static String numberToString( Number n ) {
if( n == null ){
throw new JSONException( "Null pointer" );
}
testValidity( n );
// Shave off trailing zeros and decimal point, if possible.
String s = n.toString();
if( s.indexOf( '.' ) > 0 && s.indexOf( 'e' ) < 0 && s.indexOf( 'E' ) < 0 ){
while( s.endsWith( "0" ) ){
s = s.substring( 0, s.length() - 1 );
}
if( s.endsWith( "." ) ){
s = s.substring( 0, s.length() - 1 );
}
}
return s;
}
/**
* Produce a string in double quotes with backslash sequences in all the
* right places. A backslash will be inserted within , allowing JSON text
* to be delivered in HTML. In JSON text, a string cannot contain a control
* character or an unescaped quote or backslash.
* CAUTION: if string
represents a
* javascript function, translation of characters will not take place. This
* will produce a non-conformant JSON text.
*
* @param string A String
* @return A String correctly formatted for insertion in a JSON text.
*/
public static String quote( String string ) {
if( isFunction( string ) ){
return string;
}
if( string == null || string.length() == 0 ){
return "\"\"";
}
char b;
char c = 0;
int i;
int len = string.length();
StringBuilder sb = new StringBuilder(len + 4);
String t;
sb.append( '"' );
for( i = 0; i < len; i += 1 ){
b = c;
c = string.charAt( i );
switch( c ){
case '\\':
case '"':
sb.append( '\\' );
sb.append( c );
break;
case '/':
if( b == '<' ){
sb.append( '\\' );
}
sb.append( c );
break;
case '\b':
sb.append( "\\b" );
break;
case '\t':
sb.append( "\\t" );
break;
case '\n':
sb.append( "\\n" );
break;
case '\f':
sb.append( "\\f" );
break;
case '\r':
sb.append( "\\r" );
break;
default:
if( c < ' ' ){
t = "000" + Integer.toHexString( c );
sb.append( "\\u" )
.append( t.substring( t.length() - 4 ) );
}else{
sb.append( c );
}
}
}
sb.append( '"' );
return sb.toString();
}
/**
* Minimal escape form.
*/
public static String quoteCanonical(String s) {
if (s == null || s.length() == 0) {
return "\"\"";
}
int len = s.length();
StringBuilder sb = new StringBuilder(len + 4);
sb.append('"');
for (int i = 0; i < len; i += 1) {
char c = s.charAt(i);
switch (c) {
case '\\':
case '"':
sb.append('\\');
sb.append(c);
break;
default:
if (c < ' ') {
String t = "000" + Integer.toHexString(c);
sb.append("\\u")
.append(t.substring(t.length() - 4));
} else {
sb.append(c);
}
}
}
sb.append('"');
return sb.toString();
}
/**
* Strips any single-quotes or double-quotes from boths sides of the string.
*/
public static String stripQuotes( String input ) {
if( input.length() < 2 ){
return input;
}else if( input.startsWith( SINGLE_QUOTE ) && input.endsWith( SINGLE_QUOTE ) ){
return input.substring( 1, input.length() - 1 );
}else if( input.startsWith( DOUBLE_QUOTE ) && input.endsWith( DOUBLE_QUOTE ) ){
return input.substring( 1, input.length() - 1 );
}else{
return input;
}
}
/**
* Throw an exception if the object is an NaN or infinite number.
*
* @param o The object to test.
* @throws JSONException If o is a non-finite number.
*/
public static void testValidity( Object o ) {
if( o != null ){
if( o instanceof Double ){
if( ((Double) o).isInfinite() || ((Double) o).isNaN() ){
throw new JSONException( "JSON does not allow non-finite numbers" );
}
}else if( o instanceof Float ){
if( ((Float) o).isInfinite() || ((Float) o).isNaN() ){
throw new JSONException( "JSON does not allow non-finite numbers." );
}
}else if( o instanceof BigDecimal || o instanceof BigInteger ){
// ok
return;
}
}
}
/**
* Transforms a Number into a valid javascript number.
* Float gets promoted to Double.
* Byte and Short get promoted to Integer.
* Long gets downgraded to Integer if possible.
*/
public static Number transformNumber( Number input ) {
if( input instanceof Float ){
return new Double( input.doubleValue() );
}else if( input instanceof Short ){
return new Integer( input.intValue() );
}else if( input instanceof Byte ){
return new Integer( input.intValue() );
}else if( input instanceof Long ){
Long max = new Long( Integer.MAX_VALUE );
if( input.longValue() <= max.longValue() && input.longValue() >= Integer.MIN_VALUE ){
return new Integer( input.intValue() );
}
}
return input;
}
/**
* Make a JSON text of an Object value. If the object has an
* value.toJSONString() method, then that method will be used to produce the
* JSON text. The method is required to produce a strictly conforming text.
* If the object does not contain a toJSONString method (which is the most
* common case), then a text will be produced by the rules.
*
* Warning: This method assumes that the data structure is acyclical.
*
* @param value The value to be serialized.
* @return a printable, displayable, transmittable representation of the
* object, beginning with {
(left brace)
* and ending with }
(right brace).
* @throws JSONException If the value is or contains an invalid number.
*/
public static String valueToString( Object value ) {
if( value == null || isNull( value ) ){
return "null";
}
if( value instanceof JSONFunction ){
return value.toString();
}
if( value instanceof JSONString ){
return ((JSONString) value).toJSONString();
}
if( value instanceof Number ){
return numberToString( (Number) value );
}
if( value instanceof Boolean || value instanceof JSONObject || value instanceof JSONArray ){
return value.toString();
}
return quote( value.toString() );
}
public static String valueToCanonicalString( Object value ) {
if( value == null || isNull( value ) ){
return "null";
}
if( value instanceof JSONFunction ){
return value.toString(); // there's really no canonical form for functions
}
if( value instanceof JSONString ){
return ((JSONString) value).toJSONString();
}
if( value instanceof Number ){
return numberToString( (Number) value ).toLowerCase();
}
if( value instanceof Boolean || value instanceof JSONObject || value instanceof JSONArray ){
return value.toString();
}
return quoteCanonical( value.toString() );
}
/**
* Make a prettyprinted JSON text of an object value.
*
* Warning: This method assumes that the data structure is acyclical.
*
* @param value The value to be serialized.
* @param indentFactor The number of spaces to add to each level of
* indentation.
* @param indent The indentation of the top level.
* @return a printable, displayable, transmittable representation of the
* object, beginning with {
(left brace)
* and ending with }
(right brace).
* @throws JSONException If the object contains an invalid number.
*/
public static String valueToString( Object value, int indentFactor, int indent ) {
if( value == null || isNull( value ) ){
return "null";
}
if( value instanceof JSONFunction ){
return value.toString();
}
if( value instanceof JSONString ){
return ((JSONString) value).toJSONString();
}
if( value instanceof Number ){
return numberToString( (Number) value );
}
if( value instanceof Boolean ){
return value.toString();
}
if( value instanceof JSONObject ){
return ((JSONObject) value).toString( indentFactor, indent );
}
if( value instanceof JSONArray ){
return ((JSONArray) value).toString( indentFactor, indent );
}
return quote( value.toString() );
}
/**
* Finds out if n represents a BigInteger
*
* @return true if n is instanceOf BigInteger or the literal value can be
* evaluated as a BigInteger
*/
private static boolean isBigDecimal( Number n ) {
if( n instanceof BigDecimal ){
return true;
}
try{
new BigDecimal( String.valueOf( n ) );
return true;
}catch( NumberFormatException e ){
return false;
}
}
/**
* Finds out if n represents a BigInteger
*
* @return true if n is instanceOf BigInteger or the literal value can be
* evaluated as a BigInteger
*/
private static boolean isBigInteger( Number n ) {
if( n instanceof BigInteger ){
return true;
}
try{
new BigInteger( String.valueOf( n ) );
return true;
}catch( NumberFormatException e ){
return false;
}
}
/**
* Finds out if n represents a Double.
*
* @return true if n is instanceOf Double or the literal value can be
* evaluated as a Double.
*/
private static boolean isDouble( Number n ) {
if( n instanceof Double ){
return true;
}
try{
double d = Double.parseDouble( String.valueOf( n ) );
return !Double.isInfinite( d );
}catch( NumberFormatException e ){
return false;
}
}
/**
* Finds out if n represents a Float.
*
* @return true if n is instanceOf Float or the literal value can be
* evaluated as a Float.
*/
private static boolean isFloat( Number n ) {
if( n instanceof Float ){
return true;
}
try{
float f = Float.parseFloat( String.valueOf( n ) );
return !Float.isInfinite( f );
}catch( NumberFormatException e ){
return false;
}
}
/**
* Finds out if n represents an Integer.
*
* @return true if n is instanceOf Integer or the literal value can be
* evaluated as an Integer.
*/
private static boolean isInteger( Number n ) {
if( n instanceof Integer ){
return true;
}
try{
Integer.parseInt( String.valueOf( n ) );
return true;
}catch( NumberFormatException e ){
return false;
}
}
/**
* Finds out if n represents a Long.
*
* @return true if n is instanceOf Long or the literal value can be evaluated
* as a Long.
*/
private static boolean isLong( Number n ) {
if( n instanceof Long ){
return true;
}
try{
Long.parseLong( String.valueOf( n ) );
return true;
}catch( NumberFormatException e ){
return false;
}
}
private JSONUtils() {
super();
}
}