[JAVA-3915] Unable to map Kotlin data class with an annotated field Created: 18/Dec/20  Updated: 08/Jan/21  Resolved: 08/Jan/21

Status: Closed
Project: Java Driver
Component/s: POJO
Affects Version/s: 4.1.1
Fix Version/s: None

Type: New Feature Priority: Major - P3
Reporter: Lammert Westerhoff Assignee: Ross Lawley
Resolution: Duplicate Votes: 0
Labels: None
Remaining Estimate: Not Specified
Time Spent: Not Specified
Original Estimate: Not Specified

Issue Links:
Duplicate
is duplicated by JAVA-3923 Add PojoCodec support for Kotlin data... Closed
Related
related to JAVA-3923 Add PojoCodec support for Kotlin data... Closed

 Description   

I'm using the PojoCodecProvider to map to Kotlin data classes. When the fields of those data classes contain annotations (unrelated to MongoDB) then an exception is thrown.

Stacktrace:

java.lang.NullPointerException: null
	at org.bson.codecs.pojo.PojoBuilderHelper.isAssignableClass(PojoBuilderHelper.java:169)
	at org.bson.codecs.pojo.PojoBuilderHelper.getOrCreateMethodPropertyMetadata(PojoBuilderHelper.java:160)
	at org.bson.codecs.pojo.PojoBuilderHelper.configureClassModelBuilder(PojoBuilderHelper.java:94)
	at org.bson.codecs.pojo.ClassModelBuilder.<init>(ClassModelBuilder.java:60)
	at org.bson.codecs.pojo.ClassModel.builder(ClassModel.java:68)
	at org.bson.codecs.pojo.PojoCodecProvider.createClassModel(PojoCodecProvider.java:215)
	at org.bson.codecs.pojo.PojoCodecProvider.getPojoCodec(PojoCodecProvider.java:82)
	at org.bson.codecs.pojo.PojoCodecProvider.get(PojoCodecProvider.java:72)
	at org.bson.internal.ProvidersCodecRegistry.get(ProvidersCodecRegistry.java:45)
	at org.bson.internal.ProvidersCodecRegistry.get(ProvidersCodecRegistry.java:57)
	at org.bson.internal.ProvidersCodecRegistry.get(ProvidersCodecRegistry.java:39)
	at com.mongodb.internal.operation.Operations.createFindOperation(Operations.java:131)
	at com.mongodb.internal.operation.Operations.find(Operations.java:121)
	at com.mongodb.internal.operation.SyncOperations.find(SyncOperations.java:93)
	at com.mongodb.client.internal.FindIterableImpl.asReadOperation(FindIterableImpl.java:206)
	at com.mongodb.client.internal.MongoIterableImpl.execute(MongoIterableImpl.java:135)
	at com.mongodb.client.internal.MongoIterableImpl.iterator(MongoIterableImpl.java:92)
	at com.mongodb.client.internal.MongoIterableImpl.iterator(MongoIterableImpl.java:39)
	
        ....
	at java.lang.Thread.run(Thread.java:748)
2020-12-18 09:25:11.341 [nioEventLoopGroup-4-1 @call-handler#4] ERROR Application - Unhandled: GET - /restaurants
org.bson.codecs.configuration.CodecConfigurationException: Can't find a codec for class com.fificard.domain.Restaurant.
	(Coroutine boundary)
	...
	at java.lang.Thread.run(Thread.java:748)
Caused by: org.bson.codecs.configuration.CodecConfigurationException: Can't find a codec for class com.fificard.domain.Restaurant.
	at org.bson.internal.CodecCache.getOrThrow(CodecCache.java:57)
	at org.bson.internal.ProvidersCodecRegistry.get(ProvidersCodecRegistry.java:64)
	at org.bson.internal.ProvidersCodecRegistry.get(ProvidersCodecRegistry.java:39)
	at com.mongodb.internal.operation.Operations.createFindOperation(Operations.java:131)
	at com.mongodb.internal.operation.Operations.find(Operations.java:121)
	at com.mongodb.internal.operation.SyncOperations.find(SyncOperations.java:93)
	at com.mongodb.client.internal.FindIterableImpl.asReadOperation(FindIterableImpl.java:206)
	
	... 13 common frames omitted

It seems that it sees the annotation itself as a property to map.
 



 Comments   
Comment by Ross Lawley [ 08/Jan/21 ]

I've fixed the NPE for now and have added JAVA-3923 to look to add support in the future.

Comment by Githook User [ 08/Jan/21 ]

Author:

{'name': 'Ross Lawley', 'email': 'ross.lawley@gmail.com', 'username': 'rozza'}

Message: Prevent NPE error in Pojo's with synthetic properties

JAVA-3915
Branch: master
https://github.com/mongodb/mongo-java-driver/commit/5008b2a9445e477d563258eaaf4786f0f0ed83aa

Comment by Jeffrey Yemin [ 18/Dec/20 ]

It looks like kmongo has vendored the driver's POJO mapper and modified it to work with Kotlin data classes.

Comment by Lammert Westerhoff [ 18/Dec/20 ]

I've just tried kmongo and it works fine. I first used the default Jackson engine to map to POJOs. But even when I use the POJO Codes engine as documented on https://litote.org/kmongo/quick-start/ it keeps working, which I was not expecting since I assumed it would run the same code path.

Comment by Jeffrey Yemin [ 18/Dec/20 ]

Simplified data class that demonstrates the issue:

import kotlinx.serialization.Transient
 
data class Restaurant(
        @Transient
        var name: String? = null,
)

The problem seems to be that the POJO mapper is trying to map a synthetic property based on the generated method void getName$annotations().

Comment by Jeffrey Yemin [ 18/Dec/20 ]

OK, you might want to try using kmongo. I'm curious if it will work there.

One issue is that we don't test the POJO mapper with Kotlin data classes, so I'm not surprised that there are issues. I tried first with a similar POJO and it works as expected:

import com.mongodb.lang.Nullable;
 
import java.util.List;
 
public class JAVA3915Record {
 
    @Nullable
    private List<String> localizedDetails;
 
    public JAVA3915Record() {}
    
    public JAVA3915Record(@Nullable final List<String> localizedDetails) {
        this.localizedDetails = localizedDetails;
    }
 
    public List<String> getLocalizedDetails() {
        return localizedDetails;
    }
 
    public void setLocalizedDetails(final List<String> localizedDetails) {
        this.localizedDetails = localizedDetails;
    }
}

Comment by Lammert Westerhoff [ 18/Dec/20 ]

I'm actually not familiar with kmongo. Though it seems interesting and I'll have a look. But in my case I'm using the Java driver directly:

val connString = ConnectionString(
    "mongodb+srv://...."
)
 
val pojoCodecRegistry: CodecRegistry =
    CodecRegistries.fromProviders(PojoCodecProvider.builder().automatic(true).build())
val codecRegistry: CodecRegistry = CodecRegistries.fromRegistries(
    MongoClientSettings.getDefaultCodecRegistry(),
    pojoCodecRegistry
)
 
val settings = MongoClientSettings.builder()
    .applyConnectionString(connString)
    .codecRegistry(codecRegistry)
    .retryWrites(true)
    .build()
val mongoClient = MongoClients.create(settings)
val database = mongoClient.getDatabase("....")

And then fetching the records:

val collection = database.getCollection("restaurants", Restaurant::class.java)
val records = collection.find(filter)
val restaurants = records.toList()

Comment by Jeffrey Yemin [ 18/Dec/20 ]

Just one more question, I think: are you using the kmongo wrapping driver, or using the Java driver directly?

Comment by Lammert Westerhoff [ 18/Dec/20 ]

Sure.

package com.fificard.domain
 
import kotlinx.serialization.Serializable
import kotlinx.serialization.Transient
 
@Serializable
data class Restaurant(
    var name: String = "",
    var details: String? = null,
    @Transient
    var localizedDetails: List<LocalizedString>? = null,
    var contact: Contact? = null,
    var priceCategory: Int? = null,
    var cuisines: List<String> = emptyList(),
    var categories: List<String> = emptyList(),
) {
    fun localized(language: Language) = copy(details = localizedDetails?.localized(language))
}
 
private fun List<LocalizedString>.localized(language: Language): String? {
    val localized = firstOrNull { it.language == language.languageCode } ?: firstOrNull { it.language == fallbackLanguage }
    return localized?.value
}
 
@Serializable
data class Contact(var email: String? = null, var phone: String? = null)
 
@Serializable
data class LocalizedString(var language: String = fallbackLanguage, var value: String = "")

As you can see it's in Kotlin. And we're using Kotlin serialization to later serialize the POJO to JSON. So it's unrelated to MongoDB as I mentioned. But still the @Transient annotation is causing the exceptions. (Same happens when I use kotlinx.serialization.SerialName). Without it it works fine. The @Serializable at class level does NOT cause any problems.

Comment by Jeffrey Yemin [ 18/Dec/20 ]

Hi fiftyfifty@paoapps.com

Can you provide a sample POJO that demonstrates the problem?

Thanks,
Jeff

Generated at Thu Feb 08 09:00:46 UTC 2024 using Jira 9.7.1#970001-sha1:2222b88b221c4928ef0de3161136cc90c8356a66.