1

We are creating CSV files from DTO classes, using reflection to get the names of all the column names. ClassName.class.getDeclaredFields() gives us array of all column names.

We have a use case where, we have classes with composition relation, like below:

public class Book {
  private String title;

  private Author author;
}

public class Author {
  private String name;

  private Integer age:
}

Here Book is the root class, i.e., final CSV will be named books.csv

With Book.class.getDeclaredFields(), we only get field names title and author, but we also want field names from Author class (name and age), is there a way to get those?

Amal K
  • 4,359
  • 2
  • 22
  • 44
rsp
  • 813
  • 2
  • 14
  • 26
  • Doesn't `Author.class.getDeclaredFields()` work? Or are you looking for a solution that doesn't involve knowing the type of `author` inside `Book`? – Federico klez Culloca Nov 09 '21 at 07:56
  • Excatly, we don't know the type of author in most cases. – rsp Nov 09 '21 at 07:57
  • 1
    never use 'int' or 'Integer' age. Just store the birthdate. Unlike the age, that doesn't change every year, and you can perfectly find the age based on it. name and age are not part of Book, so why would it return it? Since the type of author isn't fixed, how would the process be able to tell? – Stultuske Nov 09 '21 at 08:07
  • 2
    @bob You can check if a `Field` is a primitive type or not using `field.getType().isPrimitive()`. If it is not primitive you again call `field.getType().getDeclaredFields()`. This may have to be done recursively for every subtype. – Amal K Nov 09 '21 at 08:09
  • I see, I think it would make sense to create a separate csv for Author – rsp Nov 09 '21 at 08:09
  • 1
    @bob `isPrimitive()` would return false for strings. So you may have to do: `var type = field.getType(); if(type.isPrimitive() || type.isAssignableFrom(String.class)) {...}` to also check if it is a string. To also cover wrapper classes, see [this post](https://stackoverflow.com/a/25039320/11455105). – Amal K Nov 09 '21 at 08:24
  • 3
    @AmalK `type.isAssignableFrom(String.class)` would be `true` if `type` is `Object`. Perhaps, you simply want `type == String.class`. – Holger Nov 09 '21 at 08:58

1 Answers1

0

Notes:

  • It is far from optimal.
  • Circular reference problem is not considered.

Given your model it would return a list with this elements:

  • title
  • author.name
  • author.age

Usage:

ClassMetadataService cms = new ClassMetadataService();
List<String> properties = cms.getProperties(Book.class);

Class definition:

package io.metadata;

import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class ClassMetadataService {

    public List<String> getProperties(Class clazz) {
        List<String> properties = new ArrayList<>();
        this.collectProperties(new ArrayList<>(), clazz, properties);

        return properties;
    }

    private void collectProperties(List<String> pathNodes, Class clazz, List<String> properties) {
        for (Field field : clazz.getDeclaredFields()) {
            List<String> localPathNodes = new ArrayList<>(pathNodes);
            localPathNodes.add(field.getName());

            if (clazz.isPrimitive() || isJavaClass(field.getType())) {
                properties.add(localPathNodes.stream().collect(Collectors.joining(".")));
            } else {
                collectProperties(localPathNodes, field.getType(), properties);
            }
        }
    }

    private Boolean isJavaClass(Class clazz) {
        List<Class> javaClass = Arrays.asList(
                // ....
                Boolean.class,
                Byte.class,
                Short.class,
                Integer.class,
                Long.class,
                Float.class,
                Double.class,
                BigDecimal.class,
                BigInteger.class,
                Character.class,
                String.class,
                LocalDate.class,
                LocalDateTime.class
                // ....
        );

        return javaClass.stream().anyMatch(jc -> jc.equals(clazz));
    }
}