摘要: 使用Java语言递归地将Map里的字段名由驼峰转下划线。经过此例能够学习如何递归地解析任意嵌套的List-Map容器结构。php
难度:初级
java
在进行多语言混合编程时,因为编程规范的不一样, 有时会须要进行字段名的驼峰-下划线转换。好比 php 语言中,变量偏向于使用下划线,而Java 语言中,变量偏向于驼峰式。当 PHP 调用 java 接口时,就须要将 java 返回数据结构中的驼峰式的字段名称改成下划线。使用 jackson 解析 json 数据时,若是不指定解析的类,经常会将 json 数据转化为 LinkedHashMap 。 所以,须要将 LinkedHashMap 里的字段名由驼峰转为下划线。正则表达式
这里的难点是, Map 结构多是很是复杂的嵌套结构,一个 Map 的某个 key 对应的 value 多是原子的,好比字符串,整数等,多是嵌套的 Map, 也多是含有多个 Map 的 List , 而 map , list 内部又可能嵌套任意的 map 或 list . 所以,要使用递归的算法来将 value 里的 key 递归地转化为下划线。算法
首先,要编写一个基本函数 camelToUnderline,将一个字符串的值从驼峰转下划线。这个函数不难,逐字符处理,遇到大写字母就转成 _ + 小写字母 ; 或者使用正则表达式替换亦可;编程
其次,须要使用基本函数 camelToUnderline 对可能多层嵌套的 Map 结构进行转化。json
假设有一个函数 transferKeysFromCamelToUnderline(amap) , 能够将 amap 里的全部 key 从驼峰转化为下划线,包括 amap 里嵌套的任意 map。返回结果是 resultMap ;数据结构
(1) 首先考虑最简单的状况:没有任何嵌套的状况,原子类型的 key 对应原子类型的 value ; resultMap.put(newkey, value) 便可 , newkey = camelToUnderline(key);app
(2) 其次考虑 Map 含有嵌套 subMap 的状况: 假设 <key, value> 中,value 是一个 subMap, 那么,调用 camelToUnderline(key) 能够获得新的 newkey ,调用 transferKeysFromCamelToUnderline(subMap) 就获得转换了的 newValue , 获得 <newkey, newValue>; resultMap.put(newkey, newValue)函数
(3) 其次考虑 Map 含有 List 的状况: List 里一般含有多个 subMap , 只要遍历里面的 subMap 进行转换并添加到新的 List, 里面含有全部转换了的 newValue = map(transferKeysFromCamelToUnderline, List[subMap]); resultMap.put(newkey, newValue) .学习
当使用递归方式思考的时候,有三个技巧值得注意:
(1) 首先,必定从最简单的状况开始思考。 这是基础,也是递归终结条件;
(2) 其次,要善于从语义上去理解,而不是从细节上。 好比说 Map 含有嵌套 subMap 的时候, 就不要细想 subMap 里面是怎样复杂的结构,是单纯的一个子 map ,仍是含有 List 的 Map 的 Map 的 Map; 这样想会Feng掉滴^_^ 只须要知道 transferKeysFromCamelToUnderline(amap) 可以对任意复杂结构的 amap 进行转换获得全部 key 转换了的 resultMap , 而在主流程中直接使用这个 subResultMap 便可。这个技巧值得多体会多训练下才能掌握。
(3) 结果的存放。 既能够放在参数里,在递归调用的过程当中逐步添加完善,也能够放在返回结果中。代码实现中展现了两种的用法。从某种意义来讲,递归特别须要仔细地设计接口 transferKeysFromCamelToUnderline ,并从接口的语义上去理解和递归使用。
/** * Created by shuqin on 16/11/3. * Improved by shuqin on 17/12/31. */ package zzz.study.datastructure.map; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.function.BiFunction; import java.util.function.Function; public class TransferUtil { public static final char UNDERLINE='_'; public static String camelToUnderline(String origin){ return stringProcess( origin, (prev, c) -> { if (Character.isLowerCase(prev) && Character.isUpperCase(c)) { return "" + UNDERLINE + Character.toLowerCase(c); } return ""+c; } ); } public static String underlineToCamel(String origin) { return stringProcess( origin, (prev, c) -> { if (prev == '_' && Character.isLowerCase(c)) { return "" + Character.toUpperCase(c); } if (c == '_') { return ""; } return ""+c; } ); } public static String stringProcess(String origin, BiFunction<Character, Character, String> convertFunc) { if (origin==null||"".equals(origin.trim())){ return ""; } String newOrigin = "0" + origin; StringBuilder sb=new StringBuilder(); for (int i = 0; i < newOrigin.length()-1; i++) { char prev = newOrigin.charAt(i); char c=newOrigin.charAt(i+1); sb.append(convertFunc.apply(prev,c)); } return sb.toString(); } public static void tranferKeyToUnderline(Map<String,Object> map, Map<String,Object> resultMap, Set<String> ignoreKeys) { Set<Map.Entry<String,Object>> entries = map.entrySet(); for (Map.Entry<String, Object> entry: entries) { String key = entry.getKey(); Object value = entry.getValue(); if (ignoreKeys.contains(key)) { resultMap.put(key, value); continue; } String newkey = camelToUnderline(key); if ( (value instanceof List) ) { List newList = buildValueList( (List) value, ignoreKeys, (m, keys) -> { Map subResultMap = new HashMap(); tranferKeyToUnderline((Map) m, subResultMap, ignoreKeys); return subResultMap; }); resultMap.put(newkey, newList); } else if (value instanceof Map) { Map<String, Object> subResultMap = new HashMap<String,Object>(); tranferKeyToUnderline((Map)value, subResultMap, ignoreKeys); resultMap.put(newkey, subResultMap); } else { resultMap.put(newkey, value); } } } public static Map<String,Object> tranferKeyToUnderline2(Map<String,Object> map, Set<String> ignoreKeys) { Set<Map.Entry<String,Object>> entries = map.entrySet(); Map<String,Object> resultMap = new HashMap<String,Object>(); for (Map.Entry<String, Object> entry: entries) { String key = entry.getKey(); Object value = entry.getValue(); if (ignoreKeys.contains(key)) { resultMap.put(key, value); continue; } String newkey = camelToUnderline(key); if ( (value instanceof List) ) { List valList = buildValueList((List)value, ignoreKeys, (m, keys) -> tranferKeyToUnderline2(m, keys)); resultMap.put(newkey, valList); } else if (value instanceof Map) { Map<String, Object> subResultMap = tranferKeyToUnderline2((Map)value, ignoreKeys); resultMap.put(newkey, subResultMap); } else { resultMap.put(newkey, value); } } return resultMap; } public static List buildValueList(List valList, Set<String> ignoreKeys, BiFunction<Map, Set, Map> transferFunc) { if (valList == null || valList.size() == 0) { return valList; } Object first = valList.get(0); if (!(first instanceof List) && !(first instanceof Map)) { return valList; } List newList = new ArrayList(); for (Object val: valList) { Map<String,Object> subResultMap = transferFunc.apply((Map) val, ignoreKeys); newList.add(subResultMap); } return newList; } public static Map<String,Object> generalMapProcess(Map<String,Object> map, Function<String, String> keyFunc, Set<String> ignoreKeys) { Map<String,Object> resultMap = new HashMap<String,Object>(); map.forEach( (key, value) -> { if (ignoreKeys.contains(key)) { resultMap.put(key, value); } else { String newkey = keyFunc.apply(key); if ( (value instanceof List) ) { resultMap.put(keyFunc.apply(key), buildValueList((List) value, ignoreKeys, (m, keys) -> generalMapProcess(m, keyFunc, ignoreKeys))); } else if (value instanceof Map) { Map<String, Object> subResultMap = generalMapProcess((Map) value, keyFunc, ignoreKeys); resultMap.put(newkey, subResultMap); } else { resultMap.put(keyFunc.apply(key), value); } } } ); return resultMap; } }
不管是下划线转驼峰,仍是驼峰转下划线,须要将两个字符做为一个组块进行处理,根据两个字符的状况来判断和转化成特定字符。好比下划线转驼峰,就是 _x => 到 X, 驼峰转下划线,就是 xY => x_y。 采用了前面补零的技巧,是的第一个字符与其余字符均可以用相同的算法来处理(若是不补零的话,第一个字符就必须单独处理)。
为了达到通用性,这里也使用了函数接口 BiFunction<Character, Character, String> convertFunc ,将指定的两个字符转换成指定的字符串。流程仍然是相同的:采用逐字符处理。
细心的读者会发现一个小BUG,就是当List里的元素不是Map时,好比 "buyList": ["Food","Dress","Daily"], 程序会抛异常:Cannot cast to java.util.Map。 怎么修复呢? 须要抽离出个函数,专门对 List[E] 的值作处理,这里 E 不是 Map 也不是List。这里不考虑 List 直接嵌套 List 的状况。
public static List buildValueList(List valList, Set<String> ignoreKeys, BiFunction<Map, Set, Map> transferFunc) { if (valList == null || valList.size() == 0) { return valList; } Object first = valList.get(0); if (!(first instanceof List) && !(first instanceof Map)) { return valList; } List newList = new ArrayList(); for (Object val: valList) { Map<String,Object> subResultMap = transferFunc.apply((Map) val, ignoreKeys); newList.add(subResultMap); } return newList; }
因为 buildValueList 须要回调tranferKeyToUnderlineX 来生成转换后的Map,这里使用了BiFunction<Map, Set, Map> transferFunc。相应的, tranferKeyToUnderline 和 tranferKeyToUnderline2 的列表处理要改为:
tranferKeyToUnderline: if ( (value instanceof List) ) { List newList = buildValueList( (List) value, ignoreKeys, (m, keys) -> { Map subResultMap = new HashMap(); tranferKeyToUnderline((Map) m, subResultMap, ignoreKeys); return subResultMap; }); resultMap.put(newkey, newList); } tranferKeyToUnderline2: if ( (value instanceof List) ) { List valList = buildValueList((List)value, ignoreKeys, (m, keys) -> tranferKeyToUnderline2(m, keys)); resultMap.put(newkey, valList); }
对于复杂Map结构的处理,写一遍不容易,若是要作相似处理,是否能够复用上述处理流程呢? 上述主要的不一样在于 key 的处理。只要传入 key 的处理函数keyFunc便可。这样,当须要从下划线转驼峰时,就不须要复制代码,而后只改动一行了。代码以下所示,使用了 Java8Map 遍历方式使得代码更加简洁可读。
public static Map<String,Object> generalMapProcess(Map<String,Object> map, Function<String, String> keyFunc, Set<String> ignoreKeys) { Map<String,Object> resultMap = new HashMap<String,Object>(); map.forEach( (key, value) -> { if (ignoreKeys.contains(key)) { resultMap.put(key, value); } else { String newkey = keyFunc.apply(key); if ( (value instanceof List) ) { resultMap.put(keyFunc.apply(key), buildValueList((List) value, ignoreKeys, (m, keys) -> generalMapProcess(m, keyFunc, ignoreKeys))); } else if (value instanceof Map) { Map<String, Object> subResultMap = generalMapProcess((Map) value, keyFunc, ignoreKeys); resultMap.put(newkey, subResultMap); } else { resultMap.put(keyFunc.apply(key), value); } } } ); return resultMap; }
使用Groovy来对上述代码进行单测。由于Groovy能够提供很是方便的Map构造。单测代码以下所示:
TransferUtils.groovy
package cc.lovesq.study.test import zzz.study.datastructure.map.TransferUtil import static zzz.study.datastructure.map.TransferUtil.* /** * Created by shuqin on 17/12/31. */ class TransferUtilTest { static void main(String[] args) { [null, "", " "].each { assert "" == camelToUnderline(it) } ["isBuyGoods": "is_buy_goods", "feeling": "feeling", "G":"G", "GG": "GG"].each { key, value -> assert camelToUnderline(key) == value } [null, "", " "].each { assert "" == underlineToCamel(it) } ["is_buy_goods": "isBuyGoods", "feeling": "feeling", "b":"b", "_b":"B"].each { key, value -> assert underlineToCamel(key) == value } def amap = ["code": 200, "msg": "successful", "data": [ "total": 2, "list": [ ["isBuyGoods": "a little", "feeling": ["isHappy": "ok"]], ["isBuyGoods": "ee", "feeling": ["isHappy": "haha"]], ], "extraInfo": [ "totalFee": 1500, "totalTime": "3d", "nestInfo": [ "travelDestination": "xiada", "isIgnored": true ], "buyList": ["Food","Dress","Daily"] ] ], "extendInfo": [ "involveNumber": "40", ] ] def resultMap = [:] def ignoreSets = new HashSet() ignoreSets.add("isIgnored") tranferKeyToUnderline(amap, resultMap, ignoreSets) println(resultMap) def resultMap2 = tranferKeyToUnderline2(amap, ignoreSets) println(resultMap2) def resultMap3 = generalMapProcess(amap, TransferUtil.&camelToUnderline, ignoreSets) println(resultMap3) def resultMap4 = generalMapProcess(resultMap3, TransferUtil.&underlineToCamel, ignoreSets) println(resultMap4) } }
本文使用Java语言递归地将复杂的嵌套Map里的字段名由驼峰转下划线,并给出了更通用的代码形式,同时展现了如何处理复杂的嵌套结构。复杂的结构老是由简单的子结构经过组合和嵌套而构成,经过对子结构分而治之,而后使用递归技术来组合结果,从而实现对复杂结构的处理。
经过此例,学习了: