戳蓝字「TopCoder 」关注我们哦!
不知道小伙伴们有没有这样的困扰,平常开发中写单测,要mock一个复杂的对象,并且也知道了该对象的toString格式数据(比如从日志中获取),但是该怎么构建这个对象呢?
如果是json格式可以直接通过json反序列化得到对象,那么toString格式如何反序列得到对象呢?
从反序列化原理来看,我们首先要解析出对象的一个个属性,toString对象属性格式为 k1=v1,k2=v2
,那么可以按照逗号 ,
作为分隔符解析出一个个token,注意一个token可以是基本类型的kv,比如 int/Interger/…/String
这种;也可以是对象类型,比如 object/array/list/map
等。解析出来token之后,基本类型的token可以直接通过反射将v设置到对象属性(Field)中;对象类型的token可以继续按照toString格式进行反序列化,直到全部数据都反序列化成功为止;针对 array/list/map
的数据要获取到对应元素的实际类型才能知道要反序列化的对象。对应的代码实现如下:
/** * toString格式反序列化类 * * @author luoxiangnan * @date 2020-03-02 */ public class ToStringUtils { /** * toString格式反序列化 */ @SuppressWarnings("all") public static <T> T toObject(Class<T> clazz, String toString) throws Exception { if (Objects.isNull(clazz) || Objects.isNull(toString) || StringUtils.isEmpty(toString)) { return clazz == String.class ? (T) toString : null; } else if (TypeValueUtils.isBasicType(clazz)) { return (T) TypeValueUtils.basicTypeValue(clazz, toString.trim()); } toString = TokenUtils.cleanClassPrefix(clazz, toString.trim()); toString = StringUtils.removeStart(toString, "(").trim(); toString = StringUtils.removeEnd(toString, ")").trim(); String token = null; T result = clazz.newInstance(); while (StringUtils.isNotEmpty(toString) && StringUtils.isNotEmpty(token = TokenUtils.splitToken(toString))) { toString = StringUtils.removeStart(StringUtils.removeStart(toString, token).trim(), ",").trim(); // 解析k/v格式的属性名/值 Pair<String, String> keyValue = TokenUtils.parseToken(token); Field field = FieldUtils.getField(clazz, keyValue.getKey(), true); Object value = TypeValueUtils.buildTypeValue(field, keyValue.getValue()); FieldUtils.writeField(field, result, value, true); } return result; } /** * 字符串解析类 */ static class TokenUtils { /** * 清除类名前缀字符串 */ static String cleanClassPrefix(Class clazz, String toString) { String simpleName = clazz.getSimpleName(); if (clazz.getName().contains("$")) { // 内部类需要按照内部类名字格式 String rowSimpleName = StringUtils.substringAfterLast(clazz.getName(), "."); simpleName = StringUtils.replace(rowSimpleName, "$", "."); } return toString.startsWith(simpleName) ? StringUtils.removeStart(toString, simpleName).trim() : toString; } /** * 获取第一个token,注意: toString不再包括最外层的() */ private final static Map<Character, Character> tokenMap = new HashMap<>(); static { tokenMap.put(')', '('); tokenMap.put('}', '{'); tokenMap.put(']', '['); } static String splitToken(String toString) { if (StringUtils.isBlank(toString)) { return toString; } int bracketNum = 0; Stack<Character> stack = new Stack<>(); for (int i = 0; i < toString.length(); i++) { Character c = toString.charAt(i); if (tokenMap.containsValue(c)) { stack.push(c); } else if (tokenMap.containsKey(c) && Objects.equals(stack.peek(), tokenMap.get(c))) { stack.pop(); } else if ((c == ',') && stack.isEmpty()) { return toString.substring(0, i); } } if (stack.isEmpty()) { return toString; } throw new RuntimeException("splitFirstToken error, bracketNum=" + bracketNum + ", toString=" + toString); } /** * 从token解析出字段名,及对应值 */ static Pair<String, String> parseToken(String token) { assert Objects.nonNull(token) && token.contains("="); int pos = token.indexOf("="); return new javafx.util.Pair<>(token.substring(0, pos), token.substring(pos + 1)); } } /** * 对象构建类 */ static class TypeValueUtils { static Set<Class> BASIC_TYPE = Stream.of( char.class, Character.class, boolean.class, Boolean.class, short.class, Short.class, int.class, Integer.class, float.class, Float.class, double.class, Double.class, long.class, Long.class, String.class).collect(Collectors.toSet()); /** * Filed类型是否为基础类型 */ static boolean isBasicType(Class clazz) { return BASIC_TYPE.contains(clazz); } @SuppressWarnings("all") static Object buildTypeValue(Field field, String value) throws Exception { if (StringUtils.isBlank(value) || "null".equalsIgnoreCase(value)) { return field.getType() == String.class ? value : null; } Class clazz = field.getType(); if (isBasicType(clazz)) { return basicTypeValue(field.getGenericType(), value); } else if (field.getGenericType() == Date.class) { return new SimpleDateFormat("EEE MMM dd HH:mm:ss Z yyyy", new Locale("us")).parse(value); } else if (clazz.isArray() || clazz.isAssignableFrom(Array.class)) { return arrayTypeValue(field.getType().getComponentType(), value); } else if (clazz.isAssignableFrom(List.class)) { return listTypeValue(field, value); } else if (clazz.isAssignableFrom(Map.class)) { return mapTypeValue(field, value); } else { return toObject(clazz, value); } } static Object basicTypeValue(Type type, String value) { if (type == Character.class || type == char.class) { return value.charAt(0); } else if (type == Boolean.class || type == boolean.class) { return Boolean.valueOf(value); } else if (type == Short.class || type == short.class) { return Short.valueOf(value); } else if (type == Integer.class || type == int.class) { return Integer.valueOf(value); } else if (type == Float.class || type == float.class) { return Float.valueOf(value); } else if (type == Double.class || type == double.class) { return Double.valueOf(value); } else if (type == Long.class || type == long.class) { return Long.valueOf(value); } else if (type == String.class) { return value; } throw new RuntimeException("basicTypeValue error, type=" + type + ", value=" + value); } @SuppressWarnings("unchecked") static Object listTypeValue(Field field, String fieldValue) throws Exception { fieldValue = StringUtils.removeStart(fieldValue, "[").trim(); fieldValue = StringUtils.removeEnd(fieldValue, "]").trim(); String token; List<Object> result = new ArrayList<>(); while (StringUtils.isNotEmpty(fieldValue) && StringUtils.isNotEmpty(token = TokenUtils.splitToken(fieldValue))) { fieldValue = StringUtils.removeStart(StringUtils.removeStart(fieldValue, token).trim(), ",").trim(); result.add(toObject((Class) ((ParameterizedType) field.getGenericType()).getActualTypeArguments()[0], token)); } return result; } @SuppressWarnings("unchecked") static <T> T[] arrayTypeValue(Class<?> componentType, String fieldValue) throws Exception { fieldValue = StringUtils.removeStart(fieldValue, "[").trim(); fieldValue = StringUtils.removeEnd(fieldValue, "]").trim(); String token; T[] result = newArray(componentType, fieldValue); for (int i = 0; StringUtils.isNotEmpty(fieldValue) && StringUtils.isNotEmpty(token = TokenUtils.splitToken(fieldValue)); i++) { fieldValue = StringUtils.removeStart(StringUtils.removeStart(fieldValue, token).trim(), ",").trim(); result[i] = (T) toObject(componentType, token); } return result; } private static <T> T[] newArray(Class<?> componentType, String fieldValue) { String token; int lengh = 0; while (StringUtils.isNotEmpty(fieldValue) && StringUtils.isNotEmpty(token = TokenUtils.splitToken(fieldValue))) { fieldValue = StringUtils.removeStart(StringUtils.removeStart(fieldValue, token).trim(), ",").trim(); lengh++; } return (T[]) Array.newInstance(componentType, lengh); } @SuppressWarnings("unchecked") static Map mapTypeValue(Field field, String toString) throws Exception { toString = StringUtils.removeStart(toString, "{").trim(); toString = StringUtils.removeEnd(toString, "}").trim(); String token; Map result = new HashMap(); while (StringUtils.isNotEmpty(token = TokenUtils.splitToken(toString))) { toString = StringUtils.removeStart(StringUtils.removeStart(toString, token).trim(), ",").trim(); assert token.contains("="); String fieldName = StringUtils.substringBefore(token, "=").trim(); String fieldValue = StringUtils.substringAfter(token, "=").trim(); result.put(basicTypeValue(((ParameterizedType) field.getGenericType()).getActualTypeArguments()[0], fieldName), toObject((Class) ((ParameterizedType) field.getGenericType()).getActualTypeArguments()[1], fieldValue)); } return result; } } }
测试代码如下:
public class ToStringUtilsTest { @Test public void toObject() throws Exception { DemoBean demoBean = DemoBean.builder() .c1('c').c2('d').s1((short) 1).s2((short) 2) .i1(1).i2(2).l1(1L).l2(2L) .f1(1.0F).f2(2.0F).d1(1.0D).d2(2.0D) .ss1("").ss2("null").date(new Date()) .a(new A()).aList(Arrays.asList(new A(), new A())) .aArray((A[]) Arrays.asList(new A(), new A()).toArray()) .build(); { Map<String, A> aMap = new HashMap<>(); aMap.put("1", new A()); aMap.put("2", new A()); aMap.put("3", new A()); demoBean.setAMap(aMap); } String toString = demoBean.toString(); DemoBean demoBean2 = ToStringUtils.toObject(DemoBean.class, toString); System.out.println(demoBean2); Assert.assertEquals(toString, demoBean2.toString()); } @Data @Builder @NoArgsConstructor @AllArgsConstructor static class DemoBean { private char c1; private Character c2; private short s1; private Short s2; private int i1; private Integer i2; private long l1; private long l2; private float f1; private Float f2; private double d1; private double d2; private String ss1; private String ss2; private String ss3 = null; private Date date; private A a; private List<A> aList; private A[] aArray; private Map<String, A> aMap; } @Data static class A { private static int num = 1; private int i = 11 + num++; private Long l = 22L + num++; private Date date = new Date(System.currentTimeMillis() + num++); } }
结果输出如下:
ToStringUtilsTest.DemoBean(c1=c, c2=d, s1=1, s2=2, i1=1, i2=2, l1=1, l2=2, f1=1.0, f2=2.0, d1=1.0, d2=2.0, ss1=, ss2=null, ss3=null, date=Sun Mar 08 09:44:52 CST 2020, a=ToStringUtilsTest.A(i=12, l=24, date=Sun Mar 08 09:44:52 CST 2020), aList=[ToStringUtilsTest.A(i=15, l=27, date=Sun Mar 08 09:44:52 CST 2020), ToStringUtilsTest.A(i=18, l=30, date=Sun Mar 08 09:44:52 CST 2020)], aArray=[ToStringUtilsTest.A(i=21, l=33, date=Sun Mar 08 09:44:52 CST 2020), ToStringUtilsTest.A(i=24, l=36, date=Sun Mar 08 09:44:52 CST 2020)], aMap={1=ToStringUtilsTest.A(i=27, l=39, date=Sun Mar 08 09:44:52 CST 2020), 2=ToStringUtilsTest.A(i=30, l=42, date=Sun Mar 08 09:44:52 CST 2020), 3=ToStringUtilsTest.A(i=33, l=45, date=Sun Mar 08 09:44:52 CST 2020)})
ToStringUtils
针对大部分场景的toString反序列化是OK的,但是针对map中key是对象类型这种场景还未支持,感兴趣的小伙伴可以自行按照上述代码进行扩展,源码地址为:https://github.com/luoxn28/code-toolbox/blob/master/java/src/main/java/com/github/nan/util/ToStringUtils.java。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
黑客攻防技术宝典(第2版)
[英] Dafydd Stuttard、[英] Marcus Pinto / 石华耀、傅志红 / 人民邮电出版社 / 2012-6-26 / 99.00元
内容简介: Web应用无处不在,安全隐患如影随形。承载着丰富功能与用途的Web应用程序中布满了各种漏洞,攻击者能够利用这些漏洞盗取用户资料,实施诈骗,破坏其他系统等。近年来,一些公司的网络系统频频遭受攻击,导致用户信息泄露,造成不良影响。因此,如何确保Web应用程序的安全,已成为摆在人们眼前亟待解决的问题。 本书是Web安全领域专家的经验结晶,系统阐述了如何针对Web应用程序展开攻击与......一起来看看 《黑客攻防技术宝典(第2版)》 这本书的介绍吧!
图片转BASE64编码
在线图片转Base64编码工具
UNIX 时间戳转换
UNIX 时间戳转换