ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

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

2022-01-31 21:33:00  阅读:162  来源: 互联网

标签:Map 面向对象 item price public 嵌套 JSON id String


  能不能把这个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": "E20221013174712025",

  "item:order_no:18008888": "E20221013174712025",

  "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

  @Setter

  public class ItemCore {

  private String id;

  private String num;

  private Item item;

  private ItemPrice itemPrice;

  private List itemPriceChangeLogs;

  }

  @Getter

  @Setter

  public class Item {

  private String sId;

  private String gId;

  private String num;

  private String orderNo;

  private String id;

  private String itemCoreId;

  }

  @Getter

  @Setter

  public class ItemPrice {

  private String itemId;

  private String price;

  private String id;

  }

  @Getter

  @Setter

  public class ItemPriceChangeLog {

  private String id;

  private String itemId;

  private String detail;

  }

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

  明确了目标对象,就成功了 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="{

  "

  + " "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": "E20221013174712025",

  "

  + " "item:order_no:18008888": "E20221013174712025",

  "

  + " "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"

  "

  + "}";

  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<String, Map<String,Object>> group(String json) {

  Map<String, Object> map=JSON.parseObject(json);

  Map<String, Map<String,Object>> groupedMaps=new HashMap();

  map.forEach(

  (keyInJson, value) -> {

  TableField tableField=TableField.buildFrom(keyInJson);

  String key=tableField.getTablename() + ":" + tableField.getId();

  Map<String,Object> mapForKey=groupedMaps.getOrDefault(key, new HashMap<>());

  mapForKey.put(tableField.getField(), value);

  groupedMaps.put(key, mapForKey);

  }

  );

  return groupedMaps;

  }

  public static Map<String, Map<String,Object>> underline2camelForMap(Map<String, Map<String,Object>> underlined) {

  Map<String, Map<String,Object>> groupedMapsCamel=new HashMap<>();

  Set ignoreSets=new HashSet();

  underlined.forEach(

  (key, mapForKey) -> {

  Map<String,Object> keytoCamel=TransferUtil.generalMapProcess(mapForKey, TransferUtil::underlineToCamel, ignoreSets);

  groupedMapsCamel.put(key, keytoCamel);

  }

  );

  return groupedMapsCamel;

  }

  /**

  * 将分组后的子map先转成相应单个对象,再按照某个key值进行关联

  */

  public static Order relate(Map<String, Map<String,Object>> 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(CollectorsingBy(

  Item::getItemCoreId

  ));

  Map> itemPriceMap=itemPrices.stream().collect(CollectorsingBy(

  ItemPrice::getItemId

  ));

  Map> itemPriceChangeLogMap=itemPriceChangeLogs.stream().collect(CollectorsingBy(

  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;

  }

  }

  @Data

  public 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.apachemons.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<String, Map<String,Object>> 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<String, BizObjects> objMapping;

  public ObjectMapping() {

  objMapping=new HashMap<>();

  objMapping.put("item", new BizObjects<Item,String>(Item.class, new HashMap<>(), Item::getItemCoreId));

  objMapping.put("item_core", new BizObjects<ItemCore,String>(ItemCore.class, new HashMap<>(), ItemCore::getId));

  objMapping.put("item_price", new BizObjects<ItemPrice,String>(ItemPrice.class, new HashMap<>(), ItemPrice::getItemId));

  objMapping.put("item_price_change_log", new BizObjects<ItemPriceChangeLog,String>(ItemPriceChangeLog.class, new HashMap<>(), ItemPriceChangeLog::getItemId));

  }

  public ObjectMapping FillFrom(Map<String, Map<String,Object>> 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<String, List> 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.utilctionction;

  public class BizObjects<T, K> {

  private Class cls;

  private Map<K, List> map;

  private Function<T, K> keyFunc;

  public BizObjects(Class cls, Map<K,List> map, Function<T,K> 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<K, List> 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 版本):

  PropertyDescriptor descriptor=null;

  try {

  descriptor=getPropertyUtils().getPropertyDescriptor(target, name);

  if (descriptor==null) {

  return; // Skip this property setter

  }

  } catch (final NoSuchMethodException e) {

  return; // Skip this property setter

  }

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

  为什么 itemPropertyDescriptors 里的 key 是 GId 呢? 进一步跟踪到 propertyDescriptors 的生成,在

  Introspector.getTargetPropertyInfo 方法中,是根据属性的 getter/setter 方法来生成 propertyDescriptor 的 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字符串转成对应的嵌套对象。 当处理复杂业务关联的数据时,相比过程式的思维,转换为对象的视角会更容易处理和使用。

标签:Map,面向对象,item,price,public,嵌套,JSON,id,String
来源: https://www.cnblogs.com/ebuybay/p/15858634.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有