欢迎访问昆山宝鼎软件有限公司网站! 设为首页 | 网站地图 | XML | RSS订阅 | 宝鼎邮箱 | 后台管理


新闻资讯

MENU

软件开发知识

做一次面向工具的体操:将JSON字符串转换为嵌套工具的一种要领

点击: 次  来源:劳务派遣管理系统 时间:2018-09-04

原文出处: 琴水玉

背景与问题

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

能不能把这个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
    @Setter
    public class ItemCore {
      private String id;
      private String num;
    
      private Item item;
    
      private ItemPrice itemPrice;
    
      private List<ItemPriceChangeLog> 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 = "{\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<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<String> 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<Item> items = new ArrayList<>();
        List<ItemCore> itemCores = new ArrayList<>();
        List<ItemPrice> itemPrices = new ArrayList<>();
        List<ItemPriceChangeLog> 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<String ,List<Item>> itemMap = items.stream().collect(Collectors.groupingBy(
            Item::getItemCoreId
        ));
        Map<String ,List<ItemPrice>> itemPriceMap = itemPrices.stream().collect(Collectors.groupingBy(
            ItemPrice::getItemId
        ));
        Map<String ,List<ItemPriceChangeLog>> 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;
      }
    
    }
    @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.apache.commons.beanutils.BeanUtils;
    
    import java.util.Map;
    
    public class BeanUtil {
    
      public static <T> T map2Bean(Map map, Class<T> 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 条件分支语句。是否可以做成配置化呢?