Use a custom deserializer only on certain fields? - gson

With gson, is it possible to use a custom deserializer / serializer only on certain fields? The user guide shows how to register an adapter for an entire type, not for specific fields. The reason why I want this is because I parse a custom date format and store it in a long member field (as a Unix timestamp), so I don't want to register a type adapter for all Long fields.
Is there a way to do this?

I also store Date values as long in my objects for easy defensive copies. I also desired a way to override only the date fields when serializing my object and not having to write out all the fields in the process. This is the solution I came up with. Not sure it is the optimal way to handle this, but it seems to perform just fine.
The DateUtil class is a custom class used here to get a Date parsed as a String.
public final class Person {
private final String firstName;
private final String lastName;
private final long birthDate;
private Person(String firstName, String lastName, Date birthDate) {
this.firstName = firstName;
this.lastName = lastName;
this.birthDate = birthDate.getTime();
}
public static Person getInstance(String firstName, String lastName, Date birthDate) {
return new Person(firstName, lastName, birthDate);
}
public String toJson() {
return new GsonBuilder().registerTypeAdapter(Person.class, new PersonSerializer()).create().toJson(this);
}
public static class PersonSerializer implements JsonSerializer<Person> {
#Override
public JsonElement serialize(Person person, Type type, JsonSerializationContext context) {
JsonElement personJson = new Gson().toJsonTree(person);
personJson.getAsJsonObject().add("birthDate", new JsonPrimitive(DateUtil.getFormattedDate(new Date(policy.birthDate), DateFormat.USA_DATE)));
return personJson;
}
}
}
When the class is serialized, the birthDate field is returned as a formatted String instead of the long value.

Don't store it as a long, use a custom type with a proper adapter. Inside your type, represent your data any way you want -- a long, why not.

Related

How to generate cache CustomKey for Redis in Spring Boot

I have spring boot application which is integrated with Redis cache. Have to implement caching for one of the method call. That method argument is an object with multiple params which is external Request object. This object params will vary for each request also based on that param and its values output of the method is varies. I need to create a cache key using that Request object field/param values. How to achieve it.
We can use SimpleKeyGenerator only when method params are static?
UserService.java
#Cacheable(value = "usercache", keyGenerator="customKeyGenerator")
public UserResponse getUserResp(User user){
//Some backend calls
return user
}
User.java
public class User {
private String firstname;
private String lastname;
private Integer age;
private Date dob;
private Address address;
// Another 10 params
}
In this method implementation User object is dynamic. I have to create a cache key based on User object fields which is having valid non null values. How to achieve it.
I have implemented as like below.
User.java
public class User implements Serializable {
private String firstname;
private String lastname;
private Integer age;
private Date dob;
private Address address;
// Another 10 params
#Override
public int hashCode() {
final int prime = 31;
//Add necessary fields
}
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
//Add necessary fields
}
}
public class UserKeyGenerator implements KeyGenerator{
private static final String UNDERSCORE_DELIMITER = "_";
#Override
public Object generate(Object target, Method method, Object... params) {
String cacheKey = null;
if(params.length > 0) {
StringJoiner paramStrJoiner = new StringJoiner(UNDERSCORE_DELIMITER);
User userReq = (User) params[0];
paramStrJoiner.add(target.getClass().getSimpleName());
paramStrJoiner.add(method.getName());
paramStrJoiner.add(String.valueOf(userReq.hashCode()));
cacheKey = paramStrJoiner.toString();
}
return cacheKey;
}

Spring Boot request body semi-required fields

In our application user can write a message based on user id or screen name.
class Message {
public final Long userId;
public final String screenName;
public final String text;
#JsonCreator
public Message(#JsonProperty(value = "user_id", required = ???) Long userId,
#JsonProperty(value = "screen_name", required = ???) String screenName,
#JsonProperty(value = "text", required = true) String text) {
this.userId = userId;
this.screenName = screenName;
this.text = text;
}
}
Fields userId and screenName can't be optional at same time, one should be provided.
How in Spring Boot to mark that they are semi-required?
This seems like more of a validation concern rather than deserialization.
Create a Validator then put #Valid within the #RequestMapping on the controller.
See more here:
Spring REST Validation Example
From jenkov tutorials:
#JsonValue
The Jackson annotation #JsonValue tells Jackson that Jackson should
not attempt to serialize the object itself, but rather call a method
on the object which serializes the object to a JSON string. Note that
Jackson will escape any quotation marks inside the String returned by
the custom serialization, so you cannot return e.g. a full JSON
object. For that you should use #JsonRawValue instead (see previous
section).
The #JsonValue annotation is added to the method that Jackson is to
call to serialize the object into a JSON string. Here is an example
showing how to use the #JsonValue annotation:
public class PersonValue {
public long personId = 0;
public String name = null;
#JsonValue
public String toJson(){
return this.personId + "," + this.name;
}
}
The output you would get from asking Jackson to serialize a
PersonValue object is this:
"0,null"
So you can use #JsonValue and put your code either to ignore or not from some fields when you try to convert into JSON
#JsonValue
public String toJson(){
//ignore fields or include them here
}
Just throw an IllegalArgumentException. The best case would be to deserialize, then run through a validator though so you separate the concerns of serialization, and domain validation.
class Message {
public final Long userId;
public final String screenName;
public final String text;
#JsonCreator
public Message(#JsonProperty(value = "user_id", required = false) Long userId,
#JsonProperty(value = "screen_name", required = false) String screenName,
#JsonProperty(value = "text", required = true) String text) {
if(userId == null && screenName == null) {
throw new IllegalArgumentException("userId or screenName must be provided.");
}
this.userId = userId;
this.screenName = screenName;
this.text = text;
}
}

Hide certain fields from a spring request body in swagger

The example api below lets users create an object. The user should be able to specify the name field of the Thing object, while the id field should be automatically generated.
Given the setup below, swagger will display both the name and the id field for the request as something the user can enter, as well as displaying both fields as optional. In reality, for the request, name should be required while id should never be entered by the user.
(Note: the object that is returned when creation is successful, should include the generated id field)
I suppose one option would be to create a copy of the Thing object that is identical, except for the lack of the id field ("ThingCreationRequestObject").
Is this an acceptable solution? It seems there should be a way that doesn't require the maintenance of two objects that essentially represent the same thing.
#RequestMapping(value = "/thing", method = RequestMethod.POST)
Thing createThing(#RequestBody Thing thing) {
// add thing to database, including a generated id
// return thing object, now including the generated id
}
public class Thing {
private String id;
private String name;
}
// Swagger
Thing {
id (string, optional),
name (string, optional)
}
You can annotate the id field with #ApiModelProperty(hidden = true) , in this case it will be hidden from swagger, but still if the user entered the id then it will parsed and assigned to the id field so you need also annotate the setter method only of the id with #JsonIgnore
public class Thing {
#ApiModelProperty(hidden = true)
private String id;
private String name;
public String getId() {
return id;
}
#JsonIgnore
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}

MongoRepository custom Id

I have the following MongoDB Repository
public interface TeamRepository extends MongoRepository<Team, TeamId> {
....
}
And the following classes:
public abstract class DbId implements Serializable {
#Id
private final String id;
public DbId(final String id) { this.id = id;}
public String getId() { return id;}
}
public class TeamId extends DbId {
public TeamId(final String id) {
super(id)
}
}
As you can see, I have like a custom id for the repository (I have MongoRepository instead of something like MongoRepository). But, when I am trying to save a Team object, I get an error saying that MongoDB does not know how to generate DBId. Any clue?
MongoDb (or any database) would not know how to generate a string ID without you informing it what the value of the string is.
The default #Id is a string representation of ObjectId, which can be auto-generated by MongoDB. If you are changing the type of string ObjectId to a class, then at least the class needs to define:
** Conversion to string (serialisable), for example:
#Override
public String toString() {
return String.format(
"TeamID[uniqueString=%s]",
myUniqueString);
}
** How to generate the Id.
You can define a method in your TeamRepository i.e. save() to specify how your string can be generated. Alternatively you can check out
https://www.mkyong.com/mongodb/spring-data-mongodb-auto-sequence-id-example/
Where the example specify getNextSequenceId() to generate NumberLong custom id. Hopefully that guides you to your answer.

Parsing large character fields using GSON

I was trying to parse a JSON object using GSON parser and write into database using JDBC. However some of the fields are quite large to store in string datatype after I parse them using GSON parser, and also creates a problem as I would have to increase my column size in database for storing larger strings.
My class structure looks similar to this:
public class Results {
public String _refObjectUUID;
public String _refObjectName;
public String _type;
public String _CreatedAt;
public String Description;
public String Notes;
public String Theme;
public String StartDate;
public String EndDate;
public String State;
public String PlannedVelocity;
public String FormattedID;
public Owner Owner;
public Project Project;
public String ScheduleState;
public String Ready;
public Release Release;
}
While parsing by using the above class, some of the fields like Description for example are quite large and variable in length. So I wanted to how GSON would support such large string? Is there something similar to CLOB datatype that GSON supports?

Resources