Define deserialization type with GSON - java

I'm using Google Gson to de/serialize my app database to/from JSON. The app uses Room database with a custom class which has more than 20 fields, so for semplicity let's consider an example one. I recently changed the type of a field from String to JSON; Room managed the migration fine - just by creating a converter class and adding a void version migration - but I'm not sure how to correclty handle Gson.
Here is the object class, respectively the old and the new one:
Old exClass.java
#Entity(tableName = "example_class")
public class exClass {
#PrimaryKey(autoGenerate = true)
public int uid;
#ColumnInfo(name = "name")
public String name;
#ColumnInfo(name = "json")
public String json;
}
New exClass.java
#Entity(tableName = "example_class")
#TypeConverters({Converters.class})
public class exClass {
#PrimaryKey(autoGenerate = true)
public int uid;
#ColumnInfo(name = "name")
public String name;
#ColumnInfo(name = "json")
public JSONObject json;
}
Now when trying to restore old database backups (created with the old version of the class) Gson throws an exception, because it's expecting a JSON object for the field "json" but finds a String.
Is there a way to override the type Gson expects without having to create a custom JsonSerializer<exClass>?
Note that the JSONObject is still fine as far as Room is concerned: the converter converts "json" to a String before inserting it into the database and viceversa, so it's only a matter of having Gson correcly parse the "json" field.
Thanks in advance

Related

How can I add an additional json parent node when mapping a DTO?

I'm working on some data product mapping, and I stumbled across an issue I'm not sure how to solve.
Suppose I have a HumanMood data product. I'm receiving pushed data (HumanMoodInputDto) through a POST request method received by WebFlux controller.
The HumanMoodInputDto DTO:
#Data
public class HumanMoodInputDto {
#JsonProperty("human_mood")
#NotNull(message = "Human mood can't be null ")
private String humanMood;
#JsonProperty("last_update")
#Pattern(regexp = "([0-9]{4})-([0-9]{2})-([0-9]{2})", message = "Date format must be YYYY-MM-DD")
#NotNull(message = "Last update can't be null ")
private String lastUpdate;
}
I'm using MapStruct to map the data product. And the response that my controller is returning is based on this HumanMoodOutputDto:
#Data
#JsonRootName(value = "data")
public class HumanMoodOutputDto {
#JsonProperty("human_mood")
private String humanMood;
#JsonProperty("last_update")
#JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd")
private LocalDate lastUpdate;
#JsonProperty("updated_by")
private String updatedBy;
}
The #PostMapping for WebFlux controller:
#PostMapping("humanMood")
public Mono<HumanMoodOutputDto> getHumanMood(
#Valid #RequestBody
Mono<HumanMoodInputDto> humanMoodInputDto) {
return humanMoodService.create(humanMoodInputDto.map(mapper::dtoToPushedInput))
.map(mapper::outputToDto);
}
So at the moment I'm getting response that looks like this:
{
"data":{
"human_mood":"good",
"last_update":"2021-08-19",
"update_by":null
}
}
What I want to accomplish is to add an additional json parent node called "data_flags", which would hold ArrayList<String> values, that would describe any errors or warnings when mapping the data. So in the end I want my output to look like this:
{
"data":{
"human_mood":"good",
"last_update":"2021-08-19",
"update_by":null
}
"data_flags":[
]
}
Should I create a seperate DTO and then another one which which would contain data from both of them, or is there a more simple way to enrichen the data product?
The simplest way in my opinion is to build a separate DTO that would have the following attributes:
private HumanMoodOutputDto data;
#JsonProperty("data_flags")
private HumanMoodMappingWarningOutputDto dataFlags;
This would allow you to get rid of #JsonRootName(value = "data") in HumanMoodOutputDto.

Spring boot mongodb not saving uniqueId

I'm working on a Spring application and I'm using MongoDB as my database. I have a document structure where I am saving the id of another document to use as a reference. This id is an objectId and then save it using
mongoOperations.save(message)
It is using the same objectId as the one I'm saving for reference to create _id field for this newly created document. So my document is like this
{
"_id":{
"$oid":"610a03578c9e4937107b6501"
},
"ConversationId":{
"$oid":"610a03578c9e4937107b6501"
},
"Author":"author",
"Body":"hey there",
"CreatedAt":{
"$date":{
"$numberLong":"1628171556888"
}
}
}
As you can see that both the ids for _id and ConversationId are the same. I have tried saving ConversationId as a string and it still does the same. Not sure what mistake I'm making.
{
"_id":{
"$oid":"610a03578c9e4937107b6501"
},
"ConversationId": "610a03578c9e4937107b6501",
"Author":"author",
"Body":"hey there",
"CreatedAt":{
"$date":{
"$numberLong":"1628171556888"
}
}
}
This is my model class
#Document(collection = "messages")
public class Message {
#Id
private String id;
#Field("ConversationId")
private String conversationId;
#Field("Author")
private String author;
#Field("Body")
private String body;
#Field("CreatedAt")
private Instant createdAt;
}
COnversationId in the above model class id an objectId and I tried saving it as a string as mentioned above so It was set to type String here. I have also tried making it ObjectId and still the same issue persists.
How to make it create a unique _id for each record and not use conversationId as its id.
I think you need to change id to _id. Attaching reference below
#Document(collection = "messages")
public class Message {
private String _id;
#Field("ConversationId")
private String conversationId;
#Field("Author")
private String author;
#Field("Body")
private String body;
#Field("CreatedAt")
private Instant createdAt;
}

Using ObjectId as String in Java (Manual reference) with spring-data mongodb

In MongoDB documentation they suggest to use ObjecId for manual references.
please see https://docs.mongodb.com/manual/reference/database-references/#document-references
original_id = ObjectId()
db.places.insert({
"_id": original_id,
"name": "Broadway Center",
"url": "bc.example.net"
})
db.people.insert({
"name": "Erin",
"places_id": original_id,
"url": "bc.example.net/Erin"
})
I'm using spring-data-mongodb and what I'm looking for is to have a People class defined like this:
#Document
public class People {
private String name;
#Reference // or any Annotation to convert an ObjectId to a String
private String placesId;
private String url;
}
How to have a "places_id" as ObjectId in mongoDB but mapped to a String in our POJO ?
I was expecting to have an annotation like #Reference but it seems to not be implemented.
I don't understand why we don't have this kind of annotation in spring-data-mongodb. I don't want to implement an explicit converter like suggested in spring documentation for all documents that use manual references.
Maybe it's not the right approach.
Did I miss something ?
UPDATE :
I like the idea to have a POJO using only String instead of ObjectId. Let's say I've got a class Place like this :
#Document
public class Place {
#Id
private String id;
private String name;
}
place.getId() will be a String but people.getPlaceId() will be an ObjectId. I want to avoid this unnecessary mapping.
The solution would be:
import org.springframework.data.mongodb.core.mapping.Field;
import org.springframework.data.mongodb.core.mapping.FieldType;
public class People {
#Field(targetType = FieldType.OBJECT_ID)
private String placesId;
}
This will map POJO string to ObjectId in MongoDB.
Why don't you leave the field as ObjectId?
#Document
public class People {
private String name;
private ObjectId placesId;
private String url;
}
If you want to query by this field you can do this:
For lists
List<String> ids // the ids as strings
List<ObjectId> objIds = ids .stream()
.map(i -> new ObjectId(i))
.collect(Collectors.toList());
For single String
String id // single id
ObjectId objId = new ObjectId(id);
If you want to make a real reference to an other object in your database, use the #DBRef annotation which is provided by Spring Data.
Your updated code could look like the following:
#Document
public class People {
private String name;
#DBRef
private Place place;
private String url;
}
Spring Data will then automatically map a Place object to your People object. Internally this is done with a reference to the unique ObjectId. Try this code and have a look at your mongo database.
For more information have a look at: MongoDb with java foreign key
I have a solution very simple:
#JsonSerialize(using= ToStringSerializer.class)
private ObjectId brandId;
...
put that on the attribute that is Object Id, and the ObjectId gets and inserts like string

Save entity containing gson atribute to ElasticSearch in Spring

My Spring application uses Spring-data-elasticsearch (https://github.com/spring-projects/spring-data-elasticsearch).
I would like to save following document to the elasticsearch database:
#Document(indexName = "documents", type = "customEntity", replicas = 0, shards = 5)
public class CustomEntity implements Serializable{
#Id
private String Id;
#Field(type = FieldType.String)
private String Field1;
#Field(type = FieldType.Integer)
private int Field2;
#Field(type = FieldType.Object) //not sure which annotation I should use
private JsonObject exportJSON; //gson object
...getters and setters...
}
using this way:
public class CustomEntityDao {
#Resource
ElasticsearchTemplate elasticsearchTemplate;
public void insertCustomEntity(CustomEntity entity){
IndexQuery indexQuery = new IndexQuery();
indexQuery.setId(entity.getCustomEntityId());
indexQuery.setObject(entity);
elasticsearchTemplate.index(indexQuery); //exception thrown
}
}
but I'm getting this error:
com.fasterxml.jackson.databind.JsonMappingException: JsonObject
(through reference chain: data.nosql.entities.CustomEntity
["exportJSON"]->com.google.gson.JsonObject["asString"])
I understand the problem but I don't have clue how to solve it. Any ideas please?
My guess is that jackson tries to covert your gson object to a string to be indexable by ES and it doesn't know how to do that. If you post your full stack trace it'll be more helpful, but if i have to take a guess you need to use a #JsonSerializer annotation on your "exportJSON" object.

Foreign Key update from JSON using ORMLite

I am using ORMLite within an Android application along side Gson and currently struggling with the following issue.
Within my app, there are multiple classes that make use of ORMLite/Gson, for simplicity I shall describe the issue using only two.
Say we have a class Product:
#SerializedName("product_id")
#DatabaseTable(tableName = "products")
public class Product {
public Product() {
}
#DatabaseField(id = true)
private int id;
// Generic stuff
#DatabaseField
#SerializedName("product_desc")
private String desc;
#DatabaseField
#SerializedName("in_stock")
private boolean inStock;
#DatabaseField(unique = true)
#SerializedName("product_name")
private String name;
// Issue occurs here
#DatabaseField(foreign = true, foreignAutoRefresh = true)
private Venue venue;
}
and we have a class "Venue`:
#DatabaseTable(tableName = "venues")
public class Venue {
public Venue() {
}
#SerializedName("venueid")
#DatabaseField(id = true)
private int id;
// Other Generic Junk
#DatabaseField
private String desc;
#DatabaseField
private String email;
#DatabaseField
private String fax;
#DatabaseField
#SerializedName("venue_name")
private String name;
#DatabaseField
private String phoneNumber;
}
I use Gson to deserialize Json from a pre-written API and ORMLite populate the database with this data. An issue occurs as the API returns the venue id of the venue each product is associated with (e.g venueid = 1) and not a Venue object.
However, the database is already populated with these venues so venueid = 1 refers to a real venue within the current database.
The trouble is getting ORMLite to understand this and update the Venue object within Product to be that of id = 1!
Can anyone think of a solution?
EDIT:
To better understand my issue, here is some sample Json:
[
{
"productid": 1,
"venueid": 4,
"product_name": "Jack Daniels",
"in_stock": true,
"orders_accepted": true,
}
...
]
As you can see, I get an int for venueid and NOT a Venue object. Is there an easy way to convert it to it's corresponding venue without the large overhead of multiple queries
This is an high level answer based on assumption that:
your API returns you also a Json string for every Venue object or
you can easily get a Json string for every Venue object you have in your DB (sorry I do not know ORMLite at all, even if from its name, I can image what it does ;) ).
So, somehow build a little dictionary like a Map<Integer, String> where the int key is your venue id and value is Json string for that Venue object.
Then, when you get Product Json from API, do a simple string replacement using a regexp.
For example, you should transform:
{
"productid": 1,
"venueid": 4,
"product_name": "Jack Daniels",
"in_stock": true,
"orders_accepted": true,
}
into:
{
"productid":1,
"venue":{
"venueid":4,
"desc":"aDesc",
"email":"aEmail",
"fax":"aFax",
"venue_name":"aname",
"phoneNumber":"aPhonenumber"
},
"product_name":"Jack Daniels",
"in_stock":true,
"orders_accepted":true
}
After string replacement you should be able to parse the updated Json string into your data structure without involving database anymore. Think this like something a "Json lookup".
If you can read Venue from DB, your query cost will be only a "select all" to fill the map, after that string replacement will occur in memory only.

Categories