Конвейер ввода Elasticsearch: как рекурсивно изменять значения в хэш-карте

#elasticsearch #hashmap #pipeline #ingest

#elasticsearch #hashmap #конвейер #ввод

Вопрос:

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

В конвейере я использую функцию для обхода и изменения значений представления коллекции хэш-карты.

 PUT /_ingest/pipeline/samples
{
    "description": "preprocessing of samples.json",
    "processors": [
        {
            "script": {
                "tag": "remove underscore from sample_tags values",
                "source": """
                    void findReplace(Collection collection) {
                    collection.forEach(element -> {
                        if (element instanceof String) {
                            element.replace('_',' ');
                        } else {
                            findReplace(element);
                        }
                        return true;
                        })
                    }

                    Collection samples = ctx.samples;
                    samples.forEach(sample -> { //sample.sample_tags is a HashMap
                        Collection sample_tags = sample.sample_tags.values();
                        findReplace(sample_tags);
                        return true;
                    })
                """
            }
        }
    ]
}
 

Когда я имитирую прием конвейера, я обнаруживаю, что строковые значения не изменяются. Где я ошибаюсь?

 POST /_ingest/pipeline/samples/_simulate
{
    "docs": [
        {
            "_index": "samples",
            "_id": "xUSU_3UB5CXFr25x7DcC",
            "_source": {
                "samples": [
                    {
                        "sample_tags": {
                            "Entry_A": [
                                "A_hyphentated-sample",
                                "sample1"
                            ],
                            "Entry_B": "A_multiple_underscore_example",
                            "Entry_C": [
                                        "sample2",
                                        "another_example_with_underscores"
                            ],
                            "Entry_E": "last_example"
                        }
                    }
                ]
            }
        }
    ]
}

\Result

{
  "docs" : [
    {
      "doc" : {
        "_index" : "samples",
        "_type" : "_doc",
        "_id" : "xUSU_3UB5CXFr25x7DcC",
        "_source" : {
          "samples" : [
            {
              "sample_tags" : {
                "Entry_E" : "last_example",
                "Entry_C" : [
                  "sample2",
                  "another_example_with_underscores"
                ],
                "Entry_B" : "A_multiple_underscore_example",
                "Entry_A" : [
                  "A_hyphentated-sample",
                  "sample1"
                ]
              }
            }
          ]
        },
        "_ingest" : {
          "timestamp" : "2020-12-01T17:29:52.3917165Z"
        }
      }
    }
  ]
}

 

Ответ №1:

Вот модифицированная версия вашего скрипта, которая будет работать с предоставленными вами данными:

 PUT /_ingest/pipeline/samples
{
  "description": "preprocessing of samples.json",
  "processors": [
    {
      "script": {
        "tag": "remove underscore from sample_tags values",
        "source": """
          String replaceString(String value) {
            return value.replace('_',' ');
          }
      
          void findReplace(Map map) {
            map.keySet().forEach(key -> {
              if (map[key] instanceof String) {
                  map[key] = replaceString(map[key]);
              } else {
                  map[key] = map[key].stream().map(this::replaceString).collect(Collectors.toList());
              }
            });
          }

          ctx.samples.forEach(sample -> {
              findReplace(sample.sample_tags);
              return true;
          });
          """
      }
    }
  ]
}
 

Результат выглядит следующим образом:

      {
      "samples" : [
        {
          "sample_tags" : {
            "Entry_E" : "last example",
            "Entry_C" : [
              "sample2",
              "another example with underscores"
            ],
            "Entry_B" : "A multiple underscore example",
            "Entry_A" : [
              "A hyphentated-sample",
              "sample1"
            ]
          }
        }
      ]
    }
 

Комментарии:

1. Хорошо, я не знал, что вы можете заменить на месте. 1

2. Потрясающе, рад, что это помогло!

3. @JoeSorocin обратите внимание, что решение само по себе не заменяет «на месте», map[key] оно по-прежнему переназначается для каждого ключа, как в обычном операторе assign .

4. Верно, но findReplace по-прежнему недействителен, не так ли?

5. @JoeSorocin да, я думаю, это зависит от определения «на месте» 🙂 Для меня на месте означает, что вы повторяете сами значения и изменяете их, как если бы у вас был указатель на него, что невозможно в Java (слава богу)

Ответ №2:

Вы были на правильном пути, но работали с копиями значений и не устанавливали измененные значения обратно в контекст документа ctx , который в конечном итоге возвращается из конвейера. Это означает, что вам нужно будет отслеживать текущие индексы итерации — то есть для списков массивов, а также для хэш-карт и всего, что между ними, — чтобы затем вы могли ориентироваться на позиции полей в глубоко вложенном контексте.

Вот пример, в котором рассматриваются строки и списки массивов (только для строк). Вам нужно будет расширить его для обработки хэш-карт (и других типов), а затем, возможно, извлечь весь процесс в отдельную функцию. Но AFAIK вы не можете возвращать несколько типов данных в Java, поэтому это может быть сложно…

 PUT /_ingest/pipeline/samples
{
  "description": "preprocessing of samples.json",
  "processors": [
    {
      "script": {
        "tag": "remove underscore from sample_tags values",
        "source": """
          ArrayList samples = ctx.samples;
        
          for (int i = 0; i < samples.size(); i  ) {
              def sample = samples.get(i).sample_tags;
              
              for (def entry : sample.entrySet()) {
                  def key = entry.getKey();
                  def val = entry.getValue();
                  def replaced_val;
                  
                  if (val instanceof String) {
                    replaced_val = val.replace('_',' ');
                  } else if (val instanceof ArrayList) {
                    replaced_val = new ArrayList();
                    for (int j = 0; j < val.length; j  ) {
                        replaced_val.add(val[j].replace('_',' ')); 
                    }
                  } 
                  // else if (val instanceof HashMap) {
                    // do your thing
                  // }
                  
                  // crucial part
                  ctx.samples[i][key] = replaced_val;
              }
          }
        """
      }
    }
  ]
}