Анализ одного POJO из нескольких документов YAML, представляющих разные классы.

Я хочу использовать один файл YAML, который содержит несколько разных объектов для разных приложений. Мне нужно получить один объект, чтобы получить экземпляр MyClass1, игнорируя остальные документы для MyClass2, MyClass3 и т. д. Какая-то выборочная десериализация: теперь этот класс, затем тот... Структура MyClass2, MyClass3 такова. совершенно неизвестно приложению, работающему с MyClass1. Конечно, файл всегда является действительным YAML.

YAML может иметь любую структуру, необходимую для реализации такого многоклассового контейнера. Предпочтительным инструментом синтаксического анализа является snapyaml.

Это разумно? Как я могу игнорировать все объекты, кроме одного?

UPD: заменил все "документ" на "объект". Я думаю, что мы должны говорить об одном документе YAML, содержащем несколько объектов разной структуры. Более того, синтаксический анализатор точно знает только одну структуру и хочет игнорировать остальные.

UDP2: Я думаю, что это невозможно со змейкоймлом. Мы все равно должны прочитать все объекты, а потом выбрать нужный. Но, может быть, я ошибаюсь.

UPD2: пример файла конфигурации

--- 
- 
  exportConfiguration781: 
    attachmentFieldName: "name"
    baseSftpInboxPath: /home/user/somedir/
    somebool: false
    days: 9999
    expected: 
      - ABC w/o quotes
      - "Cat ABC"
      - "Some string"
    dateFormat: yyyy-MMdd-HHmm
    user: someuser
- 
  anotherConfiguration: 
    k1: v1
    k2: 
      - v21
      - v22

person darkSideOfTheMoon    schedule 18.05.2020    source источник


Ответы (2)


Это определенно возможно со SnakeYAML, хотя и не тривиально. Вот общее изложение того, что вам нужно сделать:

Во-первых, давайте посмотрим, что делает загрузка с помощью SnakeYAML. Вот важная часть класса YAML:

private Object loadFromReader(StreamReader sreader, Class<?> type) {
    Composer composer = new Composer(new ParserImpl(sreader), resolver, loadingConfig);
    constructor.setComposer(composer);
    return constructor.getSingleData(type);
}

Композитор анализирует ввод YAML в Узлы. Для этого не нужно никаких знаний о структуре ваших классов, поскольку каждый узел является либо ScalarNode, SequenceNode, либо MappingNode, и они просто представляют структуру YAML.

конструктор берет корневой узел, сгенерированный композитором, и создает из него собственные объекты POJO. Итак, что вы хотите сделать, так это отбросить части графа узлов до того, как они достигнут конструктора.

Самый простой способ сделать это, вероятно, состоит в том, чтобы получить от Composer и переопределить два метода, подобные этому:

public class MyComposer extends Composer {
    private final int objIndex;

    public MyComposer(Parser parser, Resolver resolver, int objIndex) {
        super(parser, resolver);
        this.objIndex = objIndex;
    }

    public MyComposer(Parser parser, Resolver resolver, LoaderOptions loadingConfig, int objIndex) {
        super(parser, resolver, loadingConfig);
        this.objIndex = objIndex;
    }


    @Override
    public Node getNode() {
        return strip(super.getNode());
    }

    private Node strip(Node input) {
        return ((SequenceNode)input).getValue().get(objIndex);
    }
}

Реализация strip — это всего лишь пример. В этом случае я предположил, что ваш YAML выглядит так (содержимое объекта произвольно):

- {first: obj}
- {second: obj}
- {third: obj}

И вы просто выбираете объект, который хотите десериализовать, по его индексу в последовательности. Но у вас также может быть что-то более сложное, например алгоритм поиска.

Теперь, когда у вас есть собственный композитор, вы можете

Constructor constructor = new Constructor();
// assuming we want to get the object at index 1 (i.e. second object)
Composer composer = new MyComposer(new ParserImpl(sreader), new Resolver(), 1);
constructor.setComposer(composer);
MyObject result = (MyObject)constructor.getSingleData(MyObject.class);
person flyx    schedule 19.05.2020
comment
Спасибо за ответ! Проблема с реализацией в том, что strip() не работает. Исключение говорит, что ScalarNode не может быть приведен к SequenceNode, что разумно. И да, мой YAML выглядит именно так, как вы предполагали. - person darkSideOfTheMoon; 20.05.2020
comment
Кроме того, мне не приходит в голову раздеться только один раз. Что, если у объекта ниже есть несколько деревьев узлов внутри (и они действительно есть)? Как он будет анализироваться с активной функцией strip()? - person darkSideOfTheMoon; 20.05.2020
comment
Вы действительно должны добавить пример YAML к своему вопросу, чтобы показать, как вам нужно его раздеть. - person flyx; 20.05.2020
comment
Я обновил код. Оказывается, getSingleNode() вызывает getNode(), поэтому переопределение первого приводит к двойному вызову strip(). Боюсь, у меня нет настройки Java для отладки, поэтому, пожалуйста, проверьте, работает ли она сейчас для этого простого случая. - person flyx; 20.05.2020
comment
Я добавил пример файла конфигурации. Глядя на ваши изменения, я все еще не понимаю кастинг в strip(). Это обязательно вызовет исключение. - person darkSideOfTheMoon; 20.05.2020

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

Решил задачу двойной перегонкой, простите, обработкой конфигурационного файла. Представьте, что последний состоит из нескольких частей, и каждая часть помечена уникальным токеном-разделителем. Ради сохранения YAML-подобий может быть

---
#this is a unique key for the configuration A
<some YAML document>
---
#this is another key for the configuration B
<some YAML document

Первый проход — предварительная обработка. Для данной String fileString и String key (и DELIMITER = "\n---\n", например) мы выбираем подстроку с конфигурацией, определяемой ключом:

int begIndex;                                                                       
do {                                                                                
  begIndex= fileString.indexOf(DELIMITER);                                          
  if (begIndex == -1) {                                                             
    break;                                                                          
  }                                                                                 
  if (fileString.startsWith(DELIMITER + key, begIndex)) {                           
    fileString = fileString.substring(begIndex + DELIMITER.length() + key.length());
    break;                                                                          
  }                                                                                 
  // spoil alien delimiter and repeat search                                        
  fileString = fileString.replaceFirst(DELIMITER, " ");                             
} while (true);                                                                     
int endIndex = fileString.indexOf(DELIMITER);                                       
if (endIndex != -1) {                                                               
  fileString = fileString.substring(0, endIndex);                                   
} 

Теперь мы передаем fileString простому парсингу YAML.

ExportConfiguration configuration = new Yaml(new Constructor(ExportConfiguration.class))
    .loadAs(fileString, ExportConfiguration.class);

На этот раз у нас есть один документ, который должен соответствовать классу ExportConfiguration.

Примечание 1: Структура и даже само содержание остального конфигурационного файла не играет абсолютно никакой роли. В этом и была основная идея, получить независимые конфигурации в одном файле

Примечание 2: остальные конфигурации могут быть JSON, XML или чем-то еще. У нас есть метод-препроцессор, который возвращает конфигурацию String — и следующий процессор правильно ее разбирает.

person darkSideOfTheMoon    schedule 21.05.2020