0

I need to find all 'missing' and extra fields for every dto's by their entities using reflection. For example.

I have

public class TestDto {
  long id;
  String name;
  int age;
  long personId;
  String phone;
}

And Entity

public class TestEntity {
  long id;
  String name;
  int age;
  Person person;
  String address;
}

person = personId(mapping). We don't need to print it like 'missing' field and 'extra' field.

Output: Missing fields for dto. Please add!: address; Extra fields for dto. Please remove! : phone;

i wrote

private final Map<String, String> fieldMappings = new HashMap<>();

      fieldMappings.put("person", "personId"); 

      Field[] dtoFields = auditDtoClass.getDeclaredFields();
      Field[] entityFields = entityClass.getDeclaredFields();

      List<String> missingFields = Arrays.stream(entityFields)
          .filter(field -> !fieldMappings.containsKey(field.getName()) && Stream.of(dtoFields)
              .noneMatch(dtoField -> dtoField.getName().equals(field.getName())))
          .map(Field::getName)
          .filter(field -> Arrays.stream(dtoFields)
              .noneMatch(f -> f.getName().equals(field)))
          .toList();

      List<String> extraFields = Arrays.stream(dtoFields)
          .filter(field -> !fieldMappings.containsValue(field.getName()) &&
              !fieldMappings.containsKey(field.getName()) && Stream.of(entityFields)
              .noneMatch(entityField -> entityField.getName().equals(field.getName())))
          .map(Field::getName)
          .filter(field -> Arrays.stream(entityFields)
              .noneMatch(f -> f.getName().equals(field)))
          .toList();

It's wrong.

Because programmer can add (private Person person field) in other entity without adding to dto and it didn't print it in missing fields.

I also think that we can link those fields fieldMappings.put("person", "personId"); to the entity/dto classes but now I don't understand how. I'd love to hear ideas on how to do this.

Taras
  • 106
  • 6
  • With `fieldMappings.put("person", "personId");` this code does work just fine. I don't see your actual issue here – XtremeBaumer Jan 11 '23 at 11:41
  • Side note: `getDeclaredFields()` will only return the fields declared in the class itself but not any declared in super classes. Also, maybe you can use any of the already existing mapping libraries to find those differences, e.g. Dozer, Mapstruct etc. - You will have to have some mapping code anyway to get the data across (e.g. `personId = person.getId()`) so I'd put that check logic into the same functionality. – Thomas Jan 11 '23 at 11:42
  • I want to check all dtos. Not only TestDto . This code wrong if you want to find to add person field for another dto. For example Shop entity has field person and it just ignore it in missing fields. – Taras Jan 11 '23 at 11:46
  • @Thomas I have `@Mapping(source = "person.id", target = "personId") TestDto toAuditDto(Test entity);` – Taras Jan 11 '23 at 11:48
  • Btw, your code seems awefully inefficient, e.g. you're basically iterating over each dto field for each entity field and vice versa thus getting O(n*m) complexity. Also it seems that you basically filter twice, i.e. `Stream.of(dtoFields).noneMatch(dtoField -> dtoField.getName().equals(field.getName()))` and `Arrays.stream(dtoFields).noneMatch(f -> f.getName().equals(field))` seem to do the same thing. – Thomas Jan 11 '23 at 11:48
  • Is that `@Mapping` from MapStruct? Then you might be able to get what you want out of the box because the library should already be able to create errors on unmapped properties - at least to some degree (it might not come down to "add!" or "remove!" but rather something like "TestDto.phone is unmapped" and "TestEntity.address is unmapped") – Thomas Jan 11 '23 at 11:54
  • @Thomas Yes. It's from MapStruct – Taras Jan 11 '23 at 11:56
  • @Thomas still don't understand how to do it – Taras Jan 11 '23 at 13:24
  • Have a look here, this should provide some hints: https://www.baeldung.com/mapstruct-ignore-unmapped-properties - you might want to have MapStruct mappers emit an error instead of a warning at compile time. – Thomas Jan 11 '23 at 14:03

2 Answers2

0

This should do the trick. Please optimize the code to your needs.

{
    Map<String, Class<?>> testDtoDeclarationMap = getFieldDeclarationMap(new TestDto());
    Map<String, Class<?>> testEntityDeclarationMap = getFieldDeclarationMap(new TestEntity());

    testDtoDeclarationMap.forEach(((k, v) -> {
        if (!(testEntityDeclarationMap.containsKey(k) && testDtoDeclarationMap.get(k) == testEntityDeclarationMap.get(k))) {
            System.out.println("Extra fields for dto. Please remove!: '" + k + "' with the datatype: " + v);
        }
    }));

    testEntityDeclarationMap.forEach(((k, v) -> {
        if (!(testDtoDeclarationMap.containsKey(k) && testEntityDeclarationMap.get(k) == testDtoDeclarationMap.get(k))) {
            System.out.println("Missing fields for dto. Please add!: '" + k + "' with the datatype: " + v);
        }
    }));
}

Map<String, Class<?>> getFieldDeclarationMap(Object obj) {
    Map<String, Class<?>> map = new HashMap<>();
    Field[] declaredFields = obj.getClass().getDeclaredFields();
    for (int x=0; x<declaredFields.length; x++) {
        Field field = declaredFields[x];
        String name = field.getName();
        Class<?> type = field.getType();
        map.put(name, type);
    }
    return map;
}
Natsu
  • 443
  • 3
  • 10
0

I decided to create 'config' for mappings. For each entity.

private final Map<String, Map<String, String>> entityFieldMappings = new HashMap<>();

Then add some 'mappings'

 entityFieldMappings.put("TestEntity", Map.of("person", "personId"));

 entityFieldMappings.put("SampleEntity", Map.of(
        "customer", "customerId",
        "location", "locationId",
        "storageCondition", "storageConditionId"
    ));

Then created method for comparing entity and dto fields

private void compareFields(Class<?> entity, Class<?> dto, String entityName, Map<String, Map<String, String>> fieldMappings) {
    final Set<String> entityFieldNames = Arrays.stream(entity.getDeclaredFields()).map(Field::getName).collect(Collectors.toSet());
    final Set<String> dtoFieldNames = Arrays.stream(dto.getDeclaredFields()).map(Field::getName).collect(Collectors.toSet());

       Map<String, String> entityMappings = fieldMappings.get(entityName);
    
        Set<String> missingFields = entityFieldNames.stream()
            .filter(fieldName -> !dtoFieldNames.contains(fieldName) && !entityMappings.containsKey(fieldName))
            .collect(Collectors.toSet());
    
        Set<String> extraFields = dtoFieldNames.stream()
            .filter(fieldName -> !entityFieldNames.contains(fieldName) && !entityMappings.containsValue(fieldName))
            .collect(Collectors.toSet());
    
        if (!missingFields.isEmpty()) {
          log.error("Missing fields in Dto: " + dto.getName() + " for entity: " + entity.getName() + " are: " + missingFields);
        }
    
        if (!extraFields.isEmpty()) {
          log.error("Extra fields in Dto: " + dto.getName() + " for entity: " + entity.getName() + " are: " + extraFields);
        }
      }
Taras
  • 106
  • 6