Динамическое создание POJO / бинов и установка значений с использованием CGLib - PullRequest
1 голос
/ 22 мая 2019

У меня есть требование для анализа текстового файла и создания документа JSON.Текстовый файл имеет текстовый шаблон, который содержит ключ, который является именем, а значение представляет собой огромный текст TSV с заголовками.

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

Class<?> beanClass = BeanClassGenerator.beanGenerator(k, mapForBeanGeneration);
            try {
                Object beanClassObject = beanClass.newInstance();
                lines.forEach(line -> {
                    if (line != null && !line.isEmpty() && !line.equals("null")) {
                        String[] lineData = line.split("\t");
                        System.out.println("LineData length :: " + lineData.length);
                        Method[] methods = beanClass.getMethods();
                        System.out.println("Methods length :: " + methods.length);
                        int index = 0;
                        for (Method m : methods) {
                            m.setAccessible(true);
                            if (m.getName().startsWith("set")) {
                                try {
                                    if ((lineData.length <= index) && lineData[index] != null) {
                                        m.invoke(beanClassObject, lineData[index]);
                                        index++;
                                    } else {
                                        m.invoke(beanClassObject, " ");
                                    }
                                } catch (IllegalAccessException | InvocationTargetException e) {
                                    e.printStackTrace();
                                }
                            }
                        }
                    }
                });
                ObjectMapper om = new ObjectMapper();
                System.out.println(om.writeValueAsString(beanClassObject));
            } catch (InstantiationException | IllegalAccessException | JsonProcessingException e) {
                e.printStackTrace();
  }});

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

Мне интересноесли есть более простой способ сделать это.Любая помощь приветствуется.

Вот метод генерации бина.

public static Class<?> beanGenerator(final String className, final Map<String, Class<?>> properties) {
        BeanGenerator beanGenerator = new BeanGenerator();
        beanGenerator.setNamingPolicy(new NamingPolicy() {
            @Override
            public String getClassName(String prefix, String source, Object key, Predicate names) {
                return className;
            }
        });

        BeanGenerator.addProperties(beanGenerator, properties);
        return (Class<?>) beanGenerator.createClass();
    }

Вот пример текстового файла, который необходимо преобразовать в вывод JSON.

<Data1>
Col1  col2 col3 col4 col5
even    sense   met has
root    greatest    spin    mostly
gentle  held    introduced  palace
cold    equator remember    grandmother
slightly    butter  depth   like
distant second  coast   everyone


<Data2>
Col1  col2 col3 col4 col5 col6 col7 col8
greatest    rope    operation   flies   brown   continent   combination read
slightly    diagram he  grandfather where   party   fifty   pour
well    put plastic anyway  refer   careful correct furniture
how since   army    tongue  birthday    been    clock   official
table   command specific    distant cutting hill    movie   experience
national    though  stopped youth   army    underline   five    know

<Data3>
Col1 col2 col3 col4 col5 col6 col7 col8 col9 col9 col10
vessels characteristic  ship    joy than    tomorrow    high    seven   future  trade
try gray    fourth  advice  week    stream  motion  musical whom    tin
limited daughter    large   rice    came    home    chicken wheat   engine  box
easy    city    pair    strange stage   visitor coach   announced   allow   simple
jet therefore   single  during  construction    flag    bigger  muscle  complex pleasure
income  several coat    range   dull    cattle  damage  jump    present shake

Вывод в формате JSON:

[{
    "<Data1>": [{
            "col1": "",
            "col2": "",
            "col3": "",
            "col4": ""
        },
        {
            "col1": "",
            "col2": "",
            "col3": "",
            "col4": ""
        },
        {
            "col1": "",
            "col2": "",
            "col3": "",
            "col4": ""
        }
    ]

}, {
    "<Data2>": [{
            "col1": "",
            "col2": "",
            "col3": "",
            "col4": "",
            "col5": "",
            "col6": "",
            "col7": "",
            "col8": ""
        },
        {
            "col1": "",
            "col2": "",
            "col3": "",
            "col4": "",
            "col5": "",
            "col6": "",
            "col7": "",
            "col8": ""
        },
        {
            "col1": "",
            "col2": "",
            "col3": "",
            "col4": "",
            "col5": "",
            "col6": "",
            "col7": "",
            "col8": ""
        }
    ]

}]

Я нашел решение с использованием Карт.

Map<String, List<Map<String, String>>> finalMap = new HashMap<>();
        metadataMap.forEach((k, v) -> {

            List<Map<String, String>> datamap = new ArrayList<>();

            String key = k;
            String[] fields = v.getFields();
            List<String> lines = v.getLines();


            lines.forEach(line -> {
                if (line != null && !line.isEmpty() && !line.equals("null")) {
                    String[] fieldData = line.split("\t");
                    Map<String, String> eachLineMap = new HashMap<>();
                    for (int index = 0; index < fields.length; index++) {
                        if (index < fieldData.length && (fieldData[index] != null && !fieldData[index].isEmpty())) {
                            eachLineMap.put(fields[index], fieldData[index]);
                        } else {
                            eachLineMap.put(fields[index], " ");
                        }
                        datamap.add(eachLineMap);
                    }
                }
            });
            finalMap.put(key, datamap);
        });

        try {
            output = new ObjectMapper().writeValueAsString(finalMap);
        }catch(JsonProcessingException e){
            e.printStackTrace();
        }

Ответы [ 3 ]

1 голос
/ 23 мая 2019

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

Сделайте следующее:

  1. Посмотрите на свои данные;она организована следующим образом:
    1. first: внешний ключ
    2. second: ровно одна строка, содержащая переменное число разделенных пробелами массив внутренних ключей.
    3. третья: некоторое количествостроки, содержащие значения.
  2. Разработайте решение, чтобы решить вашу проблему
    1. Считайте внешний ключ.Используйте это значение для создания части внешнего ключа вашего JSON.
    2. Считайте внутренние ключи.Храните их в массиве;используйте LinkedList, а не ClownList (ArrayList).
    3. Делайте это до следующей пустой строки:
      1. Чтение строки значений.
      2. Запись внутреннего JSON;используйте внутренние ключи в качестве ключей для этого.
    4. Пропускать пустые строки до тех пор, пока не будет выполнено одно из следующих действий:
      1. Если в конце файла написать конечную часть JSON.
      2. Если вы прочитали следующую внешнюю клавишу, перейдите к строке 2 (чтение внутренних клавиш) выше.
  3. Введите код.
1 голос
/ 22 мая 2019

Вам не нужно писать всю эту логику, вы можете просто использовать Apache Commons BeanUtils ;который предоставляет служебный метод (среди MANY других утилит), который берет Map имен полей в сравнении со значениями полей и заполняет им указанный компонент:

    BeanUtils.populate(target, fieldNameValueMap);

Тогда единственноевам нужно реализовать логику для создания fieldNameValueMap Map;что вы можете сделать с помощью этого простого метода:

    Map<String, String> createFieldNameValueMap(String headerLine, String valuesLine) {
        String[] fieldNames = headerLine.split("\t");
        String[] fieldValues = valuesLine.split("\t");

        return IntStream.range(0, fieldNames.length)
            .mapToObj(Integer::new)
            .collect(Collectors.toMap(idx -> fieldNames[idx], idx -> fieldValues[idx]));
    }

Вы можете протестировать это решение с помощью следующей рабочей демонстрации:

import java.lang.reflect.InvocationTargetException;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

import org.apache.commons.beanutils.BeanUtils;

import lombok.Data;

public class DynamicBeanUtils {

    static Map<String, String> createFieldNameValueMap(String headerLine, String valuesLine) {
        String[] fieldNames = headerLine.split("\t");
        String[] fieldValues = valuesLine.split("\t");

        return IntStream.range(0, fieldNames.length)
            .mapToObj(Integer::new)
            .collect(Collectors.toMap(idx -> fieldNames[idx], idx -> fieldValues[idx]));
    }

    public static void main(String[] args) {
        String headerLine = "booleanValue\tintValue\tstringValue\tdoubleValue\totherValue";
        String valuesLine = "true\t12\tthis bean will be populated\t22.44\ttest string!!!";

        Object target = new MyBean();
        try {
            BeanUtils.populate(target, createFieldNameValueMap(headerLine, valuesLine));
        } catch (IllegalAccessException | InvocationTargetException e) {
            // HANDLE EXCEPTIONS!
        }

        System.out.println(target);
    }

    @Data
    public static class MyBean {
        private String stringValue;
        private double doubleValue;
        private int intValue;
        private boolean booleanValue;
        private String otherValue;
    }
}

Это страница хранилища maven для этой зависимости, поэтому выможете включить его в свою сборку: https://mvnrepository.com/artifact/commons-beanutils/commons-beanutils/1.9.3

Я также использовал Lombok в этом решении, только чтобы избавить меня от необходимости писать getter / setters / toString для тестирования этого решения;но это не требуется для вашего решения.

Надеюсь, это поможет.

0 голосов
/ 26 мая 2019

Я понял, что вместо создания POJO со сложным подходом. Лучше использовать Map s и преобразовать их в JSON, используя Jackson ObjectMapper. Размещение сообщений для тех, кто считает, что это может быть полезным подходом.

public String convert(Map<String, ? extends Metadata> metadataMap) {
        String output = "";
        Map<String, List<Map<String, String>>> finalMap = new HashMap<>();
        metadataMap.forEach((k, v) -> {

            List<Map<String, String>> datamap = new LinkedList<>();

            String key = k;
            String[] fields = v.getFields();
            List<String> lines = v.getLines();


            lines.forEach(line -> {
                if (line != null && !line.isEmpty() && !line.equals("null")) {
                    String[] fieldData = line.split("\t",-1);
                    Map<String, String> eachLineMap = new HashMap<>();
                    for (int index = 0; index < fields.length; index++) {
                        if (index < fieldData.length && (fieldData[index] != null && !fieldData[index].isEmpty())) {
                            eachLineMap.put(fields[index], fieldData[index]);
                        } else {
                            eachLineMap.put(fields[index], " ");
                        }
                        datamap.add(eachLineMap);
                    }
                }
            });
            finalMap.put(key, datamap);
        });

        try {
            output = new ObjectMapper().writeValueAsString(finalMap);
        }catch(JsonProcessingException e){
            e.printStackTrace();
        }

        return output;
    }
...