摘要: 使用Java语言递归地将Map里的字段名由驼峰转下划线。通过此例可以学习如何递归地解析任意嵌套的List-Map容器结构。

难度:初级

概述###

在进行多语言混合编程时,由于编程规范的不同, 有时会需要进行字段名的驼峰-下划线转换。比如 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 结构进行转化。

假设有一个函数 transferKeysFromCamelToUnderline(amap) , 可以将 amap 里的所有 key 从驼峰转化为下划线,包括 amap 里嵌套的任意 map。返回结果是 resultMap ;

(1) 首先考虑最简单的情况:没有任何嵌套的情况,原子类型的 key 对应原子类型的 value ; resultMap.put(newkey, value) 即可 , newkey = camelToUnderline(key);

(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####

细心的读者会发现一个小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里的字段名由驼峰转下划线,并给出了更通用的代码形式,同时展示了如何处理复杂的嵌套结构。复杂的结构总是由简单的子结构通过组合和嵌套而构成,通过对子结构分而治之,然后使用递归技术来组合结果,从而实现对复杂结构的处理。

通过此例,学习了:

  • 设计递归程序来解析任意嵌套的List-Map容器结构;
  • 使用函数接口使代码更加通用化;
  • 使用Groovy来进行单测。

Java实现递归将嵌套Map里的字段名由驼峰转为下划线的更多相关文章

  1. 递归将Map里的字段名由驼峰转为下划线

    导航 定位 概述 算法设计 递归技巧 代码实现 定位 本文适合于想要使用Java递归地将Map里的Key字段名从驼峰转为下划线,或者想了解如何处理任意递归的Map结构的筒鞋. 概述 在进行多语言混合编 ...

  2. JAVA的驼峰和下划线互转帮助类

    实体类: import java.io.Serializable; import lombok.AllArgsConstructor; import lombok.Data; import lombo ...

  3. java驼峰法和下划线法字符串的相互转换

    java驼峰法和下划线法字符串的相互转换 1 import java.util.regex.Matcher; import java.util.regex.Pattern; public class ...

  4. vue里面的v-model的变量不要使用下划线

    遇到一个问题,就是如果变量名是text_right,的时候更改v-model的值,则text_right不会更新,如果改成textRight就会更新,目前还不知道原因,先记录下来

  5. Java驼峰和下划线互相转化

    直接上代码 : package com.utils; public class ChangeChar { public static final char UNDERLINE = '_'; publi ...

  6. java下划线与驼峰命名互转

    方式一: 下划线与驼峰命名转换: public class Tool { private static Pattern linePattern = Pattern.compile("_(\\ ...

  7. [原创]java WEB学习笔记59:Struts2学习之路---OGNL,值栈,读取对象栈中的对象的属性,读取 Context Map 里的对象的属性,调用字段和方法,数组,list,map

    本博客的目的:①总结自己的学习过程,相当于学习笔记 ②将自己的经验分享给大家,相互学习,互相交流,不可商用 内容难免出现问题,欢迎指正,交流,探讨,可以留言,也可以通过以下方式联系. 本人互联网技术爱 ...

  8. “全栈2019”Java第八十一章:外部类能否访问嵌套接口里的成员?

    难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java第 ...

  9. java List递归排序,传统方式和java8 Stream优化递归,无序的列表按照父级关系进行排序(两种排序类型)

    当有一个List列表是无序的,List中的数据有parentid进行关联,通过java排序成两种排序类型: 所用的测试列表最顶级无parentid,若为特殊值,修改下判断方法即可. 第一种排序:按照树 ...

随机推荐

  1. 【CF809D】Hitchhiking in the Baltic States Splay

    [CF809D]Hitchhiking in the Baltic States 题意:给你n个区间[li,ri],让你选出从中一个子序列,然后在子序列的每个区间里都选择一个tj,满足$t_1< ...

  2. numpy协方差矩阵numpy.cov

    numpy.cov(m, y=None, rowvar=True, bias=False, ddof=None, fweights=None, aweights=None)[source] Estim ...

  3. cURL 是一个功能强大的PHP库。

    使用PHP的cURL库可以简单和有效地去抓网页.你只需要运行一个脚本,然后分析一下你所抓取的网页,然后就可以以程序的方式得到你想要的数据了.无论是你想从从一个链接上取部分数据,或是取一个XML文件并把 ...

  4. 微信都在用的移动敏捷测试方法和工具|视频+PPT

    本文是腾讯优测总监雷彬在MPD2016 北京站上的演讲视频.他详细讲述了腾讯多年来在实践敏捷研发过程中测试的优化之路,为测试角色(包括测试工程师和开发自测)提供敏捷作业的思路.点击此处观看视频.时长5 ...

  5. opencv的基本数据类型CvPoint,CvSize,CvRect,CvScalar

    转自http://blog.csdn.net/gdut2015go/article/details/46301821 opencv的基本数据类型CvPoint,CvSize,CvRect,CvScal ...

  6. JavaScript简介与使用方法

    1.JavaScript简介 1.1.JavaScript简史 最初:网络通信很慢,网页上的数据要传送到数据库验证,然后再返回错误结果,找客观过程要等很久,于是,网景公司开发出一门新语言,当时Java ...

  7. MySQL ·InnoDB 文件系统之文件物理结构

    从上层的角度来看,InnoDB层的文件,除了redo日志外,基本上具有相当统一的结构,都是固定block大小,普遍使用的btree结构来管理数据.只是针对不同的block的应用场景会分配不同的页类型. ...

  8. 21.5.3 Updatable and Insertable Views

    http://dev.mysql.com/doc/refman/5.7/en/view-updatability.html Some views are updatable and reference ...

  9. python xlwt 设置单元格样式-合并单元格

    xlwt模块详解--合并单元格 import xlwtworkbook = xlwt.Workbook()worksheet = workbook.add_sheet('My sheet')# 合并第 ...

  10. Django - 日期、时间字段

    创建django的model时,有DateTimeField.DateField和TimeField三种类型可以用来创建日期字段,其值分别对应着datetime().date().time()三中对象 ...