Java 反序列化的自定义表达式解析实现

简介:

       通过SPEL 和 fastjson 配合使用,指定JSON中字段解析的表达式,实现指定字段按照指定表达式填充的效果。

       以使用三目运算符指定解析为例,代码如下。

首先,定义字段的反序列化解析类 SpelExpressDeserializer

import java.lang.reflect.Type;

import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.util.StringUtils;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.DefaultJSONParser;
import com.alibaba.fastjson.parser.deserializer.ContextObjectDeserializer;
import com.alibaba.fastjson.parser.deserializer.ObjectDeserializer;

import lombok.extern.slf4j.Slf4j;

/**
 * SPEL运算反序列
 * 
 * @author jun.chen
 *
 */
@Slf4j
public class SpelExpressDeserializer extends ContextObjectDeserializer implements ObjectDeserializer {

	private ExpressionParser spel = new SpelExpressionParser();

	@Override
	public int getFastMatchToken() {
		return 0;
	}

	@SuppressWarnings("unchecked")
	@Override
	public <T> T deserialze(DefaultJSONParser parser, Type type, Object fieldName, String format, int features) {
		Object parse = parser.parse(fieldName);
		if (null == parse || StringUtils.isEmpty(format)) {
			return null;
		}

		// 通过SPEL运算出结果。
		StandardEvaluationContext context = new StandardEvaluationContext();
		context.setVariables(JSON.parseObject(parser.getInput()));
		T value = null;
		try {
			String express = format.replaceAll("'", "\"");
			Expression exp = spel.parseExpression(express);
			value = (T) exp.getValue(context, Class.forName(type.getTypeName()));
		} catch (Exception e) {
			log.error(e.getMessage(), e);
		}
		return value;
	}

}

然后,单元测试如下

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import org.junit.Test;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.annotation.JSONField;

import lombok.Data;

public class JsonFormatWithSpelTest {

	@Data
	static class DemoBill {

		/** 单据编号 */
		private String billNo;
		/** 备注消息 */
		private String remark;
		/** 是否包含备注 */
		@JSONField(alternateNames = "remark", format = "#remark == null || #remark.isEmpty()? false:true", deserializeUsing = SpelExpressDeserializer.class)
		private Boolean has_remark;
		/** 备注长度 */
		@JSONField(alternateNames = "remark", format = "#remark == null || #remark.isEmpty()? 0:#remark.length()", deserializeUsing = SpelExpressDeserializer.class)
		private Integer remark_length;

	}

	@Test
	public void testDeserializer() {

		String json_a = "{\"billNo\":\"No0001\",\"remark\":\"1234\",\"_remark\":\"1234\"}";
		DemoBill demo_a = fillJson(json_a, DemoBill.class);
		System.out.println("demo_a: " + JSON.toJSONString(demo_a));

		String json_b = "{\"billNo\":\"No0002\"}";
		DemoBill demo_b = fillJson(json_b, DemoBill.class);
		System.out.println("demo_b: " + JSON.toJSONString(demo_b));
	}

	/**
	 * 将JSON字符串反序列化为Bean对象
	 * 
	 * @param <T>
	 * @param sourceJson
	 * @param clazz
	 * @return
	 */
	private <T> T fillJson(String sourceJson, Class<T> clazz) {
		Map<String, List<String>> fieldMap = getNameMapping(clazz);
		JSONObject json = JSON.parseObject(sourceJson);
		// 填充自定义标签字段值,用关联字段填充,因为JSON数据中不包含当前属性时,反序列化方法不会执行。
		for (Entry<String, List<String>> entry : fieldMap.entrySet()) {
			String key = entry.getKey();
			Object val = json.containsKey(key) ? json.get(entry.getKey()) : "";
			entry.getValue().forEach(a -> json.put(a, val));
		}
		return JSON.parseObject(json.toJSONString(), clazz);
	}

	/**
	 * 抽取类中指定使用SpelExpressDeserializer.class进行反序列化的字段和关联字段的关系
	 * 
	 * @param clazz
	 * @return key:关联字段 value:与关联字段挂钩的解析字段
	 */
	private Map<String, List<String>> getNameMapping(Class<?> clazz) {
		Map<String, List<String>> fieldMap = new HashMap<>();
		Field[] declaredFields = clazz.getDeclaredFields();
		for (Field declaredField : declaredFields) {
			declaredField.isAnnotationPresent(JSONField.class);
			JSONField annotation = declaredField.getAnnotation(JSONField.class);
			if (null == annotation) {
				continue;
			}
			if (annotation.deserializeUsing().equals(SpelExpressDeserializer.class)) {
				String name = declaredField.getName();
				String refField = annotation.alternateNames()[0];
				List<String> list = fieldMap.get(refField);
				if (null == list) {
					list = new ArrayList<>();
				}
				list.add(name);
				fieldMap.put(refField, list);
			}
		}
		return fieldMap;
	}

}

说明:JSON字符串中不包含某个属性时,不会进入反序列化方法

打印结果如下。

demo_a: {"billNo":"No0001","has_remark":true,"remark":"1234","remark_length":4}
demo_b: {"billNo":"No0002","has_remark":false,"remark_length":0}