How to write json class for Kotlin? - json

I have one interface
interface InspirationScreenItem
and entities which extend this interface , like this one for example
#Entity
#Table(name = "dish_of_the_day")
class DishOfTheDayEntity(
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
var id: Long? = null,
#Column(name = "title")
var title: String,
):InspirationScreenItem
and four other entities again extend this interface.
I have this entity which holds list of this interface: InspirationScreenItem
#Entity
#Table(name = "inspiration")
class InspirationEntity(
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
var id: Long? = null,
val inspirations: List<InspirationScreenItem>,
)
so I need to create DTO for the last entity
I've tried this approach
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type")
sealed class InspirationDTO {
#JsonTypeName("none")
object None : InspirationDTO()
#JsonTypeName("base")
class DishOfTheDay(
#JsonProperty("name")
#field:Size(min = 1, max = 20)
val name: List<DishOfTheDayEntity>,
): InspirationDTO()
...
the other entities the same way
}
but I'm not sure about this please could you give me some advice on this, is this correct way to proceed or I need a change

Related

How to solve "Could not write JSON: Infinite recursion (StackOverflowError)" in Kotlin using Data Classes?

I know there are several posts in the topic, but I just couldn't find one where Kotlin Data Classes were used. So I'm trying to make a REST API with H2 Database in Kotlin, using Spring Boot, and I'm using Postman as well. Some of the attributes of my classes have List type. And everytime I try to add some value to these lists in Postman and then try to get the results, I get the following error:
enter image description here
I have three classes:
Recipe.kt:
#Entity
data class Recipe(
#Id
#SequenceGenerator(name = RECIPE_SEQUENCE, sequenceName = RECIPE_SEQUENCE, initialValue = 1, allocationSize = 1)
#GeneratedValue(strategy = GenerationType.IDENTITY)
var id: Long = 0,
val name: String,
var cookTime: String?,
var servings: String?,
var directions: String?,
#OneToMany(cascade = [CascadeType.ALL], mappedBy = "recipe")
#JsonManagedReference
var ingredient: List<Ingredient>?,
#ManyToMany
#JsonManagedReference
#JoinTable(
name = "recipe_category",
joinColumns = [JoinColumn(name = "recipe_id")],
inverseJoinColumns = [JoinColumn(name = "category_id")]
)
var category: List<Category>?,
#Enumerated(value = EnumType.STRING)
var difficulty: Difficulty?
) { companion object { const val RECIPE_SEQUENCE: String = "RECIPE_SEQUENCE" } }
Category.kt
#Entity
data class Category(
#Id
#SequenceGenerator(name = CATEGORY_SEQUENCE, sequenceName = CATEGORY_SEQUENCE, initialValue = 1, allocationSize = 1)
#GeneratedValue(strategy = GenerationType.IDENTITY)
val id: Long = 0,
var name: String,
#ManyToMany(mappedBy = "category")
#JsonBackReference
var recipe: List<Recipe>?
) { companion object { const val CATEGORY_SEQUENCE: String = "CATEGORY_SEQUENCE" } }
Ingredient.kt
#Entity
data class Ingredient(
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
val id: Long = 0,
var description: String?,
var amount: BigDecimal?,
#ManyToOne
#JsonBackReference
var recipe: Recipe?,
var unitOfMeasure: String?
)
RecipeResponse.kt
data class RecipeResponse (var id:Long,
var name:String,
var cookTime:String?,
var servings:String?,
var directions:String?,
var ingredient:List<Ingredient>?,
var category: List<Category>?,
var difficulty: Difficulty?)
RecipeResource.kt
#RestController
#RequestMapping(value = [BASE_RECIPE_URL])
class RecipeResource(private val recipeManagementService: RecipeManagementService)
{
#GetMapping
fun findAll(): ResponseEntity<List<RecipeResponse>> = ResponseEntity.ok(this.recipeManagementService.findAll())
RecipeManagementService.kt
#Service
class RecipeManagementService (#Autowired private val recipeRepository: RecipeRepository,
private val addRecipeRequestTransformer: AddRecipeRequestTransformer) {
fun findAll(): List<RecipeResponse> = this.recipeRepository.findAll().map(Recipe::toRecipeResponse)
You can use #JsonIgnore as #Akash suggested but the result will not include category field in the response json. For a single recipe object response will look something like:
{"id":1,"name":"recipe"}
You can use #JsonManagedReference and #JsonBackReference instead. This way you will break the infinite loop but still have category list generated.
Your model will look something like:
data class Recipe(
var id: Long = 0,
val name: String,
#JsonManagedReference
var category: List<Category>,
)
data class Category(
val id: Long = 0,
var name: String,
#JsonBackReference
var recipe: List<Recipe>
)
and this will generate a json:
{"id":1,"name":"recipe","category":[{"id":1,"name":"cat"}]}
You have to add #JsonIgnore on top of your #ManyToOne and #OneToMany fields. Spring will ignore those fields where it is returning that object.
forex.
#Entity
data class Ingredient(
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
val id: Long = 0,
var description: String?,
var amount: BigDecimal?,
#ManyToOne
#JsonIgnore
var recipe: Recipe?, // This field will not be returned in JSON response.
var unitOfMeasure: String?
)
Here, also notice if you want to include some of the field of this ManyToOne or OneToMany relations in your response. You have to formulate your response using ObjectNode and then return it.
edit :
ObjectMapper objectMapper = new ObjectMapper();
#RestController
public ResponseEntity<String> someFunction() throws Exception {
ObjectNode msg = objectMapper.createObjectNode();
msg.put("success", true);
msg.put("field1", object.getValue1());
msg.put("field2", object.getValue2());
return ResponseEntity.ok()
.header("Content-Type", "application/json")
.body(objectMapper.writerWithDefaultPrettyPrinter()
.writeValueAsString(res));
}
The code here is in java you can convert this into Kotlin it will be same I think. Here, instead of object you can write your own object name ex. ingredient.getDescription() and can generate the response.

Jackson JSON infinite recursion without omiting anything from the serialization

I want to serialize and eventually deserialize an object to perform an export/import operation. I use Jackson library because of the extend annotation provided. I do break the infinite recursion by using the latest tags #JsonManagedReference, #JsonBackReference. But the problem here #JsonBackReference does omit the annotated part from the json file so I am not able to set the relationship while importing.
The relationship btwn entities can be shown:
public class A{
#Id
#Column(name = "ID", unique = true, precision = 20)
#SequenceGenerator(name = "a_generator", sequenceName =
"SEQ_A", initialValue = 1, allocationSize = 1)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator =
"a_generator")
private Long id;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "metricDefinition",
fetch = FetchType.LAZY, orphanRemoval = true)
#Fetch(FetchMode.SELECT)
#NotAudited
#JsonManagedReference
private Set<B> bSet= new HashSet<B>();
}
public class B{
#Id
#Column(name = "id", unique = true, precision = 20)
#SequenceGenerator(name = "b_generator", sequenceName = "seq_b", initialValue = 1, allocationSize = 1)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "b_generator")
private Long id;
#ManyToOne(cascade = {CascadeType.REFRESH})
#JoinColumn(name = "a_id")
#Fetch(FetchMode.SELECT)
#JsonBackReference(value = "a-b")
private A a;
#ManyToOne(cascade = {CascadeType.REFRESH})
#JoinColumn(name = "ref_a_id")
#Fetch(FetchMode.SELECT)
#JsonBackReference(value = "a-ref")
private A refA;
#Column(name = "is_optional")
#Fetch(FetchMode.SELECT)
private boolean isOptional;
#Column(name = "name")
#Fetch(FetchMode.SELECT)
private String name;
When I serialize any A object, it does serialize the B's included but the referenced A and refA are omitted. So, when I import A object of course the B's are also imported but I do want to the relationship between the objects to be set.
Is there any idea how can I break the infinite recursion without omitting the one side of the reference?
Thanks in advance
I did also try to use statement below according to answers given similar questions but it did not work so I asked the question above.
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class,
property = "#id")
it does not sufficient to break the circle. You should also add below annotation to the id property of your class.
#JsonProperty("id")
We can try to break the loop either at the Parent end or at the Child end by following 3 ways
Use #JsonManagedReference and #JsonBackReference
Use #JsonIdentityInfo
Use #JsonIgnore
Use #JsonIdentityInfo
#Entity
#Table(name = "nodes")
#JsonIdentityInfo(
generator = ObjectIdGenerators.PropertyGenerator.class,
property = "id")
public class Node {
...
}
#Entity
#Table(name = "relations")
#JsonIdentityInfo(
generator = ObjectIdGenerators.PropertyGenerator.class,
property = "id")
public class Relation {
...
}
Refer more in detail here with the working demo at the end.

Spring data JPA only one composite key auto incremented

I am using MySQL database.
My table is an employee in which there are two primary keys, out of which one is auto incremented.
My code is:
#Embeddable
public class EmployeeId implements Serializable {
/**
*
*/
private static final long serialVersionUID = 1L;
#Column(name = "id", nullable = false)// this i want to increment
private int id;
// I have tried and #GeneratedValue(strategy = GenerationType.IDENTITY),
//#GeneratedValue(strategy = GenerationType.IDENTITY)
//and #GeneratedValue(strategy = GenerationType.TABLE)
//#GeneratedValue(strategy = GenerationType.AUTO, generator = "id") #SequenceGenerator(name = "id", sequenceName = "id")
#Column(name = "gender_key", nullable = false)
private id gender_key;
}
#Entity
#Table(name = "employee")
public class employee {
#EmbeddedId
private EmployeeId employeeId;
private String emp_name;
private String mobile_no;
employee() {
}}
public interface employeeRepository extends
JpaRepository<employee, EmployeeId> {
}
In My Controller I want id after employeeRepository.save(bean); method because i want to save that id in different table .
logger.info("id is --- > "+id);
But I am getting always 0 value of id.
How can I get the incremented value of id which is inserted into MySQL table?
Please Help.
Thanks in advance.
Are you trying to get the value from your original object or from the object return from the save method? Let's assume you have the following code:
employeeRepository.save(bean)
int id = bean.getId();
logger.info("id is --- > "+id);
This will indeed return zero, since your object didn't have this information. If you want to get the value that was generated on the database you have to do the following:
bean = employeeRepository.save(bean)
int id = bean.getId();
logger.info("id is --- > "+id);

Glassfish 4.1 - JSON webservice with EclipseLink and MOXy: #EmbeddedId duplication troubles

I use GlassFish 4.1 (NetBeans), EclipseLink and the default MOXy json binding.
I have an entity with a composite primary key. I need a flat json structure for both input and output. It seems straight-forward, but...
If I do nothing special, I get a flatten json when marshalling, but the unmarshalling does not work (key = null).
If I add the annotation #XmlPath("."), then it is the opposite: the unmarshalling works, but the key fields are duplicated in the json.
Also, MOXy seems the add a type field in the json, which I never asked.
Entity classes:
The PK:
#Embeddable
public class SensorPk implements Serializable {
#Column(name = "sensor_id")
private Integer id;
#Column(name = "sensor_address")
#NotNull
#Size(min = 1, max = 10)
private String address = ADDRESS_DEFAULT;
// + getter/setters
}
The entity:
(the #org.eclipse.persistence.oxm.annotations.XmlPath is commented)
#Entity
#XmlElement
#Table(name = "sensors")
public class Sensor implements Serializable{
#EmbeddedId
// #XmlPath(".")
private SensorPk composedId;
#Column(name = "sensor_name")
#Size(min = 1, max = 45)
private String name;
// + getter/setters
}
The application configuration:
#javax.ws.rs.ApplicationPath("api")
public class ApplicationConfig extends ResourceConfig {
public ApplicationConfig() {
packages("ch.derlin.glf.bbdata");
}
}
I also tried to install jackson (1.X and 2.X), but impossible to make it work on glassfish 4.
The output without any annotation:
XML:
<sensors>
<sensor>
<address>noaddress</address>
<id>24</id>
<name>TEST</name>
</sensor>
</sensors>
JSON:
[
{
"type":"sensor",
"address":"noaddress",
"id":24,
"name":"TEST MONNEY"
}
]
Nice, but the unmarshalling of the same json fails: id and address are null. And also, what the hell is this type field ?
With annotation:
XML: idem.
JSON:
[
{
"type":"sensor",
"address":"noaddress",
"id":24,
"address":"noaddress",
"id":24,
"name":"TEST MONNEY"
}
]
But the unmarshalling works properly.
Any idea guys ?
Ok, for those with the same problem, I finally made it work be replacing #EmbeddedId with #IdClass.
The class SensorPk is left untouched, but the Sensor class is rewritten like this:
#IdClass(SensorPk.class)
public class Sensor implements Serializable {
#Column(name = "sensor_id")
#Id private Integer id;
#Column(name = "sensor_address")
#NotNull
#Size(min = 1, max = 10)
#Id private String address = ADDRESS_DEFAULT;
#Column(name = "sensor_name")
#Size(min = 1, max = 45)
private String name;
}
Changes are:
the annotation #IdClass is added at the top,
the SensorPk fields are copy pasted with the annotation #Id

How to read specific fields from mapped object in Hibernate

I've got two entity objects in my database: UserEntity and ItemEntity and they're mapped with OneToMany relationship.
Here is my code:
UserEntity:
#Entity
#Table(name = "users")
public class UserEntity {
#Id
#Column(name = "user_id")
#GeneratedValue
public int user_id;
#Column(name = "userlogin")
public String userlogin;
#Column(name = "userpass")
public String userpass;
#Column(name = "name")
public String name;
#Column(name = "email")
public String email;
....
#JsonBackReference
#OneToMany(mappedBy="user", cascade = { CascadeType.MERGE },fetch=FetchType.EAGER)
private List<ItemEntity> items;
ItemEntity:
#Entity
#Table(name = "items")
public class ItemEntity {
#Id
#Column(name = "id")
#GeneratedValue
private int id;
#Column(name = "title")
public String title;
#Column(name = "info")
public String info;
#JsonManagedReference
#ManyToOne
#JoinColumn(name="user_id")
private UserEntity user;
And now I'm trying to read all my Items from my database with specific fields from users that owns current item. I need only UserEntity name and email.
This code:
Query query = this.sessionFactory.getCurrentSession().createQuery("from ItemEntity WHERE title = :title");
returns all fields from UserEntity also, because it's mapped, but I don't want that, because I'm sending that data as JSON, and someone can see all informations about user who own that item (like user login and password) in some dev tools like Chrome.
How to reach that?
I'd suggest you use DTO.
Covert your entities to DTO and then transform the DTO objects to
json string.
In the DTO populate only those field that you want as part of your response.
This would make your design more clean.
In addition to what's jitsonfire is suggesting, you can write a query like this
select name, email from ItemEntity WHERE title = :title
than get your results like
List<Object[]> result = query.list();
The object array will contain your columns, the list element will equal to rows, so you can do something like
for (Object[] tuple : result) {
tuple[0]; //name
tuple[1]; // email
}