表达式和java对象的互相转换

起源和问题

    这两天写了一个很让人头疼的东西,手上的项目对接一个银行的系统,看了他们的接口文档发现,他们要求我们用表单形式进行参数传递(其实就是键值对),这个还好,后面跟着是如果是list,那就是形如这样:key=user[0].name,value=“张三”;如果是map,那就是:key=user[name],value=“张三”,这种表达式其实很常见,常见的模板引擎里经常看到,比如el表达式不就差不多长这样嘛。当时问题是,我怎么把一个java对象转换成一组这样格式的表达式呢。一开始我觉得这种东西其实挺常见的,就在网上搜,找了一下午没找到(绝望),还去一些java群里问了,也没人回答,还有人冒出来说这不就是json嘛(老子真的是,要是是json,遍地都是工具包,老子还发群里来问?)
    找了一下午没找到现成的,最后心一横决定自己写了,就当是letcode上的一到算法题吧。

对象转键值对组

分析

    其实对象转换成一组键值对,难点在于key,value其实就是变量的值,我们就拿下面这个类为例:

@Data
public class PaserJSON {

    private User user1;

    private String test;

    private List<User> users;

    private Map<String,User> userMap;

    private Map<String,String> stringMap;

    private List<List<User>> listList;

    @Data
    public static class User {
        private String username;
        private String hiheih;
        private boolean flag;
        private Boolean flag2;
        private int val1;
        private Integer val2;
        
    }
    
}

这个类也是我做测试的类,其实已经挺复杂了,里面最简单的肯定是String test这个变量,转换成键值对就是key=test,value=“xxx”(xxx变量的值)。比较复杂的,比如users往下的一个变量,转换成键值对就是key=users[0].username,value=“xxx”,如果是userMap就是,key=userMap[张三].username,value=“xxx”,还有最后那个listList,key=listList[0][0].username,value=“xxx”。分析下来,基调就定下来了,其实就是一个递归,对最外层的属性进行一个递归,一层一层往里面走,一直走到不可拆为止。
    这么看,其实不是特别难。但有几个关于java的知识点:

  • 怎么获取到一个对象的每个属性的值,以及变量名,以及类型。其实很容易想到就是反射:
            Field[] fields = o.getClass().getDeclaredFields();

o就是一个对象,这样便能获取到一个类里面所有的属性,以及属性的变量名,属性类型,如果甚至可以直接拿到属性的值,但是如果是私有类型,会破坏类,所以我们后面采用了反射调用get方法来获取属性值。

  • 怎么确定一个对象不可再分?比如说上面这个类User,你看到这个User你怎么知道还要往里面走?你可能会说通过getDeclaredFields,直到拿不到Filed[],如果是基本数据类型是正确的,你调int.class.getDeclaredFields(),你会发现拿不到Field了。但是如果是String、Integer之类的呢?亲自测了一下:
    public static void main(String[] args) {
        Field[] fields =Integer.class.getDeclaredFields();
        for(Field field:fields){
            System.out.println(field.getName());
        }
    }

打印结果:

MIN_VALUE
MAX_VALUE
TYPE
digits
DigitTens
DigitOnes
sizeTable
value
SIZE
BYTES
serialVersionUID

原来Integer里有这么多属性。所以这一招是行不通的,你可以说你对象里不用Integer全用int,但你说不用String,那也太难了。后来决定以这个为边界:如果是自己定义的类,那就是可拆的,如果是java自带的一些类那就不可拆,直接toString转字符串(当然List,Map这些集合除外)。那如何区分一个类是自己写的还是java自带的?如下:

    private static boolean isJavaClass(Class<?> clz) {
        return clz != null && clz.getClassLoader() == null;
    }

如果不理解,可以去翻一下我jvm专题下,类加载机制那一章就懂了,如果不懂类加载机制,可能确实看不懂这一行代码(这行代码还不是我写的,还是我网上找到了,直到看到之后才恍然大悟,之前自己也不知道怎么区分)。

  • 如果一个类是java自带的类,如何判断他是否为集合呢,Class类里的isAssignableFrom这个方法能够判断,一个类是否为你的父类,比如:
List.class.isAssignableFrom(ArrayList.class); \\true
Map.class.isAssignableFrom(HashMap.class); \\true
ArrayList.class.isAssignableFrom(List.class); \\false
HashMap.class.isAssignableFrom(List.class); \\false

判断前面的一个类是否为后面的一个类的父类。其实我们用的最多的也就是这两个:List和Map。

实现

通过上面的分析,那最终的代码也就出来了:

   /**
     * 将对象转换成map
     *
     * @param prefix
     * @param o
     * @return
     */
    public static Map<String, String> getFiledsInfo(String prefix, Object o) {
        Map<String, String> params = new HashMap<>();
        if (null == o) {
            return new HashMap<>();
        }
        Class clazz = o.getClass();
        if (isJavaClass(clazz)) {
            if (Map.class.isAssignableFrom(clazz)) {
                Map<String, Object> map = (Map) o;
                for (Map.Entry<String, Object> val : map.entrySet()) {
                    String childPrefix = prefix + "[" + camelToUnderline(val.getKey(), 1) + "]";
                    params.putAll(getFiledsInfo(childPrefix, val.getValue()));
                }
            } else if (List.class.isAssignableFrom(clazz)) {
                List<Object> list = (List) o;
                int index = 0;
                for (Object val : list) {
                    String childPrefix = prefix + "[" + index + "]";
                    params.putAll(getFiledsInfo(childPrefix, val));
                    index++;
                }
            } else {
                params.put(prefix, o.toString());
            }
        } else {
            Field[] fields = o.getClass().getDeclaredFields();
            for (Field field : fields) {
                String childPrefix = prefix + (StringUtils.isEmpty(prefix) ? "" : ".") + camelToUnderline(field.getName(), 1);
                params.putAll(getFiledsInfo(childPrefix, getFieldValueByName(field.getName(), o, field.getType())));
            }
        }
        return params;
    }

    /**
     * 判断一个类是JAVA类型还是用户定义类型
     *
     * @param clz
     * @return
     */
    private static boolean isJavaClass(Class<?> clz) {
        return clz != null && clz.getClassLoader() == null;
    }


    /**
     * 根据属性名获取属性值
     */
    private static Object getFieldValueByName(String fieldName, Object o, Class clazz) {
        try {
            String firstLetter = fieldName.substring(0, 1).toUpperCase();
            String getter = "get" + firstLetter + fieldName.substring(1);
            if (clazz.equals(boolean.class)) {
                getter = "is" + firstLetter + fieldName.substring(1);
            }
            Method method = o.getClass().getMethod(getter, new Class[]{});
            Object value = method.invoke(o, new Object[]{});
            return value;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }


    /**
     * 驼峰赚下划线
     *
     * @param param    驼峰字符
     * @param charType 统一转大写
     * @return
     */
    public static String camelToUnderline(String param, Integer charType) {
        if (param == null || "".equals(param.trim())) {
            return "";
        }
        int len = param.length();
        StringBuilder sb = new StringBuilder(len);
        for (int i = 0; i < len; i++) {
            char c = param.charAt(i);
            if (Character.isUpperCase(c)) {
                sb.append(UNDERLINE);
            }
            if (charType == 2) {
                sb.append(Character.toUpperCase(c));  //统一都转大写
            } else {
                sb.append(Character.toLowerCase(c));  //统一都转小写
            }


        }
        return sb.toString();
    }

由于银行文档里的字段全是下划线格式,而我们的公司命名规则全是驼峰形式的,所以这里面加了驼峰到下换线转换的代码。

键值对组字符串转对象

    一开始以为上面那个很难,其实写起来发现还好,但是下面这个真的有点难。有点可惜的我花了大功夫把这个实现了出来,最后发现行方要求我们这样给他们传对象,他们给我们传对象到时毫不含糊的传了个json过来,所以最后我又换回了json工具包。这都是后话了。

分析

    其实键值对转回对象就是上面的逆过程,但是处理的时候就遇到一个问题,按道理我们以"."为分界,但是写下来发现[]里面的其实也是一个节点,所以一开始把字符串处理了一下:

for (Map.Entry<String, String> entry : map.entrySet()) {
    String key = entry.getKey().replaceAll("]", "");
    key = key.replaceAll("\\[", ".[");
}

把形如"[name]“这样的换成了”.[name"这样的来处理,然后通过"."划分节点。
    类其实就是一个树状结构,我写之前在思考要不要先把键值对换成树状结构,一开始没有这么做,后来还是这样做了,两点思考:

  1. 如果不先转换成树结构,那每次新解析一个键值对,都要从对象的根节点往下遍历,如果转换成树状结构后,就不用每次都从根节点重新遍历了,简单估计了一下效率似乎要高一些(我也没验证过)
  2. 第二点是关键点,就是不转换成树状结构,list不是很好排序,必须要全部处理完之后,再遍历整个数,把是list 的进行排序。而转换成树之后,每次递归回到上一层,就可以进行排序了。

    和上面对象转字符一样,这里也需要考虑几个java问题:

  1. 如何把一个字符串转换成任意基本类型(包括他们的包装类和String),这是值的处理。键值对里值都是字符串,但类里面,最终的值都是基本数据类型。直接强转肯定不行的,网上找了好久,没找到好的处理方法,最终自己这样实现了:
    /**
     * 将字符串转换成任意基本类型
     *
     * @param val
     * @param fieldClass
     * @return
     */
    private static Object castString(String val, Class fieldClass) {
        if (val == null || val.equals("null")) {
            return null;
        }
        if (fieldClass.equals(int.class) || fieldClass.equals(Integer.class)) {
            return Integer.parseInt(val);
        }
        if (fieldClass.equals(boolean.class) || fieldClass.equals(Boolean.class)) {
            return Boolean.parseBoolean(val);
        }
        if (fieldClass.equals(float.class) || fieldClass.equals(Float.class)) {
            return Float.parseFloat(val);
        }
        if (fieldClass.equals(double.class) || fieldClass.equals(Double.class)) {
            return Float.parseFloat(val);
        }
        if (fieldClass.equals(char.class) || fieldClass.equals(Character.class)) {
            return val.charAt(0);
        }

        if (fieldClass.equals(String.class)) {
            return val;
        }
        return null;
    }
  1. 怎么才能拿到泛型的真实类型,比如List的E,和Map<V,W>的W,V一般就是字符串:
ParameterizedType parameterizedType = (ParameterizedType) field.getGenericType();
Type childNodeClassType = parameterizedType.getActualTypeArguments()[0];
  1. 怎么才能拿到泛型里的泛型呢?比如List<Map<String,String>>,虽然不多见,倒也是个场景,承接上面的代码,其实第二行拿出来的Type childNodeClassType还是一个ParameterizedType类型,所以继续getActualTypeArguments就好了:
ParameterizedType parameterizedType = (ParameterizedType) nodeClassType;
Type childType = parameterizedType.getActualTypeArguments()[0];
实现
    public static <T> T string2Bean(String str, Class<T> clazz) throws Exception {
       Map<String, String> map = (Map<String, String>) JSON.parse(str);
       Node root = new Node();
       root.setKey("root");
       root.setChildrenType(2);
       for (Map.Entry<String, String> entry : map.entrySet()) {
           String key = entry.getKey().replaceAll("]", "");
           key = key.replaceAll("\\[", ".[");
           str2Node(key, entry.getValue(), root);
       }
       return node2Bean(root, clazz);
    }


    private static <T> T node2Bean(Node node, Class<T> clazz) throws Exception {
        Object obj = clazz.newInstance();
        Field[] fields = clazz.getDeclaredFields();
        for (Node childNode : node.getChildren()) {
            for (Field childField : fields) {
                if (childField.getName().equals(childNode.getKey())) {
                    childNode2Bean(childNode, obj, node.getChildrenType(), childField.getType(), childField);
                    break;
                }
            }
        }
        return (T) obj;
    }

        /**
     * @param node          当前节点
     * @param parent        当前节点的所属对象
     * @param type          当前节点所属类型 1-list,2-bean
     * @param nodeClassType 当前节点的数据类型:如果是父节点是list,为list泛型的真实类型,如果父节点为类,则是这个字段的类型
     * @return
     * @throws Exception
     */
    public static void childNode2Bean(Node node, Object parent, int type, Type nodeClassType, Field field) throws Exception {
        if (type == 1) {
            handleList(node, parent, nodeClassType, field);
        } else if (type == 2) {
            handleBean(node, parent, nodeClassType, field);
        } else if (type == 3) {
            handleMap(node, parent, nodeClassType, field);
        }
    }



        public static void handleMap(Node node, Object parent, Type nodeClassType, Field field) throws Exception {
        Map map = (Map) parent;
        if (node.getChildrenType() == 0) {
            Class clazz = (Class) nodeClassType;
            map.put(node.getKey(), castString(node.getValue(), clazz));
        } else if (node.getChildrenType() == 1) {
            ParameterizedType parameterizedType = (ParameterizedType) nodeClassType;
            Type childType = parameterizedType.getActualTypeArguments()[0];
            List childList = new ArrayList(node.getChildren().size());
            for (Node childNode : node.getChildren()) {
                childNode2Bean(childNode, childList, node.getChildrenType(), childType, null);
            }
            map.put(node.getKey(), childList);
        } else if (node.getChildrenType() == 2) {
            handleChildBean(node, map, nodeClassType, field);
        } else if (node.getChildrenType() == 3) {
            ParameterizedType parameterizedType = (ParameterizedType) nodeClassType;
            Type childType = parameterizedType.getActualTypeArguments()[1];
            Map childMap = new HashMap();
            for (Node childNode : node.getChildren()) {
                childNode2Bean(childNode, childMap, node.getChildrenType(), childType, null);
            }
            map.put(node.getKey(), childMap);
        }
    }


        private static void handleList(Node node, Object parent, Type nodeClassType, Field field) throws Exception {
        List list = (List) parent;
        if (node.getChildrenType() == 0) {
            Class clazz = (Class) nodeClassType;
            list.add(Integer.parseInt(node.getKey()), castString(node.getValue(), clazz));
        } else if (node.getChildrenType() == 1) {
            ParameterizedType parameterizedType = (ParameterizedType) nodeClassType;
            Type childType = parameterizedType.getActualTypeArguments()[0];
            List childList = new ArrayList(node.getChildren().size());
            for (Node childNode : node.getChildren()) {
                childNode2Bean(childNode, childList, node.getChildrenType(), childType, null);
            }
            list.add(childList);
        } else if (node.getChildrenType() == 2) {
            handleChildBean(node, list, nodeClassType, field);
        } else if (node.getChildrenType() == 3) {

            ParameterizedType parameterizedType = (ParameterizedType) nodeClassType;
            Type childType = parameterizedType.getActualTypeArguments()[1];
            Map childMap = new HashMap();
            for (Node childNode : node.getChildren()) {
                childNode2Bean(childNode, childMap, node.getChildrenType(), childType, null);
            }
            list.add(childMap);
        }
    }

        private static void handleBean(Node node, Object parent, Type nodeClassType, Field field) throws Exception {
        if (node.getChildrenType() == 0) {
            Class clazz = (Class) nodeClassType;
            setFieldValueByName(node.getKey(), parent, node.getValue(), clazz);
        } else if (node.getChildrenType() == 1) {
            List list = new ArrayList(node.getChildren().size());
            for (Node child : node.getChildren()) {
                ParameterizedType parameterizedType = (ParameterizedType) field.getGenericType();
                Type childNodeClassType = parameterizedType.getActualTypeArguments()[0];
                childNode2Bean(child, list, node.getChildrenType(), childNodeClassType, null);
            }
            setFieldValueByName(node.getKey(), parent, list, List.class);
        } else if (node.getChildrenType() == 2) {
            handleChildBean(node, parent, nodeClassType, field);
        } else if (node.getChildrenType() == 3) {

            ParameterizedType parameterizedType = (ParameterizedType) field.getGenericType();
            Type childType = parameterizedType.getActualTypeArguments()[1];
            Map childMap = new HashMap();
            for (Node childNode : node.getChildren()) {
                childNode2Bean(childNode, childMap, node.getChildrenType(), childType, null);
            }
            setFieldValueByName(node.getKey(), parent, childMap, Map.class);
        }
    }


    
    private static void handleChildBean(Node node, Object parent, Type nodeClassType, Field field) throws Exception {
        Class clazz = (Class) nodeClassType;
        Object obj = clazz.newInstance();
        Field[] fields = clazz.getDeclaredFields();
        for (Node childNode : node.getChildren()) {
            for (Field childField : fields) {
                if (childField.getName().equals(childNode.getKey())) {
                    childNode2Bean(childNode, obj, node.getChildrenType(), childField.getType(), childField);
                    break;
                }
            }
        }
        if (parent instanceof List) {
            ((List) ((List) parent)).add(obj);
        } else if (parent instanceof Map) {
            ((Map) parent).put(node.getKey(), obj);
        } else {
            setFieldValueByName(node.getKey(), parent, obj, clazz);
        }
    }



   private static void str2Node(String key, String val, Node parentNode) {
        int indexOfPoint = key.indexOf(".");
        String realKey = indexOfPoint > 0 ? (key.substring(0, indexOfPoint)) : key;
        boolean isOver = indexOfPoint < 0 ? true : false;

        int type = 2;
        int begin = realKey.indexOf("[");
        String keyName = begin >= 0 ? realKey.substring(1) : realKey;
        if (begin == 0 && StringUtils.isAllNumber(keyName)) {
            type = 1;
        } else if (begin == 0) {
            type = 3;
        }
        parentNode.setChildrenType(type);


        Node node = null;
        boolean nodeIsExist = false;
        if (parentNode.getChildren() != null) {
            for (Node existNode : parentNode.getChildren()) {
                if (existNode.getKey().equals(keyName)) {
                    node = existNode;
                    nodeIsExist = true;
                }
            }
        }
        if (!nodeIsExist) {
            node = new Node();
            node.setKey(underlineToCamel(keyName));

            if (isOver) {
                node.setValue(val);
            } else {
                str2Node(key.substring(indexOfPoint + 1), val, node);
            }

            List<Node> nodeList = parentNode.getChildren();
            if (nodeList == null) {
                nodeList = new ArrayList<>();
                parentNode.setChildren(nodeList);
            }
            nodeList.add(node);
        } else {
            str2Node(key.substring(indexOfPoint + 1), val, node);
        }
    }

后记

    后来实际测下来发现,银行给我们返回的时候给我们的是标准的json格式,所以最后字符串转对象,没有用上,所以如果要用这部分代码,最好自己再测试和调试一下。但换句话说回来了,为啥银行要求我们要转成这种格式给他们,他们给我们就变成了json了。或许他们发现自己实现bean转这种格式的键值对有点头秃。那大家可能互相,难道这种键值对转对象他们就不投秃了,其实这个是被springmvc给实现了的。如果我们仔细的话,在网页上通过表单网后端传数据的时候,list其实就是users[0].name类似这种格式网后端传的,而后端springmvc里,我们直接给一个对应的对象,就能接到数据了。

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
JavaScript正则表达式Java正则表达式在语法上有一些差异,但是它们的基本概念和用法是相似的。下面是将JavaScript正则表达式转换Java正则表达式的一些常见规则: 1. 语法差异: - JavaScript正则表达式使用斜杠(/)作为定界符,而Java正则表达式使用双引号(")作为定界符。 - JavaScript正则表达式中的特殊字符需要进行转义,而Java正则表达式中的特殊字符不需要转义。 2. 字符类: - JavaScript正则表达式中的字符类使用方括号([])表示,而Java正则表达式中使用方括号([])或者Unicode转义(\p{...})表示。 - JavaScript正则表达式中的字符类可以使用连字符(-)表示范围,而Java正则表达式中需要使用Unicode转义(\uXXXX)表示范围。 3. 量词: - JavaScript正则表达式中的量词使用花括号({})表示,而Java正则表达式中使用花括号({})或者问号(?)表示。 - JavaScript正则表达式中的贪婪量词默认是贪婪模式,而Java正则表达式中的贪婪量词需要在后面添加问号(?)来表示非贪婪模式。 4. 边界匹配: - JavaScript正则表达式中的边界匹配使用插入符号(^)和美元符号($)表示,而Java正则表达式中使用\A和\Z表示。 5. 其他差异: - JavaScript正则表达式中的捕获组使用圆括号(())表示,而Java正则表达式中使用圆括号(())或者方括号([])表示。 - JavaScript正则表达式中的反向引用使用反斜杠加数字(\1、\2等)表示,而Java正则表达式中使用美元符号加数字($1、$2等)表示。 以上是一些常见的JavaScript正则表达式转换Java正则表达式的规则。具体转换时,还需要根据具体的正则表达式进行适当的调整。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值