Search code examples
kotlinjacksonjooq

Jooq: How can I map a JSONB column to a Kotlin data class field?


I have this table that has a metadata jsonb column, that's supposed to be a json array of data about other tables/PKs. I am able to insert rows into the database, but am having a hard time mapping the the record into the data class, due to this json column.

CREATE TABLE IF NOT EXISTS tracked_event
(
    id          uuid primary key,
    user_id     uuid references "user"      not null,
    -- other columns
    metadata jsonb not null
);

And I have a data class for it:

data class TrackedEvent(
    val id: UUID,
    val userId: UUID,
    // other fields
    val metadata: List<Metadata>
)

data class Metadata(
    val tableRef: String,
    val value: UUID
)

I can create a row just fine for it like so:

fun createTrackedEvent(trackedEvent: TrackedEvent): TrackedEvent {
        val record = dslContext.newRecord(TRACKED_EVENT, trackedEvent)
        record.metadata = JSONB.jsonb(objectMapper.writeValueAsString(trackedEvent.metadata))
        record.store()
        return record.into(TrackedEvent::class.java) // issue here
    }

However, that last line of code has a serializing issue:

Resolved [org.springframework.http.converter.HttpMessageNotWritableException: Could not write JSON: object is not an instance of declaring class; nested exception is com.fasterxml.jackson.databind.JsonMappingException: object is not an instance of declaring class (through reference chain: com.my.project.TrackedEvent["metadata"]->java.util.ArrayList[0]->java.util.LinkedHashMap["tableRef"])]

Note that if I change the data class to use an Array instead of a List, it works fine. But I think this should be able to work with the Kotlin's List instead?

data class TrackedEvent(
    val id: UUID,
    val userId: UUID,
    // other fields
    val metadata: Array<Metadata> // this works but then it asks me the following: Property with 'Array' type in a 'data' class: it is recommended to override 'equals()' and 'hashCode()' 
)



Solution

  • The best approach is to attach a Converter directly to your generated code as documented here:

    That way, the conversion from/to JSONB / List<MetaData> will be done transparently, whenever you access this information. Code generation configuration from the above documentation:

    <configuration>
      <generator>
        <database>
          <forcedTypes>
            <forcedType>
              <userType><![CDATA[kotlin.Array<com.example.Metadata>]]></userType>
              <jsonConverter>true</jsonConverter>
              <includeExpression>(?i:tracked_event\.metadata)</includeExpression>
            </forcedType>         
          </forcedTypes>
        </database>
      </generator>
    </configuration>
    

    See the docs for more details, and additional dependencies required.