panda.bind.json.JsonDeserializer Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of panda-core Show documentation
Show all versions of panda-core Show documentation
Panda Core is the core module of Panda Framework, it contains commonly used utility classes similar to apache-commons.
package panda.bind.json;
import java.io.Reader;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
import panda.bean.BeanHandler;
import panda.bind.AbstractDeserializer;
import panda.lang.Numbers;
import panda.lang.reflect.Types;
/**
*
*
*/
public class JsonDeserializer extends AbstractDeserializer {
private Class> defaultJsonObjectType = JsonObject.class;
private Class> defaultJsonArrayType = JsonArray.class;
private JsonTokener tokener;
public JsonDeserializer() {
}
/**
* @return the defaultJsonObjectType
*/
public Class> getDefaultJsonObjectType() {
return defaultJsonObjectType;
}
/**
* @param defaultJsonObjectType the defaultJsonObjectType to set
*/
public void setDefaultJsonObjectType(Class> defaultJsonObjectType) {
this.defaultJsonObjectType = defaultJsonObjectType;
}
/**
* @return the defaultJsonArrayType
*/
public Class> getDefaultJsonArrayType() {
return defaultJsonArrayType;
}
/**
* @param defaultJsonArrayType the defaultJsonArrayType to set
*/
public void setDefaultJsonArrayType(Class> defaultJsonArrayType) {
this.defaultJsonArrayType = defaultJsonArrayType;
}
/**
* Creates a object from a JSON, with a specific target class.
*/
public T deserialize(Reader json, Type type) {
tokener = new JsonTokener(json);
char c = tokener.nextClean();
if (c == '[') {
if (Object.class.equals(type)) {
type = defaultJsonArrayType;
}
if (isArrayType(type)) {
tokener.back();
return parseJsonArray(type);
}
throw syntaxError("A json array can not be serialized to the " + Types.toString(type));
}
else if (c == '{') {
if (Object.class.equals(type)) {
type = defaultJsonObjectType;
}
if (!isArrayType(type)) {
tokener.back();
return parseJsonObject(type);
}
throw syntaxError("A json object can not be serialized to the " + Types.toString(type));
}
else {
if (isImmutableType(type)) {
tokener.back();
return nextValue(type);
}
throw syntaxError("Invalid json character: " + c);
}
}
/**
* Get the next value. The value can be a Boolean, Double, Integer, JSONArray, JSONObject, Long,
* or String, or the JSONObject.NULL object.
*
* @throws JsonException If syntax error.
* @return An object.
*/
private T nextValue(Type type) {
char c = tokener.nextClean();
String s;
switch (c) {
case '"':
case '\'':
return convertValue(tokener.nextString(c), type);
case '{':
tokener.back();
return parseJsonObject(type);
case '[':
tokener.back();
return parseJsonArray(type);
default:
// empty
}
/*
* Handle unquoted text. This could be the values true, false, or null, or it can be a
* number. An implementation (such as this one) is allowed to also accept non-standard
* forms. Accumulate characters until we reach the end of the text or a formatting
* character.
*/
StringBuilder sb = new StringBuilder();
char b = c;
while (c >= ' ' && c <= 0x7F && ",:]}/\\\"[{;=#".indexOf(c) < 0) {
sb.append(c);
c = tokener.next();
}
tokener.back();
/*
* If it is true, false, or null, return the proper value.
*/
s = sb.toString().trim();
if (s.isEmpty()) {
throw syntaxError("Missing value.");
}
if (s.equalsIgnoreCase("true")) {
return convertValue(Boolean.TRUE, type);
}
if (s.equalsIgnoreCase("false")) {
return convertValue(Boolean.FALSE, type);
}
if (s.equals("null") || s.equals("undefined")) {
return null;
}
/*
* If it might be a number, try converting it. We support the 0- and 0x- conventions. If a
* number cannot be produced, then the value will just be a string. Note that the 0-, 0x-,
* plus, and implied string conventions are non-standard. A JSON parser is free to accept
* non-JSON forms as long as it accepts all correct JSON forms.
*/
if ((b >= '0' && b <= '9') || b == '.' || b == '-' || b == '+') {
if (b == '0') {
if (s.length() > 2 && (s.charAt(1) == 'x' || s.charAt(1) == 'X')) {
Integer i = null;
try {
i = new Integer(Integer.parseInt(s.substring(2), 16));
}
catch (Exception e) {
/* Ignore the error */
}
if (i != null) {
convertValue(i, type);
}
}
else {
Integer i = null;
try {
i = new Integer(Integer.parseInt(s, 8));
}
catch (Exception e) {
/* Ignore the error */
}
if (i != null) {
convertValue(i, type);
}
}
}
Number i = null;
try {
i = Numbers.createNumber(s);
}
catch (Exception e) {
}
if (i != null) {
return convertValue(i, type);
}
return convertValue(s, type);
}
switch (tokener.peek()) {
case ',':
case '}':
case '{':
case '[':
case ']':
throw new JsonException("Unquotted string '" + s + "'");
}
return convertValue(s, type);
}
private JsonException syntaxError(String message) {
return tokener.syntaxError(message);
}
@Override
protected T convertValue(Object value, Type toType) {
try {
return super.convertValue(value, toType);
}
catch (Exception e) {
throw new JsonException("Failed convert value " + tokener.toString(), e);
}
}
private T parseJsonObject(Type type) {
if (Object.class.equals(type)) {
type = defaultJsonObjectType;
}
char c = tokener.nextClean();
if (c != '{') {
throw syntaxError("A json object text must begin with '{'");
}
if (type != null && isArrayType(type)) {
throw syntaxError("A json object can not be serialized to the " + Types.toString(type));
}
T obj = null;
BeanHandler bh = null;
if (type != null) {
bh = getBeanHandler(type);
obj = bh.createObject();
}
String key;
while (true) {
c = tokener.nextClean();
switch (c) {
case 0:
throw syntaxError("A JSONObject text must end with '}'");
case '}':
return obj;
case '"':
case '\'':
key = tokener.nextString(c);
break;
default:
if (Character.isJavaIdentifierStart(c)) {
tokener.back();
key = tokener.nextId();
}
else {
throw syntaxError("Invalid json character: " + c);
}
}
/*
* The key is followed by ':'. We will also tolerate '=' or '=>'.
*/
c = tokener.nextClean();
if (c == '=') {
if (tokener.next() != '>') {
tokener.back();
}
}
else if (c != ':') {
throw syntaxError("Expected a ':' after a key");
}
if (type == null || isExcludeProperty(key)) {
nextValue(null);
}
else {
if (bh.canWriteProperty(key)) {
Type pt = bh.getPropertyType(key);
if (isExcludeProperty(Types.getRawType(pt))) {
nextValue(null);
}
else {
Object val = nextValue(pt);
bh.setPropertyValue(obj, key, val);
}
}
else {
if (isIgnoreReadonlyProperty() && isIgnoreMissingProperty()) {
nextValue(null);
}
else {
if (bh.canReadProperty(key)) {
if (isIgnoreReadonlyProperty()) {
nextValue(null);
}
else {
throw syntaxError("readonly property [" + key + "] of " + type);
}
}
else {
if (isIgnoreMissingProperty()) {
nextValue(null);
}
else {
throw syntaxError("missing property [" + key + "] of " + type);
}
}
}
}
}
/*
* Pairs are separated by ','. We will also tolerate ';'.
*/
switch (tokener.nextClean()) {
case ';':
case ',':
if (tokener.nextClean() == '}') {
return obj;
}
tokener.back();
break;
case '}':
return obj;
default:
throw syntaxError("Expected a ',' or '}'");
}
}
}
@SuppressWarnings("unchecked")
private T parseJsonArray(Type type) {
if (Object.class.equals(type)) {
type = defaultJsonArrayType;
}
if (tokener.nextClean() != '[') {
throw syntaxError("A json array text must start with '['");
}
if (type != null && !isArrayType(type)) {
throw syntaxError("A json array can not be serialized to the " + Types.toString(type));
}
List