У меня есть два 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 - класс перечисления с этими значениями. На данный момент он имеет только два значения, но в целом он имеет около 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

Таким образом, каждое перечисление в классе Tag имеет особое значение, и мне нужно выполнить определенные операции соответственно. Сейчас их два, но в целом их 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 будут cross и browse, а его значением будет def с декодированным URL.

2
john 26 Ноя 2016 в 05:40

2 ответа

Лучший ответ

Я использую Java 7 для большей части этого ответа, но внизу есть несколько комментариев о Java 8.

Я считаю, что для начала важно сделать два ключевых вывода:

  • Используйте ясные имена классов, методов и переменных - имена должны передавать смысл и цель, тогда как идентификаторы, такие как mapA и mapB, не делают этого, что затрудняет рассуждение о проблемах, которые вы пытаетесь решить.
  • Разделите проблему на более мелкие части; решите эти части по отдельности, а затем соедините их.

Я попытаюсь применить здесь обе эти концепции.


  1. Преобразуйте JSON в осмысленную и полезную структуру данных (т.е. не просто набор объектов Map). Ваш второй JSON имеет четкую структуру, с которой нелегко работать как с вложенными картами. Вместо того, чтобы пытаться создавать сложные алгоритмы для работы с запутанными данными, создавайте объекты, которые позволяют вам осмысленно представлять и взаимодействовать с вашими данными.

    Представьте свой второй JSON в виде Map<String, Transformation>, где класс Transformation выглядит так:

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

    Что делает apply()? Мы к этому еще вернемся. На данный момент достаточно иметь структуру классов, потому что теперь мы можем написать простую функцию для выполнения необходимой обработки.

  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 выполняет свою работу, должно быть совершенно очевидно, что этот метод работает.

    Обратите внимание, что ключи transformations - это ключи закодированной карты, а не декодированные ключи. С этим намного проще работать, поэтому в идеале вы должны инвертировать свой JSON, чтобы отразить ту же структуру, но если вы не можете этого сделать, вам просто нужно перевернуть их в коде перед созданием объектов Transformation.

  3. Теперь создайте частную бизнес-логику для кодирования ваших преобразований внутри класса Transformation. Это может выглядеть примерно так:

    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);
      }
    

    Класс Transformation может обрабатывать столько задач, сколько вам нужно, и никакой другой код не должен знать об особенностях.

  4. Последний шаг - проанализировать ваш JSON в этой структуре Map<String, Transformation>. Я оставлю это на ваше усмотрение, поскольку это зависит от выбранного парсера JSON, однако я предлагаю использовать Gson .


Ваше требование, чтобы запись lndp была преобразована в две отдельные записи с одинаковым значением, является нечетным. Для меня это сильно пахнет xy проблемой - вам нужна помощь в решении эта сложная логика трансформации работает, а не исследует, ошибочна ли сама основная предпосылка. Подумайте о том, чтобы изучить это в отдельном вопросе, где вы разъясняете, с чего вы начинаете, куда вы пытаетесь двигаться и почему ваше текущее решение кажется лучшим. Затем люди могут предложить предложения по самой стратегии.

Тем не менее, мы все еще можем применить мой второй пункт выше к этой проблеме. Вместо того, чтобы пытаться определить логику преобразования, которая поддерживает отношения с несколькими записями, давайте добавим к преобразованию этап постобработки. Определите отдельную структуру данных, которая кодирует, какие записи (в новой карте) следует дублировать, например

{
  "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 очень похож на Function - это потому, что по сути он один. Вы можете добавить implements Function<Entry<String, String>, Entry<String, String>> (или в Java 8 implements UnaryOperator<Entry<String, String>>) и иметь возможность использовать Transformation, как любой другой Function.

В частности, это позволит вам создавать Transformation интерфейс и составлять простые объекты Transformation вместе, а не определять один массивный тип, отвечающий за каждую задачу. У вас был бы специальный подкласс Transformation для каждой желаемой задачи, например:

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() (или Function.andThen()), чтобы создать составная функция, которая применяет все желаемые преобразования. Например это:

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

Возвращает новую функцию, которая будет применять как операцию переименования ключа, так и операцию декодирования URL-адреса к данной записи. Вы можете несколько раз объединить функции, чтобы применить столько преобразований, сколько вам нужно.

Первый объект Transformation может быть проще концептуализировать (и это вполне разумное решение), но эта вторая идея составления небольших функций переводит идею инкапсуляции, упомянутую выше, на другой уровень, изолируя каждую отдельную задачу преобразования друг от друга и упрощая добавление дальнейших преобразований по ходу дела. Это пример шаблона стратегии, который представляет собой элегантный способ разделить данные, которые вы ' повторная обработка из точных этапов обработки, которые должны быть выполнены.

3
Community 20 Мар 2017 в 10:29

Как насчет этого

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;
}

Или что бы вы ни использовали для декодирования ...

1
Ginandi 27 Ноя 2016 в 07:29