原标题:做一次面向对象的体操:将 JSON 字符串转换为嵌套对象的一种方法

来源:琴水玉 ,

www.cnblogs.com/lovesqcc/p/9478678.html

程序员共读整理发布,转载请联系作者获得授权

背景与问题

在 《一个略复杂的数据映射聚合例子及代码重构》 一文中,将一个JSON字符串转成了所需要的订单信息Map。尽管做了代码重构和配置化,过程式的代码仍然显得晦涩难懂,并且客户端使用Map也非常难受。

https://www.cnblogs.com/lovesqcc/p/7812875.html

能不能把这个JSON串转成相应的对象,更易于使用呢? 为了方便讲解,这里重复写下JSON串。

{ "item:s_id:18006666": "1024", "item:s_id:18008888": "1024", "item:g_id:18006666": "6666", "item:g_id:18008888": "8888", "item:num:18008888": "8", "item:num:18006666": "6", "item:item_core_id:18006666": "9876666", "item:item_core_id:18008888": "9878888", "item:order_no:18006666": "E20171013174712025", "item:order_no:18008888": "E20171013174712025", "item:id:18008888": "18008888", "item:id:18006666": "18006666", "item_core:num:9878888": "8", "item_core:num:9876666": "6", "item_core:id:9876666": "9876666", "item_core:id:9878888": "9878888", "item_price:item_id:1000": "9876666", "item_price:item_id:2000": "9878888", "item_price:price:1000": "100", "item_price:price:2000": "200", "item_price:id:2000": "2000", "item_price:id:1000": "1000", "item_price_change_log:id:1111": "1111", "item_price_change_log:id:2222": "2222", "item_price_change_log:item_id:1111": "9876666", "item_price_change_log:item_id:2222": "9878888", "item_price_change_log:detail:1111": "haha1111", "item_price_change_log:detail:2222": "haha2222", "item_price_change_log:id:3333": "3333", "item_price_change_log:id:4444": "4444", "item_price_change_log:item_id:3333": "9876666", "item_price_change_log:item_id:4444": "9878888", "item_price_change_log:detail:3333": "haha3333", "item_price_change_log:detail:4444": "haha4444"}

思路与实现

要解决这个问题,需要有一个清晰的思路。

首先,需要知道应该转成怎样的目标对象。

其次,需要找到一种方法,建立从JSON串到目标对象的桥梁。

推断目标对象

仔细观察可知,每个 key 都是 tablename:field:id 组成,其中 table:id 相同的可以构成一个对象的数据; 此外,不同的tablename 对应不同的对象,而这些对象之间可以通过相同的 itemId 关联。

根据对JSON字符串的仔细分析(尤其是字段的关联性),可以知道: 目标对象应该类似如下嵌套对象:

@Getter@Setterpublic class ItemCore { private String id; private String num; private Item item; private ItemPrice itemPrice; private List itemPriceChangeLogs;}@Getter@Setterpublic class Item { private String sId; private String gId; private String num; private String orderNo; private String id; private String itemCoreId;}@Getter@Setterpublic class ItemPrice { private String itemId; private String price; private String id;}@Getter@Setterpublic class ItemPriceChangeLog { private String id; private String itemId; private String detail;}

注意到,对象里的属性是驼峰式,JSON串里的字段是下划线,遵循各自领域内的命名惯例。这里需要用到一个函数,将Map的key从下划线转成驼峰。这个方法在 《Java实现递归将嵌套Map里的字段名由驼峰转为下划线》 给出。

https://www.cnblogs.com/lovesqcc/p/6083904.html

明确了目标对象,就成功了 30%。 接下来,需要找到一种方法,从指定字符串转换到这个对象。

算法设计

由于 JSON 并不是与对象结构对应的嵌套结构。需要先转成容易处理的Map对象。这里的一种思路是,

STEP1: 将 table:id 相同的字段及值分组聚合,得到 Map[tablename:id, mapForKey[field, value]];

STEP2: 将每个 mapForKey[field, value] 转成 tablename 对应的单个对象 Item, ItemCore, ItemPrice, ItemPriceChangeLog;

STEP3: 然后根据 itemId 来关联这些对象,组成最终对象。

代码实现

package zzz.study.algorithm.object;import com.alibaba.fastjson.JSON;import java.util.ArrayList;import java.util.HashMap;import java.util.HashSet;import java.util.List;import java.util.Map;import java.util.Set;import java.util.stream.Collectors;import zzz.study.datastructure.map.TransferUtil;import static zzz.study.utils.BeanUtil.map2Bean;public class MapToObject { private static final String json = "{n" + " "item:s_id:18006666": "1024",n" + " "item:s_id:18008888": "1024",n" + " "item:g_id:18006666": "6666",n" + " "item:g_id:18008888": "8888",n" + " "item:num:18008888": "8",n" + " "item:num:18006666": "6",n" + " "item:item_core_id:18006666": "9876666",n" + " "item:item_core_id:18008888": "9878888",n" + " "item:order_no:18006666": "E20171013174712025",n" + " "item:order_no:18008888": "E20171013174712025",n" + " "item:id:18008888": "18008888",n" + " "item:id:18006666": "18006666",n" + " n" + " "item_core:num:9878888": "8",n" + " "item_core:num:9876666": "6",n" + " "item_core:id:9876666": "9876666",n" + " "item_core:id:9878888": "9878888",n" + "n" + " "item_price:item_id:1000": "9876666",n" + " "item_price:item_id:2000": "9878888",n" + " "item_price:price:1000": "100",n" + " "item_price:price:2000": "200",n" + " "item_price:id:2000": "2000",n" + " "item_price:id:1000": "1000",n" + "n" + " "item_price_change_log:id:1111": "1111",n" + " "item_price_change_log:id:2222": "2222",n" + " "item_price_change_log:item_id:1111": "9876666",n" + " "item_price_change_log:item_id:2222": "9878888",n" + " "item_price_change_log:detail:1111": "haha1111",n" + " "item_price_change_log:detail:2222": "haha2222",n" + " "item_price_change_log:id:3333": "3333",n" + " "item_price_change_log:id:4444": "4444",n" + " "item_price_change_log:item_id:3333": "9876666",n" + " "item_price_change_log:item_id:4444": "9878888",n" + " "item_price_change_log:detail:3333": "haha3333",n" + " "item_price_change_log:detail:4444": "haha4444"n" + "}"; public static void main(String[] args) { Order order = transferOrder(json); System.out.println(JSON.toJSONString(order)); } public static Order transferOrder(String json) { return relate(underline2camelForMap(group(json))); } /** * 转换成 Map[tablename:id => Map["field": value]] */ public static Map> group(String json) { Map map = JSON.parseObject(json); Map> groupedMaps = new HashMap(); map.forEach( (keyInJson, value) -> { TableField tableField = TableField.buildFrom(keyInJson); String key = tableField.getTablename() + ":" + tableField.getId(); Map mapForKey = groupedMaps.getOrDefault(key, new HashMap<>()); mapForKey.put(tableField.getField(), value); groupedMaps.put(key, mapForKey); } ); return groupedMaps; } public static Map> underline2camelForMap(Map> underlined) { Map> groupedMapsCamel = new HashMap<>(); Set ignoreSets = new HashSet(); underlined.forEach( (key, mapForKey) -> { Map keytoCamel = TransferUtil.generalMapProcess(mapForKey, TransferUtil::underlineToCamel, ignoreSets); groupedMapsCamel.put(key, keytoCamel); } ); return groupedMapsCamel; } /** * 将分组后的子map先转成相应单个对象,再按照某个key值进行关联 */ public static Order relate(Map> groupedMaps) { List items = new ArrayList<>(); List itemCores = new ArrayList<>(); List itemPrices = new ArrayList<>(); List itemPriceChangeLogs = new ArrayList<>(); groupedMaps.forEach( (key, mapForKey) -> { if (key.startsWith("item:")) { items.add(map2Bean(mapForKey, Item.class)); } else if (key.startsWith("item_core:")) { itemCores.add(map2Bean(mapForKey, ItemCore.class)); } else if (key.startsWith("item_price:")) { itemPrices.add(map2Bean(mapForKey, ItemPrice.class)); } else if (key.startsWith("item_price_change_log:")) { itemPriceChangeLogs.add(map2Bean(mapForKey, ItemPriceChangeLog.class)); } } ); Map> itemMap = items.stream().collect(Collectors.groupingBy( Item::getItemCoreId )); Map> itemPriceMap = itemPrices.stream().collect(Collectors.groupingBy( ItemPrice::getItemId )); Map> itemPriceChangeLogMap = itemPriceChangeLogs.stream().collect(Collectors.groupingBy( ItemPriceChangeLog::getItemId )); itemCores.forEach( itemCore -> { String itemId = itemCore.getId(); itemCore.setItem(itemMap.get(itemId).get(0)); itemCore.setItemPrice(itemPriceMap.get(itemId).get(0)); itemCore.setItemPriceChangeLogs(itemPriceChangeLogMap.get(itemId)); } ); Order order = new Order(); order.setItemCores(itemCores); return order; } } @Datapublic class TableField { String tablename; String field; String id; public TableField(String tablename, String field, String id) { this.tablename = tablename; this.field = field; this.id = id; } public static TableField buildFrom(String combined) { String[] parts = combined.split(":"); if (parts != null && parts.length == 3) { return new TableField(parts[0], parts[1], parts[2]); } throw new IllegalArgumentException(combined); }} package zzz.study.utils;import org.apache.commons.beanutils.BeanUtils;import java.util.Map;public class BeanUtil { public static T map2Bean(Map map, Class c) { try { T t = c.newInstance(); BeanUtils.populate(t, map); return t; } catch (Exception ex) { throw new RuntimeException(ex.getCause()); } }}

代码重构

group的实现已经不涉及具体业务。这里重点说下 relate 实现的优化。在实现中看到了 if-elseif-elseif-else 条件分支语句。是否可以做成配置化呢?

做配置化的关键在于:将关联项表达成配置。看看 relate 的前半段,实际上就是一个套路: 匹配某个前缀 – 转换为相应的Bean – 加入相应的对象列表。 后半段,需要根据关键字段(itemCoreId)来构建对象列表的 Map 方便做关联。因此,可以提取相应的配置项: (prefix, beanClass, BeanMap, BeanKeyFunc)。这个配置项抽象成 BizObjects , 整体配置构成 objMapping 对象。 在这个基础上,可以将代码重构如下:

public static Order relate2(Map> groupedMaps) { ObjectMapping objectMapping = new ObjectMapping(); objectMapping = objectMapping.FillFrom(groupedMaps); List finalItemCoreList = objectMapping.buildFinalList(); Order order = new Order(); order.setItemCores(finalItemCoreList); return order; }

ObjectMapping.java

package zzz.study.algorithm.object;import java.util.ArrayList;import java.util.HashMap;import java.util.List;import java.util.Map;import static zzz.study.utils.BeanUtil.map2Bean;public class ObjectMapping { Map objMapping; public ObjectMapping() { objMapping = new HashMap<>(); objMapping.put("item", new BizObjects(Item.class, new HashMap<>(), Item::getItemCoreId)); objMapping.put("item_core", new BizObjects(ItemCore.class, new HashMap<>(), ItemCore::getId)); objMapping.put("item_price", new BizObjects(ItemPrice.class, new HashMap<>(), ItemPrice::getItemId)); objMapping.put("item_price_change_log", new BizObjects(ItemPriceChangeLog.class, new HashMap<>(), ItemPriceChangeLog::getItemId)); } public ObjectMapping FillFrom(Map> groupedMaps) { groupedMaps.forEach( (key, mapForKey) -> { String prefixOfKey = key.split(":")[0]; BizObjects bizObjects = objMapping.get(prefixOfKey); bizObjects.add(map2Bean(mapForKey, bizObjects.getObjectClass())); } ); return this; } public List buildFinalList() { Map> itemCores = objMapping.get("item_core").getObjects(); List finalItemCoreList = new ArrayList<>(); itemCores.forEach( (itemCoreId, itemCoreList) -> { ItemCore itemCore = itemCoreList.get(0); itemCore.setItem((Item) objMapping.get("item").getSingle(itemCoreId)); itemCore.setItemPrice((ItemPrice) objMapping.get("item_price").getSingle(itemCoreId)); itemCore.setItemPriceChangeLogs(objMapping.get("item_price_change_log").get(itemCoreId)); finalItemCoreList.add(itemCore); } ); return finalItemCoreList; }}

BizObjects.java

package zzz.study.algorithm.object;import java.util.ArrayList;import java.util.Collections;import java.util.HashMap;import java.util.List;import java.util.Map;import java.util.function.Function;public class BizObjects { private Class cls; private Map> map; private Function keyFunc; public BizObjects(Class cls, Map> map, Function keyFunc) { this.cls = cls; this.map = (map != null ? map : new HashMap<>()); this.keyFunc = keyFunc; } public void add(T t) { K key = keyFunc.apply(t); List objs = map.getOrDefault(key, new ArrayList<>()); objs.add(t); map.put(key, objs); } public Class getObjectClass() { return cls; } public List get(K key) { return map.get(key); } public T getSingle(K key) { return (map != null && map.containsKey(key) && map.get(key).size() > 0) ? map.get(key).get(0) : null; } public Map> getObjects() { return Collections.unmodifiableMap(map); }}

新的实现的主要特点在于:

去掉了条件语句;

将转换为嵌套对象的重要配置与逻辑都集中到 objMapping ;

更加对象化的思维。

美中不足的是,大量使用了泛型来提高通用性,同时也牺牲了运行时安全的好处(需要强制类型转换)。 后半段关联对象,还是不够配置化,暂时没想到更好的方法。

为什么 BizObjects 里要用 Map 而不用 List 来表示多个对象呢 ? 因为后面需要根据 itemCoreId 来关联相应对象。如果用 List , 后续还要一个单独的 buildObjMap 操作。这里添加的时候就构建 Map ,将行为集中于 BizObjects 内部管理, 为后续配置化地关联对象留下一个空间。

一个小坑

运行结果会发现,转换后的 item 对象的属性 sId, gId 的值为 null 。纳尼 ? 这是怎么回事呢?

单步调试,运行后,会发现在 BeanUtilsBean.java 932 行有这样一行代码(用的是 commons-beanutils 的 1.9.3 版本):

PropertyDeor deor = null; try { deor = getPropertyUtils().getPropertyDeor(target, name); if (deor == null) { return; // Skip this property setter } } catch (final NoSuchMethodException e) { return; // Skip this property setter }

当 name = “gId” 时,会获取不到 deor 直接返回。 为什么获取不到呢,因为 Item propertyDeors 缓存里的 key是 GId ,而不是 gId !

为什么 itemPropertyDeors 里的 key 是 GId 呢? 进一步跟踪到 propertyDeors 的生成,在 Introspector.getTargetPropertyInfo 方法中,是根据属性的 getter/setter 方法来生成 propertyDeor 的 name 的。 最终定位的代码是 Introspector.decapitalize 方法:

public static String decapitalize(String name) { if (name == null || name.length() == 0) { return name; } if (name.length() > 1 && Character.isUpperCase(name.charAt(1)) && Character.isUpperCase(name.charAt(0))){ return name; } char chars[] = name.toCharArray(); chars[0] = Character.toLowerCase(chars[0]); return new String(chars); }

这里 name 是 getter/setter 方法的第四位开始的字符串。比如 gId 的 setter 方法为 setGId ,那么 name = GId 。根据这个方法得到的 name = GId ,也就是走到中间那个 if 分支了。 之所以这样,方法的解释是这样的:

This normally means converting the first * character from upper case to lower case, but in the (unusual) special * case when there is more than one character and both the first and * second characters are upper case, we leave it alone. * * Thus "FooBah" becomes "fooBah" and "X" becomes "x", but "URL" stays * as "URL".

真相大白! 当使用 BeanUtils.populate 将 map 转为对象时,对象的属性命名要尤其注意: 第二个字母不能是大写!

收工!

小结

本文展示了一种方法, 将具有内在关联性的JSON字符串转成对应的嵌套对象。 当处理复杂业务关联的数据时,相比过程式的思维,转换为对象的视角会更容易处理和使用。返回搜狐,查看更多

责任编辑:

java json转抽象对象_做一次面向对象的体操:将 JSON 字符串转换为嵌套对象的一种方法...相关推荐

  1. c语言 map转换成字符串数组,JSON数组形式字符串转换为ListMapString,String的几种方法...

    json转map通用方法 这里提供一方法,可以当作工具类,只要json字符串没有问题,嵌套几层都可以. import net.sf.json.JSONArray; import net.sf.json ...

  2. java equals 判断空_Java 判断字符串是否为空的三种方法与性能分析

    [java中判断字符串是否为数字的三种方法  1>用JAVA自带的函数 public static boolean isNumeric(String str){   for (int i = s ...

  3. 将指定日期字符串转换为Calendar对象

    应用格式化日期时间的java.text.SimpleDateFormat类 创建一个"yyyy-mm-dd"格式的格式化对象 SimpleDateFormat format=new ...

  4. Java 判断字符串是否为空的四种方法、优缺点与注意事项

    以下是Java 判断字符串是否为空的四种方法: 方法一: 最多人使用的一个方法, 直观, 方便, 但效率很低: if(s == null ||"".equals(s)); 方法二: ...

  5. java字符串为空抛出异常_Java 判断字符串是否为空的四种方法,及效率比较。

    以下是Java 判断字符串是否为空的四种方法: 方法一: 最多人使用的一个方法, 直观, 方便, 但效率很低: if(s == null ||"".equals(s)); 方法二: ...

  6. P1125 笨小猴笨小猴的词汇量很小,所以每次做英语选择题的时候都很头疼。但是他找到了一种方法,经试验证明,用这种方法去选择选项的时候选对的几率非常大! 这种方法的具体描述如下:假设maxn是单词

    题目描述 笨小猴的词汇量很小,所以每次做英语选择题的时候都很头疼.但是他找到了一种方法,经试验证明,用这种方法去选择选项的时候选对的几率非常大! 这种方法的具体描述如下:假设maxn是单词中出现次数最 ...

  7. String字符串转换为实体对象

    将一个String字符串转换为实体对象 LibraryGdsFaxingCatgDTO类 public class LibraryGdsFaxingCatgDTO {/*** 层级展示*/privat ...

  8. java this关键字的使用_做java两年了,构造方法和方法重载还是搞不明白?一文帮你搞定...

    成员方法 类成员主要包括成员变量和成员方法. 带参数的成员方法 带参数的成员方法可以接受用户输入的内容.创建带参数的方法时定义的参数叫形式参数,即形参:调用方法时传入的参数叫实际参数,即实参. 语法结 ...

  9. java将字符串转成对象_java – 将字符串转换为类对象

    I am storing a class object into a string using toString() method. Now, I want to convert the string ...

最新文章

  1. PhoneGap 安装体验
  2. 如何去调试前端JS代码?以Chrome谷歌浏览器为例
  3. python常用正则表达式_Python3常用正则表达式
  4. selenium 保持窗口一直开启_Python+selenium自动化测试
  5. php mysql多表关联删除_MySQL中多表删除方法
  6. A2D JS框架 - loadScript实现
  7. 常用软件版本查看Windows下
  8. Funcode学习笔记:写一个维护性高、扩展性强的框架【By Myself】【C++】
  9. VB.NET利用WMI获取操作系统所在硬盘序列号
  10. java 给图片添加水印_java 为图片添加水印(文字水印、图片水印)
  11. Improved Zero-shot Neural Machine Translation via Ignoring Spurious Correlations
  12. STM32学习笔记一:开发环境
  13. android 网页存储,90%的人不懂将网页永久保存在手机里的方法
  14. 有趣好玩的Linux之代码雨效果
  15. lgv50进入工程模式_LG手机工程模式进入方法及菜单指令翻译(适用G6、G7、V20、V30等)...
  16. 电脑视频加水印软件哪个好?这些软件值得收藏
  17. 国企招聘计算机笔试考什么区别,国企秋招要来了!看看你能报考哪家国企!
  18. 计算机科学ESI 2020年7月,2020年7月ESI学科高水平论文134篇
  19. javascript视频倍速播放
  20. 区块链丨拒绝虚假信息,优化你的网络社交生活

热门文章

  1. 【OC】状态估计(3) 卡尔曼滤波B
  2. 怎样阅读论文(台湾彭明辉)
  3. 趣味数学:解24点游戏小技巧
  4. Navicat Premium 15的使用
  5. 背单词-项目设计文档
  6. 基于最小二乘法的磁力计椭球拟合方法
  7. matlab 电力电子元件对应名称,MATLAB在电力电子技术的应用
  8. 信息安全方向就业要考哪些证书?
  9. 历史上消失的十天--儒略日(Julian Days)的来历
  10. 笔记本计算机电池没有电,笔记本主板电池没电会影响开机吗