Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for interface projection using Kotlin "is"-properties #3127

Closed
sttomm opened this issue Jul 29, 2024 · 4 comments
Closed

Add support for interface projection using Kotlin "is"-properties #3127

sttomm opened this issue Jul 29, 2024 · 4 comments
Assignees
Labels
type: enhancement A general enhancement

Comments

@sttomm
Copy link

sttomm commented Jul 29, 2024

When I upgraded our existing application from Spring Boot 3.1.12 to 3.2.8 I ran into the following error, which was not present before the upgrade:
.w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.http.converter.HttpMessageNotWritableException: Could not write JSON: Invalid property 'valid' of bean class [io.meshcloud.entitymodel_projection_issue.model.TestEntity]: Could not find field for property during fallback access]

I reproduced the issue with a minimal Spring Boot Application using Spring Data and Spring HATEOAS. The Controller is using a ProjectionFactory directly.

@GetMapping("/directProjections")
fun findDirectProjection(): ResponseEntity<*> {
    val entities = repo.findAll(PageRequest.of(0, 10))

    val projectedEntities = entities.map {
        projectionFactory.createProjection(EntityWithIsFieldProjection::class.java, it)
    }

    return ResponseEntity.ok(projectedEntities)
}

The entity looks like this (it has some "is"-properties):

@Entity
class TestEntity(
        @Id
        val id: Long,
        val mandatoryValue: String?,

        @get:JsonProperty("isGreat")
        val isGreat: Boolean
) {
    @get:JsonProperty("isValid")
    val isValid: Boolean
        get() = mandatoryValue != null
}

And the projection like this (also with the "is"-properties):

interface EntityWithIsFieldProjection {

    val projectedValue: String?
        @Value("#{target.mandatoryValue}")
        get() = projectedValue

    @get:JsonProperty("isGreat")
    val isGreat: Boolean

    @get:JsonProperty("isValid")
    val isValid: Boolean
}

When I call the controller endpoint, I get the error mentioned above. Sadly I could not get the Stacktrace for it, as the DefaultExceptionResolver just logged it as a warning.

Here is the small sample project I wrote to reproduce the issue:
failing-projection-json-mapping.zip

You can simply switch the spring boot version to 3.1.12 in there to see, that all tests in EntityModelTests succeed. But with 3.2.8 two of them fail.

I added some counter tests to see what happens if I don't use a projection or if I have a project that does not have a field starting with is and as expected, those cases work fine. So it seems to be something deep down in Spring Data Projections that leads to this issue.

I hope I provided sufficient details for you to reproduce the issue and provide a fix for it. I would be happy for a quick workaround if you could imagine any. I will also continue finding a workaround, as I don't want to rename all affected entities in our real application to not use "is"-properties.

@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged label Jul 29, 2024
@sttomm
Copy link
Author

sttomm commented Jul 29, 2024

Looks like a workaround is using SPEL on the projection. With changing the projection as follows, it works:

interface EntityWithIsFieldProjection {

    val projectedValue: String?
        @Value("#{target.mandatoryValue}")
        get() = projectedValue

    @get:JsonProperty("isValid")
    @get:Value("#{target.isValid}")
    val valid: Boolean
}

@sttomm
Copy link
Author

sttomm commented Jul 29, 2024

Just noticed now, that I created it in the wrong spring-data repository, sorry! I think it probably fits best to spring-data-commons? Can someone move it over?

@mp911de mp911de transferred this issue from spring-projects/spring-data-jpa Jul 30, 2024
@mp911de
Copy link
Member

mp911de commented Jul 30, 2024

Thanks for letting us know. I moved the ticket into Spring Data Commons.

@mp911de
Copy link
Member

mp911de commented Jul 30, 2024

The underlying issue is that Kotlin rewrites getters to is<property-name-without-is>. The actual property name is isValid and Java Beans spec expects a getter named isIsValid. Due to Kotlin rewriting magic, all these assumptions are broken and we need to add a translation of the property name to unwind what Kotlin did to the property name.

With Spring Data 3.2 we added KotlinBeanInfoFactory that uses Kotlin reflection metadata to derive Bean info.

@mp911de mp911de added type: enhancement A general enhancement and removed status: waiting-for-triage An issue we've not yet triaged labels Jul 30, 2024
@mp911de mp911de changed the title Jackson Mapping with Kotlin of Projections with "is"-properties fails Add support for interface projection using Kotlin "is"-properties Jul 30, 2024
@christophstrobl christophstrobl self-assigned this Jul 30, 2024
mp911de pushed a commit that referenced this issue Aug 1, 2024
This commit makes sure to use the target objects method to determine the property used for the projection.

Closes: #3127
Original pull request: #3129
mp911de pushed a commit that referenced this issue Aug 1, 2024
This commit makes sure to use the target objects method to determine the property used for the projection.

Closes: #3127
Original pull request: #3129
@mp911de mp911de closed this as completed in 431a1f5 Aug 1, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
type: enhancement A general enhancement
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants