如何使用两张地图制作另一张地图?

How to use two maps and make another map?

我有两个 JSON,每个 JSON 我都在地图中加载它。为了使示例更简单,我缩短了 JSON.

第一个JSON

{
    "abc": "2",
    "plmtq": "hello+world",
    "lndp": "def",
    "yyt": "opi"
}

我正在将这个 JSON 作为 key:value 对加载到地图中。这里 abc 是键,2 是值。我们称之为 mapA。这是一个字符串到字符串的映射。

第二个JSON

{
    "count": {
        "linkedTo": "abc"
    },
    "title": {
        "linkedTo": "plmtq",
        "decode": "true"
    },
    "cross": {
        "linkedTo": "lndp"
    },
    "browse": {
        "linkedTo": "lndp"
    }
}

我也在地图中加载此 JSON。我们称之为 mapB。这是 Map<String, Map<Tag, String>>。所以在 mapB 中,键是 count,值是另一个映射,其中键是 linkedTo 标签枚举,值是 abc 字符串。其他人也一样。如果它使这个问题更简单,我也愿意为此使用不同的格式。

此处 Tag 是具有这些值的枚举 class。现在它只有两个值,但通常它有大约 7-8 个值。

  public enum Tag {
    linkedTo, decode;
  };

例如:此处linkedTo表示abc的值将进入count变量。类似地,plmtq 的值进入 title 变量,但 URL 解码为 decode 字段存在且为真。

问题陈述:

现在我需要使用这两个地图并制作一个新地图,它看起来像这样,它也是 String 和 String。

count=2
title=hello world // as you can see this got URL decoded with UTF-8 format.
cross=def
browse=def
yyt=opi

所以Tagclass中的每个枚举都有特殊的含义,我需要相应地执行某些操作。现在它有两个,但通常有 7-8 个。解决此问题的最佳和有效方法是什么?

  private static Map<String, String> generateMap(final Map<String, String> mapA,
      final Map<String, Map<Tag, String>> mapB) {
    Map<String, String> newMap = new HashMap<>();
    for (Entry<String, Map<Tag, String>> entry : mapB.entrySet()) {
      String eventTag = entry.getKey();

      // I am confuse how to proceed further
    }
    return newMap;
  }

更新:-

在与 dimo 讨论后,我们为第二张地图提出了这个新的 JSON 设计:

第二个JSON

{
  "abc": {
      "renameTo": "count"
  },
  "plmtq": {
      "renameTo": "title",
      "decode": "true"
  },
  "lndp": {
      "renameTo": ["cross", "browse"],
     "decode": "true"
  }
}

基本上只是恢复格式。所以在 newMap 键中将是 count,它的值将是 abc 的值。类似地,对于 lndp 的情况,在 newMap 中,键将是 crossbrowse,它的值将是 def 并解码 URL。

这个怎么样

private static Map<String, String> generateMap(final Map<String, String> mapA,
                                                  final Map<String, Map<Tag, String>> mapB) {
   Map<String, String> newMap = new HashMap<>();

   for (Map.Entry<String, Map<Tag, String>> entry : mapB.entrySet()) {
        String eventTag = entry.getKey();
        Map<Tag, String> values = entry.getValue();

        if (values.containsKey(Tag.linkedTo)) {
            String linkedValue = mapA.get(values.get(Tag.linkedTo));
            if (values.containsKey(Tag.decode)) {
                linkedValue = URLDecoder.decode(linkedValue);
            }

            newMap.put(eventTag, linkedValue);
        }
    }
    return newMap;
}

或者你用来解码的任何东西...

这个答案的大部分我使用 Java 7,但底部有一些 Java 8 条评论。

我认为有两个关键见解很重要:

  • 使用明确的 class、方法和变量名称 - 名称应该传达含义和目的,而 mapAmapB 等标识符则不然,因此很难推理您要解决的问题。
  • 将您的问题分成smaller parts;单独解决这些部分,然后将它们放在一起。

我将尝试在此处应用这两个概念。


  1. 将 JSON 转换为有意义且有用的数据结构(即不仅仅是一堆 Map 对象)。你的第二个 JSON 有一些清晰的结构,不容易作为嵌套地图使用。与其尝试创建复杂的算法来处理复杂的数据,不如创建允许您有意义地表示数据并与之交互的对象。

    考虑将您的第二个 JSON 表示为 Map<String, Transformation>,其中 Transformation class 如下所示:

    public class Transformation {
      /** Applies one or more transformations to the provided entry. */
      public Entry<String, String> apply(Entry<String, String> e) {
        // ...
      }
    }
    

    apply() 是做什么的?我们会做到这一点。现在拥有 class 结构就足够了,因为现在我们可以编写一个简单的函数来完成您需要的处理。

  2. 有了有用的数据结构,您手头的任务就会变得简单。考虑这样的事情(注意更清晰的函数和变量名称):

    private static Map<String, String> decodeMap(
        Map<String, String> encodedData,
        Map<String, Transformation> transformations) {
      Map<String, String> decodedData = new HashMap<>();
      for (Entry<String, String> e : encodedData.entrySet()) {
        Transformation t = transformations.get(e.getKey());
        if (t == null) {
          t = Transformation.NO_OP; // just passes the entry through
        }
        Entry<String, String> decoded = t.apply(e);
        decodedData.put(decoded.getKey(), decoded.getValue());
      }
      return decodedData;
    }
    

    这是一个非常简单的方法;我们遍历每个条目,应用(可能是无操作)转换,并构建这些转换条目的映射。假设我们的 Transformation class 完成了它的工作,那么很明显这个方法是有效的。

    请注意 transformations 的键是 编码 地图的键,而不是解码键。这更容易使用,所以理想情况下你会反转你的 JSON 来反映相同的结构,但如果你不能这样做,你只需要在构建 [=21= 之前在代码中翻转它们] 对象。

  3. 现在创建私有业务逻辑以在 Transformationclass 内对您的转换进行编码。这可能看起来像:

    public class Transformation {
      public static final Transformation NO_OP =
          new Transformation(ImmutableMap.of());
    
      private final Map<String, String> tasks;
    
      public Transformation(Map<String, String> tasks) {
        // could add some sanity checks here that tasks.keySet() doesn't
        // contain any unexpected values
        this.tasks = tasks;
      }
    
      public Entry<String, String> apply(Entry<String, String> e) {
        String key = e.getKey();
        String value = e.getValue();
        for (Entry<String, String> task : tasks.entrySet()) {
          switch (task.getKey()) {
            case "linkedTo":
              // this assumes we've inverted the old/new mapping. If not pass
              // the new key name into the constructor and use that here instead
              key = task.getValue();
              break;
            case "decode":
              if (Boolean.valueOf(task.getValue())) {
                // or whichever decoding function you prefer
                value = URLDecoder.decode(value);
              }
              break;
            // and so on
            default:
              throw new IllegalArgumentException(
                  "Unknown task " + task.getKey());
          }
        }
        return Maps.immutableEntry(key, value);
      }
    

    Transformationclass可以处理任意多的任务,其他代码无需了解具体细节。

  4. 最后一步是将您的 JSON 解析为这个 Map<String, Transformation> 结构。我会把它留给你,因为它取决于你选择的 JSON 解析器,但是我建议使用 Gson.


您要求将 lndp 条目转换为两个具有相同值的单独条目是一个奇怪的要求。我闻起来很像 x-y problem - 你正在寻求帮助使这个复杂的转换逻辑起作用,而不是探索底层前提本身是否有缺陷。考虑在一个单独的问题中探讨这个问题,在这个问题中你要说明你从什么开始,你想去哪里,以及为什么你当前的解决方案看起来最好。然后人们可以对策略本身提出建议。

也就是说,我们仍然可以将上面的第二个要点应用于此问题。与其尝试定义支持多条目关系的转换逻辑,不如向转换添加一个 post 处理步骤。定义一个单独的数据结构来编码哪些条目(在新地图中)应该被复制,例如

{
  "cross": ["browse"],
  ...
}

然后,decodeMap() 看起来像这样:

private static Map<String, String> decodeMap(
    Map<String, String> encodedData,
    Map<String, Transformation> transformations,
    Map<String, List<String>> duplications) {
  // ... existing method body
  for (Entry<String, List<String>> e : duplications) {
    String value = checkNotNull(decodedData.get(e.getKey()));
    for (String newKey : e.getValue()) {
      decodedData.put(newKey, value);
    }
  }
  return decodedData;
}

这是将问题分解成更小部分的另一个例子,而不是试图一举解决所有问题。您甚至可以将这些任务拆分为单独的方法,这样 decodeMap() 本身就不会变得太复杂。


您可能会注意到 Transformation class 看起来很像 Function - 那是因为它本质上是一个。您可以添加 implements Function<Entry<String, String>, Entry<String, String>>(或在 Java 8 implements UnaryOperator<Entry<String, String>> 中)并能够像使用任何其他 Function 一样使用 Transformation

特别是,这将允许您创建 Transformation 接口并将简单的 Transformation 对象组合在一起,而不是定义一个庞大的类型来负责每项任务。对于每个所需的任务,您都会有一个 Transformation 的专用子 class,例如:

public RenameKeyTransformation implements Transformation {
  private final renameTo;
  public RenameKeyTransformation(String renameTo) {
    this.renameTo = checkNotNull(renameTo);
  }

  public Entry<String, String> apply(Entry<String, String> e) {
    return Maps.immutableEntry(renameTo, e.getValue());
  }
}

等等。然后,您可以将它们与 Functions.compose() (or Java 8's Function.andThen()) 组合起来,以创建一个应用所有所需转换的复合函数。例如:

Function<Entry<String, String>, Entry<String, String>> compound =
    Functions.compose(
        new RenameKeyTransformation("plmtq"),
        new UrlDecodeValue());

returns 一个新函数,它将对给定条目应用重命名键操作和 url 解码值操作。您可以重复链接函数以根据需要应用任意数量的转换。

第一个 Transformation 对象可能更容易概念化(这是一个非常合理的解决方案),但是组合小函数的第二种想法将上述封装想法提升到另一个层次,隔离每个单独的转换彼此任务,并使您可以轻松地添加进一步的转换。这是 strategy pattern 的示例,这是一种将您正在处理的数据与要执行的确切处理步骤分离的优雅方式。