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

io.github.kits.json.parser.JsonParser Maven / Gradle / Ivy

The newest version!
package io.github.kits.json.parser;

import io.github.kits.EnvKit;
import io.github.kits.ReflectKit;
import io.github.kits.StringKit;
import io.github.kits.annotations.IgnoreJsonSerialize;
import io.github.kits.annotations.JsonCamelCase;
import io.github.kits.annotations.JsonSerializeName;
import io.github.kits.annotations.NoneSerializeNull;
import io.github.kits.exception.JsonParseException;
import io.github.kits.json.JsonKit;
import io.github.kits.json.tokenizer.JsonToken;
import io.github.kits.json.tokenizer.JsonTokenList;
import io.github.kits.json.tokenizer.JsonTokenType;
import io.github.kits.json.tokenizer.JsonTokenizer;

import java.io.IOException;
import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;

import static io.github.kits.json.tokenizer.JsonTokenType.BEGIN_ARRAY;
import static io.github.kits.json.tokenizer.JsonTokenType.BEGIN_OBJECT;
import static io.github.kits.json.tokenizer.JsonTokenType.BOOLEAN;
import static io.github.kits.json.tokenizer.JsonTokenType.END_ARRAY;
import static io.github.kits.json.tokenizer.JsonTokenType.END_OBJECT;
import static io.github.kits.json.tokenizer.JsonTokenType.NULL;
import static io.github.kits.json.tokenizer.JsonTokenType.NUMBER;
import static io.github.kits.json.tokenizer.JsonTokenType.OBJECT;
import static io.github.kits.json.tokenizer.JsonTokenType.SEP_COLON;
import static io.github.kits.json.tokenizer.JsonTokenType.SEP_COMMA;
import static io.github.kits.json.tokenizer.JsonTokenType.STRING;

/**
 * @project: kits
 * @created: with IDEA
 * @author: whimthen
 * @date: 2019-03-05-14:15 | March. Tuesday
 */
public class JsonParser {

	private final JsonTokenizer tokenizer = new JsonTokenizer();
	private       JsonTokenList tokens;

	/**
	 * Convert jsonTokenizer to JsonObject or JsonArray
	 *
	 * @return JsonObject or JsonArray
	 */
	private Object parse() {
		JsonToken token = tokens.next();
		if (token == null) {
			return new JsonObject();
		} else if (token.getTokenType() == BEGIN_OBJECT) {
			return parseJsonObject();
		} else if (token.getTokenType() == BEGIN_ARRAY) {
			return parseJsonArray();
		} else {
			throw new JsonParseException("Parse error, invalid JsonToken.");
		}
	}

	/**
	 * Convert a JSON string to a List collection
	 * 
{@code
	 * 		public class Case {
	 * 		 	private boolean json;
	 *
	 * 		 	getter and setter .......
	 *        }
	 *
	 *     	List cases = JsonKit.toList("[{\"json\": true}, {\"json\": false}]", Case.class);
	 * }
* * @param json JSON String * @param tClass List Type * @param Generic * @return List * @throws IOException IOException */ public List parseList(String json, Class tClass) throws IOException { this.tokens = tokenizer.tokenize(json); Object object = parse(); if (object instanceof JsonArray) { JsonArray jsonArray = (JsonArray) object; if (JsonObject.class.isAssignableFrom(tClass)) { throw new JsonParseException("JsonArray can not be cast JsonObject."); } if (JsonArray.class.isAssignableFrom(tClass)) { return jsonArray.getList(); } ArrayList arrayList = new ArrayList<>(); if (jsonArray.getList() .isEmpty()) { return arrayList; } jsonArray.getList() .forEach(jsonObject -> { try { arrayList.add(convertObject((JsonObject) jsonObject, tClass)); } catch (ReflectiveOperationException e) { throw new JsonParseException(e); } }); return arrayList; } return new ArrayList<>(); } /** * Convert a JSON string to an object *
{@code
	 * 		public class User {
	 * 			private int userId;
	 * 		 	private String userName;
	 *
	 * 			getter and setter .......
	 *        }
	 *
	 * 		User user = JsonKit.toObject("{\"userId\": 1, \"userName\": \"whimthen\"}", User.class);
	 * }
* * @param json JSON String * @param tClass Object Type * @param Generic * @return Object * @throws ReflectiveOperationException Abnormal reflection operation * @throws IOException IOException */ public T parseObject(String json, Class tClass) throws ReflectiveOperationException, IOException { this.tokens = tokenizer.tokenize(json); Object object = parse(); if (object instanceof JsonObject) { if (JsonArray.class.isAssignableFrom(tClass)) { throw new JsonParseException("JsonObject can not be cast JsonArray."); } if (JsonObject.class.isAssignableFrom(tClass)) { return (T) object; } return convertObject((JsonObject) object, tClass); } return (T) object; } /** * Convert Object to JSON String *
{@code
	 * 		User user = new User();
	 * 		user.setUserId(1);
	 * 		user.setUserName(\"whimthen\");
	 *
	 * 		String json = JsonKit.toJson(user);
	 * 		// json = {\"userId\": 1, \"userName\": \"whimthen\"}
	 * }
* * @param object Object * @return JsonString */ public String toJson(Object object) { if (Objects.isNull(object)) { return NULL.getValue(); } Class aClass = object.getClass(); JsonTokenList jsonTokenList = new JsonTokenList(); if (object instanceof Collection) { jsonTokenList.add(new JsonToken(BEGIN_ARRAY)); ((Collection) object).forEach(val -> listOrMapAddJsonTokenList(jsonTokenList, val)); return getStringFromJsonTokenList(jsonTokenList, END_ARRAY); } else if (Map.class.isAssignableFrom(aClass)) { jsonTokenList.add(new JsonToken(BEGIN_OBJECT)); ((Map) object).forEach((k, v) -> { jsonTokenList.add(new JsonToken(STRING, k)); jsonTokenList.add(new JsonToken(SEP_COLON)); listOrMapAddJsonTokenList(jsonTokenList, v); }); return getStringFromJsonTokenList(jsonTokenList, END_OBJECT); } else if (aClass.equals(JsonObject.class)) { return toJson(((JsonObject) object).getEntry()); } else if (CharSequence.class.isAssignableFrom(object.getClass())) { return object.toString(); } else if (EnvKit.isBasicType(aClass) || aClass.isPrimitive()) { return object.toString(); } else if (EnvKit.isSystemType(aClass)) { return object.toString(); } else { jsonTokenList.add(new JsonToken(BEGIN_OBJECT)); } NoneSerializeNull serializeNull = aClass.getAnnotation(NoneSerializeNull.class); AtomicBoolean isSerializeNull = new AtomicBoolean(Objects.isNull(serializeNull)); ReflectKit.getFields(aClass) .forEach(field -> complexJsonTokenListForObject(object, jsonTokenList, isSerializeNull, field)); jsonTokenList.remove(jsonTokenList.size() - 1); jsonTokenList.add(new JsonToken(END_OBJECT)); return toJsonString(jsonTokenList); } private String getStringFromJsonTokenList(JsonTokenList jsonTokenList, JsonTokenType endTokenType) { if (jsonTokenList.size() - 1 > 0) { jsonTokenList.remove(jsonTokenList.size() - 1); } jsonTokenList.add(new JsonToken(endTokenType)); return toJsonString(jsonTokenList); } private void listOrMapAddJsonTokenList(JsonTokenList jsonTokenList, Object v) { JsonToken valueType = getValueType(v.getClass(), v.toString()); if (Objects.nonNull(valueType)) { jsonTokenList.add(valueType); } else { jsonTokenList.add(new JsonToken(OBJECT, toJson(v))); } jsonTokenList.add(new JsonToken(SEP_COMMA)); } private void complexJsonTokenListForObject(Object object, JsonTokenList jsonTokenList, AtomicBoolean isSerializeNull, Field field) { try { if (isSerializeNull.get()) { isSerializeNull.set(Objects.isNull(field.getAnnotation(NoneSerializeNull.class))); } IgnoreJsonSerialize ignoreJson = field.getAnnotation(IgnoreJsonSerialize.class); if (Objects.isNull(ignoreJson)) { String name = field.getName(); JsonSerializeName serializeName = field.getAnnotation(JsonSerializeName.class); if (Objects.nonNull(serializeName)) { name = serializeName.value(); } if (!field.isAccessible()) { field.setAccessible(true); } Object value = field.get(object); boolean isNotNullValue = Objects.nonNull(value); boolean isSerializeNullOrNotNullValue = isSerializeNull.get() || isNotNullValue; if (isSerializeNullOrNotNullValue) { jsonTokenList.add(new JsonToken(STRING, name)); jsonTokenList.add(new JsonToken(SEP_COLON)); } if (isNotNullValue) { JsonToken jsonToken = getValueType(field.getType(), value.toString()); if (Objects.isNull(jsonToken)) { jsonTokenList.add(new JsonToken(OBJECT, toJson(field.get(object)))); } else { jsonTokenList.add(jsonToken); } } else if (isSerializeNull.get()) { jsonTokenList.add(new JsonToken(NULL)); } if (isSerializeNullOrNotNullValue) { jsonTokenList.add(new JsonToken(SEP_COMMA)); } } } catch (IllegalAccessException ex) { throw new JsonParseException(ex); } } /** * Convert JsonTokenList to JsonString * * @param tokens JsonTokenList * @return JsonString */ private String toJsonString(JsonTokenList tokens) { if (Objects.isNull(tokens) || tokens.size() <= 0) { return null; } StringBuilder json = new StringBuilder(); while (tokens.hasMore()) { JsonToken jsonToken = tokens.next(); switch (jsonToken.getTokenType()) { case BEGIN_OBJECT: case END_OBJECT: case BEGIN_ARRAY: case END_ARRAY: case END_DOCUMENT: case NULL: case OBJECT: json.append(jsonToken.getValue()); break; case SEP_COMMA: case SEP_COLON: json.append(jsonToken.getValue()).append(" "); break; case STRING: json.append("\"").append(jsonToken.getValue()).append("\""); break; case NUMBER: if (jsonToken.getValue().contains(".")) { json.append(Double.valueOf(jsonToken.getValue())); } else { json.append(Long.valueOf(jsonToken.getValue())); } break; case BOOLEAN: json.append(Boolean.valueOf(jsonToken.getValue())); break; } } return json.toString(); } /** * Get the corresponding JsonToken * according to the Field type, and assign the value * * @param type Field * @param value Value * @return JsonToken */ private JsonToken getValueType(Class type, String value) { if (type.equals(Boolean.class) || type.equals(boolean.class)) { return new JsonToken(BOOLEAN, value); } else if (type.equals(Short.class) || type.equals(short.class) || type.equals(Integer.class) || type.equals(int.class) || type.equals(Long.class) || type.equals(long.class) || type.equals(Float.class) || type.equals(float.class) || type.equals(Double.class) || type.equals(double.class) || type.equals(BigDecimal.class)) { return new JsonToken(NUMBER, value); } else if (CharSequence.class.isAssignableFrom(type)) { return new JsonToken(STRING, value); } return null; } /** * Convert a JsonObject to a specific type of object * * @param object JsonObject * @param tClass Object Type * @param Generic * @return Object * @throws ReflectiveOperationException Abnormal reflection operation */ private T convertObject(JsonObject object, Class tClass) throws ReflectiveOperationException { T t; if (CharSequence.class.isAssignableFrom(tClass)) { if (object.size() != 0) { return (T) object.getEntry(); } else { return null; } } else if (EnvKit.isBasicType(tClass)) { // generic type if (CharSequence.class.isAssignableFrom(tClass)) { return (T) JsonKit.toJson(object.getEntry()); } else { throw new JsonParseException("JsonToken can not parse to basic type"); } } else if (Collection.class.isAssignableFrom(tClass)) { // Collection Type ArrayList list = new ArrayList<>(); list.add(object.getEntry()); // if there is interface not instance if (tClass.isInterface()) { return (T) list; } else { // but implement type can instance Collection collection = (Collection) tClass.newInstance(); collection.addAll(list); return (T) collection; } } else if (Map.class.isAssignableFrom(tClass)) { object.getEntry().forEach((k, v) -> { if (v instanceof JsonArray) { v = ((JsonArray) v).getList(); } else if (v instanceof JsonObject) { v = ((JsonObject) v).getEntry(); } object.getEntry().put(k, v); }); return (T) object.getEntry(); } else { t = tClass.newInstance(); } T finalT = t; JsonCamelCase classCameCase = tClass.getAnnotation(JsonCamelCase.class); AtomicBoolean classIsCameCase = new AtomicBoolean(false); if (Objects.nonNull(classCameCase)) { classIsCameCase.set(true); } ReflectKit.getFields(tClass) .forEach(field -> invokeSetMethodByField(object, tClass, finalT, classIsCameCase, field)); return t; } /** * The setter method of the object according to the field * * @param object JsonObject * @param tClass Class * @param finalT Object * @param classIsCameCase 是否下划线转驼峰 * @param field 字段 * @param 返回类型 */ private void invokeSetMethodByField(JsonObject object, Class tClass, T finalT, AtomicBoolean classIsCameCase, Field field) { try { IgnoreJsonSerialize serialization = field.getAnnotation(IgnoreJsonSerialize.class); String valKey = field.getName(); // If there is a JsonCameCase annotation on the class, // then there is no judgment on whether there is this annotation identifier on each field. if (classIsCameCase.get()) { valKey = StringKit.unCameCase(valKey); } else { JsonCamelCase fieldIsCameCase = field.getAnnotation(JsonCamelCase.class); if (Objects.nonNull(fieldIsCameCase)) { valKey = StringKit.unCameCase(valKey); } } JsonSerializeName serializationName = field.getAnnotation(JsonSerializeName.class); if (Objects.nonNull(serializationName)) { valKey = serializationName.value(); } if (Objects.isNull(serialization)) { if (object.contains(valKey)) { Object val = getValue(field.getType(), valKey, object); object.remove(valKey); ReflectKit.invokeSetMethod(tClass, finalT, field, val); } } } catch (ReflectiveOperationException | IOException ex) { throw new JsonParseException(ex); } } /** * Get values ​​from JsonObject based on field * * @param type Field * @param jo JsonObject * @return Object */ private Object getValue(Class type, String name, JsonObject jo) throws ReflectiveOperationException { if (Boolean.class.equals(type) || boolean.class.equals(type)) { return jo.getBoolean(name); } else if (Short.class.equals(type) || short.class.equals(type)) { return jo.getShort(name); } else if (Integer.class.equals(type) || int.class.equals(type)) { return jo.getInt(name); } else if (Long.class.equals(type) || long.class.equals(type)) { return jo.getLong(name); } else if (Float.class.equals(type) || float.class.equals(type)) { return jo.getFloat(name); } else if (CharSequence.class.isAssignableFrom(type)) { return jo.getString(name); } else if (Double.class.equals(type) || double.class.equals(type)) { return jo.getDouble(name); } else if (BigDecimal.class.equals(type)) { return jo.getBigdecimal(name); } else if (JsonObject.class.equals(type)) { return jo.getJsonObject(name); } else if (JsonArray.class.equals(type)) { return jo.getJsonArray(name); } else if ((List.class.isAssignableFrom(type) && Objects.nonNull(jo.get(name)))) { // if type is interface can not instance if (type.isInterface()) { return jo.getJsonArray(name).getList(); } else { List o = (List) type.newInstance(); o.addAll(jo.getJsonArray(name).getList()); return o; } } else if ((Set.class.isAssignableFrom(type) && Objects.nonNull(jo.get(name)))) { // if type is interface can not instance if (type.isInterface()) { return new HashSet<>(jo.getJsonArray(name).getList()); } else { Set o = (Set) type.newInstance(); List list = jo.getJsonArray(name).getList(); // TODO 添加之前还得考虑泛型约束, 上边List同理 o.addAll(list); // list.forEach(val -> { // if (val instanceof JsonObject) { // o.add(((JsonObject) val).getEntry()); // } else if (val instanceof JsonArray) { // o.add(((JsonArray) val).getList()); // } else { // o.add(val); // } // }); return o; } } else { if (Objects.isNull(jo.get(name))) { return null; } return convertObject(jo.getJsonObject(name), type); } } /** * Convert the value of JsonTokenList to JsonObject * * @return JsonObject */ private JsonObject parseJsonObject() { JsonObject jsonObject = new JsonObject(); int expectToken = STRING.getTokenCode() | END_OBJECT.getTokenCode(); String key = null; Object value; while (tokens.hasMore()) { JsonToken token = tokens.next(); JsonTokenType tokenType = token.getTokenType(); String tokenValue = token.getValue(); switch (tokenType) { case BEGIN_OBJECT: checkExpectToken(tokenType, expectToken); jsonObject.put(key, parseJsonObject()); // 递归解析 json object expectToken = SEP_COMMA.getTokenCode() | END_OBJECT.getTokenCode(); break; case BEGIN_ARRAY: // 解析 json array checkExpectToken(tokenType, expectToken); jsonObject.put(key, parseJsonArray()); expectToken = SEP_COMMA.getTokenCode() | END_OBJECT.getTokenCode(); break; case NULL: checkExpectToken(tokenType, expectToken); jsonObject.put(key, null); expectToken = SEP_COMMA.getTokenCode() | END_OBJECT.getTokenCode(); break; case NUMBER: checkExpectToken(tokenType, expectToken); if (tokenValue.contains(".") || tokenValue.contains("e") || tokenValue.contains("E")) { jsonObject.put(key, Double.valueOf(tokenValue)); } else { Long num = Long.valueOf(tokenValue); if (num > Integer.MAX_VALUE || num < Integer.MIN_VALUE) { jsonObject.put(key, num); } else { jsonObject.put(key, num.intValue()); } } expectToken = SEP_COMMA.getTokenCode() | END_OBJECT.getTokenCode(); break; case BOOLEAN: checkExpectToken(tokenType, expectToken); jsonObject.put(key, Boolean.valueOf(token.getValue())); expectToken = SEP_COMMA.getTokenCode() | END_OBJECT.getTokenCode(); break; case STRING: checkExpectToken(tokenType, expectToken); JsonToken preToken = tokens.peekPrevious(); /* * 在 JSON 中,字符串既可以作为键,也可作为值。 * 作为键时,只期待下一个 JsonToken 类型为 SEP_COLON。 * 作为值时,期待下一个 JsonToken 类型为 SEP_COMMA 或 END_OBJECT */ if (preToken.getTokenType() == SEP_COLON) { value = token.getValue(); jsonObject.put(key, value); expectToken = SEP_COMMA.getTokenCode() | END_OBJECT.getTokenCode(); } else { key = token.getValue(); expectToken = SEP_COLON.getTokenCode(); } break; case SEP_COLON: checkExpectToken(tokenType, expectToken); expectToken = NULL.getTokenCode() | NUMBER.getTokenCode() | BOOLEAN.getTokenCode() | STRING.getTokenCode() | BEGIN_OBJECT.getTokenCode() | BEGIN_ARRAY.getTokenCode(); break; case SEP_COMMA: checkExpectToken(tokenType, expectToken); expectToken = STRING.getTokenCode(); break; case END_OBJECT: case END_DOCUMENT: checkExpectToken(tokenType, expectToken); return jsonObject; default: throw new JsonParseException("Unexpected JsonToken."); } } throw new JsonParseException("Parse error, invalid JsonToken."); } /** * Convert the value of JsonTokenList to JsonArray * * @return JsonArray */ private JsonArray parseJsonArray() { int expectToken = BEGIN_ARRAY.getTokenCode() | END_ARRAY.getTokenCode() | BEGIN_OBJECT.getTokenCode() | NULL.getTokenCode() | NUMBER.getTokenCode() | BOOLEAN.getTokenCode() | STRING.getTokenCode(); JsonArray jsonArray = new JsonArray(); while (tokens.hasMore()) { JsonToken token = tokens.next(); JsonTokenType tokenType = token.getTokenType(); String tokenValue = token.getValue(); switch (tokenType) { case BEGIN_OBJECT: checkExpectToken(tokenType, expectToken); jsonArray.add(parseJsonObject()); expectToken = SEP_COMMA.getTokenCode() | END_ARRAY.getTokenCode(); break; case BEGIN_ARRAY: checkExpectToken(tokenType, expectToken); jsonArray.add(parseJsonArray()); expectToken = SEP_COMMA.getTokenCode() | END_ARRAY.getTokenCode(); break; case NULL: checkExpectToken(tokenType, expectToken); jsonArray.add(null); expectToken = SEP_COMMA.getTokenCode() | END_ARRAY.getTokenCode(); break; case NUMBER: checkExpectToken(tokenType, expectToken); if (tokenValue.contains(".") || tokenValue.contains("e") || tokenValue.contains("E")) { jsonArray.add(Double.valueOf(tokenValue)); } else { Long num = Long.valueOf(tokenValue); if (num > Integer.MAX_VALUE || num < Integer.MIN_VALUE) { jsonArray.add(num); } else { jsonArray.add(num.intValue()); } } expectToken = SEP_COMMA.getTokenCode() | END_ARRAY.getTokenCode(); break; case BOOLEAN: checkExpectToken(tokenType, expectToken); jsonArray.add(Boolean.valueOf(tokenValue)); expectToken = SEP_COMMA.getTokenCode() | END_ARRAY.getTokenCode(); break; case STRING: checkExpectToken(tokenType, expectToken); jsonArray.add(tokenValue); expectToken = SEP_COMMA.getTokenCode() | END_ARRAY.getTokenCode(); break; case SEP_COMMA: checkExpectToken(tokenType, expectToken); expectToken = STRING.getTokenCode() | NULL.getTokenCode() | NUMBER.getTokenCode() | BOOLEAN.getTokenCode() | BEGIN_ARRAY.getTokenCode() | BEGIN_OBJECT.getTokenCode(); break; case END_ARRAY: case END_DOCUMENT: checkExpectToken(tokenType, expectToken); return jsonArray; default: throw new JsonParseException("Unexpected JsonToken."); } } throw new JsonParseException("Parse error, invalid JsonToken."); } /** * Check if JsonTokenType is correct * * @param tokenType JsonTokenType * @param expectToken expectToken */ private void checkExpectToken(JsonTokenType tokenType, int expectToken) { if ((tokenType.getTokenCode() & expectToken) == 0) { throw new JsonParseException("Parse error, invalid JsonToken."); } } }