How can the property example for discriminator be done? - kotlin

I'm generating swagger description with openapi annotations.
There is a case with polymorphic dto's with a discriminator property type that I can't handle:
#JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.PROPERTY,
property = "type"
)
#JsonSubTypes(
JsonSubTypes.Type(value = ListValue::class, name = "in"),
JsonSubTypes.Type(value = ExactValue::class, name = "is")
)
abstract class CompositeValue
and two subclasses:
#JsonTypeName("is")
data class ExactValue(
#get:Schema(
description = "Single condition value.",
example = "2"
)
val value: String
) : CompositeValue()
#JsonTypeName("in")
data class ListValue(
#get:Schema(
description = "Multiple condition value.",
example = "[2, 4, 6]"
)
val value: List<String>,
) : CompositeValue()
Openapi generates swagger example type field example "string", nut i expect one of two available values (is / in) to be there:
"filters": [
{
"name": "value",
"value": {
---> "type": "string", <---
"value": "2"
}
}
]
I couldn't find any solution (annotation or wmth) which I can use to make openapi to make a correct property example of type. Expected to use some jackson annotation but found nothing about it...
How such a case can be handled?

Related

How to access every SerialName associated with each enum constant in an enum class?

I have a data class that I want to make serializable
data class ClassTemp (
val str: String,
val tmp: Temp,
)
#Serializable
enum class Temp {
#SerialName("Serial Name - 1")
S1,
#SerialName("Serial Name - 2")
S2
}
Example JSON that I want to deserialize and it's corresponding ClassTemp object
{
"str": "Some String",
"tmp": "Serial Name - 1",
}
ClassTemp(
str: "Some String"
tmp: Temp.S1
)
Now the problem is not specifically about serializing and deserializing since that's working fine. It's just that for the given enum class Temp How would I get all the SerialNames that are in it?
[ "Serial Name - 1", "Serial Name - 2" ]
You can use reflection to get the annotation values of each enum field:
val serialNames = Temp.values().map {
Temp::class.java.getField(it.name).getAnnotation(SerialName::class.java)?.value
}

Populate data classes with values from linkedhashmap

I have a linkedhashmap that has the following shape: <String, Subject>. The class Subject has the following fields:
class Subject {
var name: Boolean? = null
var lastname: Boolean? = null
var location: Boolean? = null
..
}
final_result =
"admin" -> Subject
"customer" -> Subject
etc.
I need to populate data classes that have the following format:
data class SubjectSummary(
val admin: SubjectData,
val customer: SubjectData
...
)
data class SubjectData(val details: DetailsData)
data class DetailsData(val name:String, val lastName:String ...)
Because I need to serialize the SubjectSummary class and get the following json format:
{
"admin": {
"details": {
"name": "",
"lastname": "",
...
}
}
"customer": {
"details": {
"name": "",
"lastname": "",
...
}
}
}
How do I assign the final_result map to match the SubjectSummary structure? I have done it with simple data classes, but when the fields within the data class are data classes, I'm not sure hot to populate it. Any guidance?
For simplicity I'm only showing a small example with a few fields.
If your goal with this transformation is just to be able to serialize with the given JSON format, then you don't need this SubjectSummary class. A Map<String, SubjectData> would be sufficient and probably more convenient to create when transforming from the initial map.
Also, it seems that DetailsData contains the same fields as Subject. If that's the case there is no need for an extra class either.
So in the end it seems you just need to create a Map<String, SubjectData where SubjectData could be defined as data class SubjectData(val details: Subject). You can transform your initial map pretty easily then:
val transformed = finalResult.mapValues { (_, subject) -> SubjectData(subject) }

Spring JPA Projection not working with UUID types?

I have a project with Kotlin and Springboot.
In this project an entity have this following fields: id, name and parent.
What i want is to only get the id and the name. So i made a projection interface view for that entity.
This is my entity:
#Entity
data class Keyword(
#Id
#GeneratedValue(generator = "uuid2")
#GenericGenerator(name = "uuid2", strategy = "uuid2")
#Column(columnDefinition = "BINARY(16)")
var id: UUID? = null,
#Column(columnDefinition = "BINARY(16)")
var parent: UUID? = null,
#NotBlank
var name: String? = null
)
Repository:
#Repository
interface KeywordRepository: JpaRepository<Keyword, UUID> {
#Query(value = "SELECT keyword.id, keyword.parent, keyword.name FROM keyword LEFT JOIN project_keyword ON project_keyword.keyword_id = keyword.id WHERE project_keyword.project_id LIKE :id", nativeQuery = true)
fun findAllKeywordsByProjectId(id: UUID): MutableList<KeyWordView>
}
Service:
#Service
class KeywordService (
private val projectService: ProjectService,
private val keywordRepository: KeywordRepository
) {
fun getKeywordsByProjectId(id: UUID): MutableList<KeyWordView> {
projectService.checkIfProjectExistsById(id)
return keywordRepository.findAllKeywordsByProjectId(id).toMutableList()
}
}
My projection interface class:
interface KeyWordView {
val id: UUID
val name: String?
}
When i call this endpoint via controller class. I get this output:
"list": [
{
"name": "MARINE MICROBIOTA",
"id": "0,31,-36,77,29,123,66,-25,-127,-43,-31,83,104,-90,47,10"
}
]
But if i change the val id: String to val id: UUID in my KeywordView interface, i get this following error:
{
"code": 500,
"data": null,
"message": "Could not write JSON: Projection type must be an interface!; nested exception is com.fasterxml.jackson.databind.JsonMappingException: Projection type must be an interface! (through reference chain: no.inmeta.ris.util.response.ResponseDto[\"data\"]->no.inmeta.ris.util.pagination.PageDto[\"list\"]->java.util.ArrayList[0]->com.sun.proxy.$Proxy199[\"id\"])",
"status": "FAIL"
}
Anyone know how to solve this problem? I want to receive the UUID as UUID not with the strange format.
Thank you!
I guess the problem is the UUID Class. As your entity states you defined dir hibernate that the column in db is a binary(16). And i suppose using this UUID Type in the Interface projection does not Work since there is no valid mapping information (how to transform the data coming as binary with a length of 16 bytes to the UUID Class). So I assume you have to change the Type of your column or you have to use string and write a function to Transform that String.
Another possibility is to create a second entity for that table with Just the two cols you want. And Just use that second dao as your are using yiur projection.

How to deserialize JSON having references to abstract types in Jackson

I have an issue with object references to abstract classes and JSON serialization and deserialization. The abstracted issue looks like this:
I have a graph consisting of nodes and edges. Each edge connects two nodes. Nodes can be of flavor red and green. Therefore, there is an abstract class Node and two derived classes RedNode and GreenNode. A Node takes an id (#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")):
#JsonSubTypes({
#JsonSubTypes.Type(value = GreenNode.class, name = "GreenNode"),
#JsonSubTypes.Type(value = RedNode.class, name = "RedNode")
})
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
public abstract class Node {
public String id;
}
public class RedNode extends Node {
// ...
}
public class GreenNode extends Node {
// ...
}
An Edge has a source and a target of type Node, which are serialized as references (#JsonIdentityReference(alwaysAsId = true)):
public class Edge {
#JsonIdentityReference(alwaysAsId = true)
public Node source;
#JsonIdentityReference(alwaysAsId = true)
public Node target;
}
The graph is defined as follows:
public class Graph {
public List<GreenNode> greenNodes = new ArrayList();
public List<RedNode> redNodes = new ArrayList();
public List<Edge> edges = new ArrayList();
}
An example JSON looks as follows:
{
"greenNodes" : [ {
"id" : "g",
"content" : "green g",
"greenProperty" : "green"
} ],
"redNodes" : [ {
"id" : "r",
"content" : "red r",
"redProperty" : "red"
} ],
"edges" : [ {
"source" : "g",
"target" : "r"
} ]
}
Using an ObjectMapper cannot read this:
Can not construct instance of com.github.koppor.jsonidentityissue.model.Node: abstract types either need to be mapped to concrete types, have custom deserializer, or contain additional type information
The error location is "line: 13, column: 16". Thus, it is hit at the id of the edge. The nodes themselves are properly serialized.
A workaround is to add type information in the json:
#JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.PROPERTY,
property = "type")
public abstract class Node {
Then, everything works:
{
"greenNodes" : [ {
"id" : "g",
"type" : "GreenNode",
"content" : "green g",
"greenProperty" : "green"
} ],
"redNodes" : [ {
"id" : "r",
"type" : "RedNode",
"content" : "red r",
"redProperty" : "red"
} ],
"edges" : [ {
"source" : "g",
"target" : "r"
} ]
}
Then, everything works.
Is it really necessary to include type information in the referenced objects to have the reference working? Without the type information, a graph with red and green nodes (and no edges) can be loaded. After an edge comes in, it can't. However, the JSON of an edge contains an id only. The referenced objects are already parsed.
I really like to get rid off the #JsonTypeInfo annotation. Is there a way to have a clean JSON?
The full example is made available at https://github.com/koppor/jackson-jsonidentityreference-issue/tree/issue.
The current solution is to include fake type information. Full code at https://github.com/koppor/jackson-jsonidentityreference-issue.
The Node gets an existing property type, which is not written:
#JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.EXISTING_PROPERTY,
property = "type")
public abstract class Node {
#JsonIgnore
public abstract String getType();
}
Each subclass specifies itself as defaultImpl and provides an implementation of getType:
#JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.EXISTING_PROPERTY,
property = "type",
defaultImpl=GreenNode.class)
public class GreenNode extends Node {
#Override
public String getType() {
return "GreeNode";
}
}
This way, the JSON remains clean, but Jackson can resolve the id reference without any issues.

Can't POST a collection

I have a simple Entity with a single collection mapped.
#Entity
public class Appointment Identifiable<Integer> {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#JsonIgnore
private Integer id;
#Column(name="TRAK_NBR")
private String trackNumber;
#OneToMany(fetch =FetchType.EAGER, cascade= CascadeType.ALL)
#JoinColumn(name="CNSM_APT_VER_WRK_I", nullable = false)
private Set<Product> products = new HashSet<Product>();
}
#Entity
public class Product implements Identifiable<Integer> {
#Id
#Column(name = "CNSM_PRD_VER_WRK_I")
#GeneratedValue(strategy = GenerationType.AUTO)
#JsonIgnore
private Integer id;
#Column(name = "PRD_MDL_NBR")
private String model;
#Column(name = "PRD_SPEC_DSC")
private String description;
}
In my application when I only include a PagingAndSortingRepository for Appointment. I can call the POST command with the following payload.
{
"trackNumber" : "XYZ123",
"products": [
{"model" : "MODEL",
"description" : "NAME"
}]
}
When I add a PagingAndSortingRepository for Product and try the same POST I get the following error message.
{
"cause" : {
"cause" : {
"cause" : null,
"message" : null
},
"message" : "(was java.lang.NullPointerException) (through reference chain: com..model.Appointment[\"products\"])"
},
"message" : "Could not read JSON: (was java.lang.NullPointerException) (through reference chain: com.model.Appointment[\"products\"]); nested exception is com.fasterxml.jackson.databind.JsonMappingException: (was java.lang.NullPointerException) (through reference chain: com.model.AppointmentVerification[\"products\"])"
}
My GET payload with both Repositories returns this. This is my desired format. The link to products should be included
{
"trackNumber" : "XYZ123",
"_links" : {
"self" : {
"href" : "http://localhost:8080/consumerappointment/appointments/70"
},
"products" : {
"href" : "http://localhost:8080/consumerappointment/appointments/70/products"
}
}
With only the Appointment repository I get the following payload and can post the list of products.
{
"trackNumber" : "XYZ123",
"products" : [ {
"model" : "MODEL",
"description" : "NAME",
} ],
"_links" : {
"self" : {
"href" : "http://localhost:8080/consumerappointment/appointments/1"
}
}
}
Let's take a step back and make sure you understand what's happening here: if a repository is detected, Spring Data REST exposes a dedicated set of resources for it to manage the aggregates handled by the repository via HTTP. Thus, if you have repositories for multiple entities related to each other, the relationship is represented as a link. This is why you see the products inlined with only the AppointmentRepository in place and the products link in place once you create a ProductRepository.
If you want to expose both repositories as resources, you need to hand the URIs of the Product instances in the payload for the POST to create an Appointment. That means, instead of posting this:
{ "trackNumber" : "XYZ123",
"products": [
{ "model" : "MODEL",
"description" : "NAME"
}
]
}
you'd create a Product first:
POST /products
{ "model" : "MODEL",
"description" : "NAME" }
201 Created
Location: …/products/4711
And then hand the ID of the product to the Appointment payload:
{ "trackNumber" : "XYZ123",
"products": [ "…/products/4711" ]}
In case you don't want any of this (no resources exposed for Product in the first place, use #RepositoryRestResource(exported = false) on PersonRepository. That would still leave you with the bean instance created for the repo, but no resources exported and the resource exposed for Appointments back to inlining related Products.