Skip to content

@DynamicLabel causing objects to be instantiated as incorrect child type #3036

@keenan-joyce-lg

Description

@keenan-joyce-lg

When using the @DynamicLabels annotation, objects are getting instantiated as the incorrect type if there are values added to the dynamic labels set. This issue has been tested and confirmed on as late as SDN version 7.5.2. This issue also seems similar to this ticket from last year:
#2886.

Here is an example that demonstrates the issue:

Entities

@Getter
@Setter
@NoArgsConstructor
@Node(primaryLabel = "Vehicle")
public class Vehicle {
    @Id
    public String id;

    @Property(name = "name")
    public String name;

    @JsonIgnore
    @DynamicLabels
    public Set<String> labels = Set.of();
}

@Getter
@Setter
@NoArgsConstructor
@Node(primaryLabel = "Car")
public class Car extends Vehicle {
}
@Getter
@Setter
@NoArgsConstructor
@Node(primaryLabel = "Sedan")
public class Sedan extends Car {
}

Repository

@Repository
public interface VehicleRepository extends Neo4jRepository<Vehicle, String> {
    @Query("MATCH (vehicle:Vehicle) RETURN vehicle")
    List<Vehicle> findAllVehicles();
}

Test

@DataNeo4jTest
class VehicleRepositoryTest (
    @Autowired val vehicleRepository: VehicleRepository,
) {
    @Test
    fun `dynamic labels cause incorrect sub-types`() {
        val vehicleWithDynamicLabel = Vehicle().apply {
            name = "Vehicle with dynamic label"
            labels = setOf("Random Label")
        }
        val vehicleWithTwoDynamicLabels = Vehicle().apply {
            name = "Vehicle with dynamic label"
            labels = setOf("Random Label", "Random Label 2")
        }
        val vehicleWithoutDynamicLabel = Vehicle().apply {
            name = "Vehicle without dynamic label"
        }
        vehicleRepository.saveAll(listOf(vehicleWithDynamicLabel, vehicleWithoutDynamicLabel, vehicleWithTwoDynamicLabels))

        val vehicles = vehicleRepository.findAllVehicles();
        assertThat(vehicles.filterIsInstance<Sedan>()).isEmpty()
        assertThat(vehicles.filterIsInstance<Car>()).isEmpty()
    }
}

The above test fails, because vehicleWithDynamicLabel and vehicleWithTwoDynamicLabels get mapped to Car objects when retrieved from the database, when they are nodes of the parent type Vehicle. vehicleWithoutDynamicLabel is correctly mapped to a Vehicle object because it does not have dynamic labels. With one or two dynamic labels, both Vehicle nodes still mapped to Car objects, neither were instantiated as the grandchild type Sedan.

We can see in the following picture, that two of the three Vehicle nodes get mapped to Car because they have dynamic labels:
Image

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions