How to update a field in a immutable object - java

Supposing the following class:
#Immutable
public final MyMessageClass {
private String message;
private Date dateLastChange;
private String identifier;
public MyClass(final String message){
this.message = message;
dataLastChange = new Date();
}
public Date lastChange() {
return new Date(dateLastChange.getTime());
}
public String messageValue(){
return message;
}
}
Once we have built an object of this class,
MyMessageClass myMessageObject = new MyMessageClass("Once upon a time ...");
and once we have finish doing some operations with it,
logger.info("The message is{}",myMessageObject.messageValue());
logger.info("The last change was done on {}",myMessageObject.lastChange());
it is expected to get an identifier from somewhere (a remote service, for instance) and attach it to the message. But, if I do something like this:
myMessageObject.setIdentifier(identifier);
I supposedly breaking the immutability capability of the object. If so, how is the best way to update the identifier field avoiding to do a new constructor call (so creating a new object)?

So the problem is just because you want to log some stuff first? Can't you do that after you've constructed the object?
Alternatively, you can use the builder pattern, with something like this. Note the final instance fields - instances of this class will be immutable even without the annotation.
#Immutable
public final MyMessageClass {
private final String message;
private final Date dateLastChange;
private final String identifier;
public MyClass(final MyMessageClass.Builder builder){
this.message = builder.message;
this.dataLastChange = builder.dataLastChange;
this.identifier = builder.identifier;
}
public Date lastChange() {
return new Date(dateLastChange.getTime());
}
public String messageValue(){
return message;
}
public String identifier(){
return identifier;
}
public static final class Builder {
private String message;
private final Date dateLastChange = new Date();
private String identifier;
public Builder message(final String message) {
this.message = message;
return this;
}
public String message() {
return message;
}
public Builder identifier(final String identifier) {
this.identifier = identifier;
return this;
}
public String identifier() {
return identifier;
}
public Date lastChange() {
return new Date(dateLastChange.getTime());
}
public MyMessageClass build() {
return new MyMessageClass(this);
}
}
}
You can then incrementally build the content of your object.
MyMessageClass.Builder builder = new MyMessageClass.Builder().message("Once upon a time ...");
logger.info("The message is{}", builder.message());
logger.info("The last change was done on {}",builder.lastChange());
String identifier = // get the identifier from somewhere
MyMessageClass message = builder.identifier(identifier).build();

Related

Spring Boot enum JSON serializer

Below is the object that I want to convert to JSON;
public class TestDto{
private ResponseType responseType;
private Long id;
private String name;
}
The ResponseType below is an enum;
public enum ResponseType{
TEST1("test message 1"), TEST2("test message 2"), TEST3("test message 3");
private String message;
}
Below is the JSON which I want to create:
{"code":"TEST1", "message":"test message 1", "id":1, "name":"name"}
and code in the JSON response points the name of the enum and the message in the JSON response points the message field of the enum.
Is there any way to do it?
Easiest way to do this is to add derived getters/setters to TestDto, and suppress JSON serialization of the responseType field.
class TestDto {
private ResponseType responseType;
private Long id;
private String name;
#JsonIgnore // Suppress JSON serialization
public ResponseType getResponseType() {
return this.responseType;
}
public void setResponseType(ResponseType responseType) {
this.responseType = responseType;
}
public String getCode() { // Derived getter for "code" property
return this.responseType.name();
}
public void setCode(String code) { // Derived setter for "code" property
this.responseType = (code == null ? null : ResponseType.valueOf(code));
}
public String getMessage() { // Derived getter for "message" property
return this.responseType.getMessage();
}
#Deprecated // Shouldn't be called by Java code, since it's a dummy stub method
#SuppressWarnings("unused")
public void setMessage(String message) { // Derived setter for "message" property
// Ignore value
}
public Long getId() {
return this.id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
}
Two-way test
TestDto testDto = new TestDto();
testDto.setResponseType(ResponseType.TEST1);
testDto.setId(1L);
testDto.setName("name");
ObjectMapper mapper = new ObjectMapper();
String json = mapper.writeValueAsString(testDto);
System.out.println(json);
TestDto testDto2 = mapper.readValue(json, TestDto.class);
System.out.println(testDto2.getResponseType());
System.out.println(testDto2.getId());
System.out.println(testDto2.getName());
Output
{"id":1,"name":"name","message":"test message 1","code":"TEST1"}
TEST1
1
name
I have updated my previous answer as it wasn't correct. You can use #JsonFormat(shape = JsonFormat.Shape.OBJECT) to indicate that the enum should be serialized like an object (based on the getters).
#JsonFormat(shape = JsonFormat.Shape.OBJECT)
public enum ResponseType{
TEST1("test message 1"), TEST2("test message 2"), TEST3("test message 3");
private String message;
ResponseType(String message) {
this.message = message;
}
public String getMessage() {
return message;
}
public String getCode() {
return this.toString();
}
}
After that, you must also use #JsonUnwrapped on the Enum field to avoid having it's fields serialized as an object.
public static class TestDto {
#JsonUnwrapped private ResponseType responseType;
private Long id;
private String name;
}
Running the following code
TestDto testDto = new TestDto(ResponseType.TEST1, 1234356L, "First Response");
result = mapper.writeValueAsString(testDto);
System.out.println(result);
I get the result {"message":"test message 1","code":"TEST1","id":1234356,"name":"First Response"}

Spring ConstraintValidator customizing

Before asking questions, I apologize for not being good at English.
I'm implementing a custom ConstraintValidator for cross-field validation as shown below.
Validation Target Class
public class ValidationTarget {
#Valid
private Inner inner;
#ValidDates(fromField = "from", toField = "to",
message = "{from} must not be later than {to}")
public class Inner {
private Date from;
private Date to;
}
// ...
}
CustomConstraintValidator
// imports ...
public class CustomConstraintValidator implements ConstraintValidator<ValidDates, Object> {
private String fromFieldName;
private String toFieldName;
private String message;
#Override
public void initialize(ValidDatesvalidationSpec) {
this.fromFieldName = validationSpec.fromField();
this.toFieldName = validationSpec.toField();
this.message = validationSpec.message();
}
#Override
public boolean isValid(Object target, ConstraintValidatorContext ctx) {
Date startDateObject = getFieldValue(target, fromFieldName);
Date endDateObject = getFieldValue(target, toFieldName);
if (start.after(end)) {
addConstraintViolation(toFieldName, message, ctx);
return false;
}
return true;
}
private void addConstraintViolation(String propertyName, String message, ConstraintValidatorContext ctx) {
ctx.buildConstraintViolationWithTemplate(message)
.addPropertyNode(propertyName)
.addConstraintViolation()
.disableDefaultConstraintViolation();
}
private Date getFieldValue(Object instance, String fieldName) {
Field field = ReflectionUtils.findField(instance.getClass(), fieldName);
ReflectionUtils.makeAccessible(field);
return (Date) ReflectionUtils.getField(field, instance);
}
}
In the above code, Inner class object is returned when calling getInvalidValue() of ConstraintViolation.
I want to change this value only to a specific field value rather than to the entire Inner class.

Alternative to multiple constructors

I have this constructor...
public ShiftLog(String companyName, boolean workedForAgent, String agentName,
Date shiftStart, Date shiftEnd,
boolean breakTaken, Date breakStart,
Date breakEnd, boolean isTransportJob,
String transportCompanyName, String vehicleRegistration) {
this.userUid = FirebaseAuth.getInstance().getCurrentUser().getUid();
this.companyName = companyName;
this.workedForAgent = workedForAgent;
this.agentName = agentName;
this.shiftStart = shiftStart;
this.shiftEnd = shiftEnd;
this.breakTaken = breakTaken;
this.breakStart = breakStart;
this.breakEnd = breakEnd;
this.isTransportJob = isTransportJob;
this.transportCompanyName = transportCompanyName;
this.vehicleRegistration = vehicleRegistration;
}
Now I want to add in a shift log (instantiate a shift log object for a user). The problem is that there are multiple combinations a shift log can have. For example, workedForAgent is false, there should be no need to pass in agentName. How can I do that without creating multiple constructors because there can be multiple possible combinations? For example, user can work for agent but not take a break, meaning break start time and end time shouldn't be needed to pass in. But that would require so many constructors for all possible combinations. Any alternative?
Also I am using the room database to append all this info. So if workedForAgent is false for example, automatically set agentName to null. How could that be done as well.
Take a look at Builder patterns.
Builder pattern is a creational design pattern it means its solves problem related to object creation.
It typically solve problem in object oriented programming i.e determining what constructor to use.
Adding to #Kodiak
You can replace your constructor with builder in few clicks
as mentioned here https://www.jetbrains.com/help/idea/replace-constructor-with-builder.html
Plus, the best part is,it will refactor all the occurrence of the constructor with builder automatically
Short Answer: Use Getters/Setters
Long Answer: The alternative method here is that you can instantiate the variables that you sure they must exist in the constructor and then the other conditional variables can be defined with setter methods and you can easily fetch with getters.
public class ShiftLog {
private Object userUid;
private String companyName;
private boolean workedForAgent;
private String agentName;
private Date shiftStart;
private Date shiftEnd;
private boolean breakTaken;
private Date breakStart;
private Date breakEnd;
private boolean isTransportJob;
private String transportCompanyName;
private String vehicleRegistration;
public ShiftLog(String companyName, Date shiftStart, Date shiftEnd) {
this.userUid = FirebaseAuth.getInstance().getCurrentUser().getUid();
this.companyName = companyName;
this.shiftStart = shiftStart;
this.shiftEnd = shiftEnd;
}
public boolean isWorkedForAgent() {
return workedForAgent;
}
public void setWorkedForAgent(boolean workedForAgent) {
this.workedForAgent = workedForAgent;
}
public String getAgentName() {
return agentName;
}
public void setAgentName(String agentName) {
this.agentName = agentName;
}
public boolean isBreakTaken() {
return breakTaken;
}
public void setBreakTaken(boolean breakTaken) {
this.breakTaken = breakTaken;
}
public Date getBreakStart() {
return breakStart;
}
public void setBreakStart(Date breakStart) {
this.breakStart = breakStart;
}
public Date getBreakEnd() {
return breakEnd;
}
public void setBreakEnd(Date breakEnd) {
this.breakEnd = breakEnd;
}
public boolean isTransportJob() {
return isTransportJob;
}
public void setTransportJob(boolean isTransportJob) {
this.isTransportJob = isTransportJob;
}
public String getTransportCompanyName() {
return transportCompanyName;
}
public void setTransportCompanyName(String transportCompanyName) {
this.transportCompanyName = transportCompanyName;
}
public String getVehicleRegistration() {
return vehicleRegistration;
}
public void setVehicleRegistration(String vehicleRegistration) {
this.vehicleRegistration = vehicleRegistration;
}
}

JSON.decodeValue Decode Exception

I want to make this java code works:
RequestManager rm = Json.decodeValue(request.getBodyAsString(), RequestManager.class);
But i have this error:
io.vertx.core.json.DecodeException: Failed to decode:No suitable constructor found for type [simple type, class RequestManager]: can not instantiate from JSON object (missing default constructor or creator, or perhaps need to add/enable type information?)
at [Source: {"messageId":"fsdfsdf"}; line: 1, column: 2]
And here the code of my class :
public class RequestManager {
private String messageId;
private String messageContent;
public RequestManager(String messageId, String messageContent) {
this.messageId = messageId;
this.messageContent = messageContent;
}
public String getMessageId() {
return messageId;
}
public String getMessageContent() {
return messageContent;
}
}
I really don't know why it's not working and there is only few topics about it, but they were irrelevant.
Someone can help ?
EDIT--
I know have the RequestManager class like this:
public class RequestManager {
private String messageId;
private String messageContent;
public RequestManager(String messageId, String messageContent) {
this.messageId = messageId;
this.messageContent = messageContent + "check";
}
public RequestManager() {
}
public String getMessageId() {
return messageId;
}
public String getMessageContent() {
return messageContent;
}
public void setMessageId(String messageId) {
this.messageId = messageId;
}
public void setMessageContent(String messageContent) {
this.messageContent = messageContent;
}
}
But know when i try to print the fields of my RequestManager object created with the JSON.decodeValue it's return me null. I've already done that in the past and had the same error. I think it's because the empty constructor is used instead.
I still don't really understand....
EDIT--2
I have tried to change my class again, here it is:
#JsonIgnoreProperties(ignoreUnknown=true)
public class RequestManager {
#JsonProperty("messageId") private String messageId;
#JsonProperty("messageContent") private String messageContent;
#JsonCreator
public RequestManager(#JsonProperty("messageId") String messageId, #JsonProperty("messageContent") String messageContent) {
this.messageId = messageId;
this.messageContent = messageContent;
System.out.println("This constructor is used.");
}
public RequestManager() {
}
public String getMessageId() {
return messageId;
}
public String getMessageContent() {
return messageContent;
}
public void setMessageId(String messageId) {
this.messageId = messageId;
}
public void setMessageContent(String messageContent) {
this.messageContent = messageContent;
}
}
And this is in my main :
final RequestManager rm = Json.decodeValue("{\"messageId\":\"themessage\"}", RequestManager.class);
System.out.println(rm.getMessageContent());
"{\"messageId\":\"themessage\"}" = the JSON format, i'm sure of it because decodeValue would return a Decode Exception if it wasn't.
Now the field is "nullcheck" when i print it. So it means that the constructor is well used but the fields are nulls. Where am i doint it wrong ?
You could try to have an empty constructor.
It's because you have your own constructor, and JSON doesn't know what values should be passed into it.
There is documentation on their GitHub page explaining how to set up a data object that you expect to be given to you as JSON and converted to Java.
https://github.com/vert-x3/vertx-codegen#data-objects
As per the example that you linked to: http://vertx.io/blog/some-rest-with-vert-x/, notice how they explicitly provide a constructor that takes no arguments, and public setter methods
Whisky()
setName(String name)
setOrigin(String origin)
The alternative is to provide annotations: https://github.com/FasterXML/jackson-annotations. You can choose how to do it, using annotation if you want, or using a bean class (getters and setters). Annotation has the advantage that you can say things like "ignore this value when you convert to JSON", etc. You can be more explicit with annotation. I would recommend picking one and staying with it. Consistency becomes important as your projects grow.

Json to Object using Gson

I have a class DocumentBO which has the following attributes -
public class DocumentBO implements IStorageBO {
private String aId;
private String studyId;
private Map<AlgorithmsEnum, JobIOStatus> status;
private String text;
private Collection<Sentence> sentences;
public String getaId() {
return aId;
}
public void setaId(String aId) {
this.aId = aId;
}
public String getStudyId() {
return studyId;
}
public void setStudyId(String studyId) {
this.studyId = studyId;
}
public Map<AlgorithmsEnum, JobIOStatus> getStatus() {
return status;
}
public void setStatus(Map<AlgorithmsEnum, JobIOStatus> status) {
this.status = status;
}
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
public Collection<Sentence> getSentences() {
return sentences;
}
public void setSentences(Collection<Sentence> sentences) {
this.sentences = sentences;
}
}
The AlgorithmsEnum is as follows -
public enum AlgorithmsEnum {
SENTIMENT("sentiment"),
INTENTION("intention"),
TOPIC("topic"),
NER("ner"),
UIMA("uima");
private final String value;
private AlgorithmsEnum(String value) {
this.value = value;
}
public String value() {
return value;
}
#Override
public String toString() {
return value;
}
public static AlgorithmsEnum fromValue(String value) {
if (value != null) {
for (AlgorithmsEnum aEnum : AlgorithmsEnum.values()) {
if (aEnum.value().equals(value)) {
return aEnum;
}
}
}
return null;
}
}
The JobIOStatus is also similar.
I am successfully able to create a JSON string of Collection using GSON using the following TypeToken
Type type = new TypeToken<Collection<DocumentBO>>() {}.getType();
But, when I try to recreate the Collection object using the JSON string returned by Gson and the same TypeToken, the key of the status hashmap is always returned as NULL whereas the value is successfully created. What do you think can be the issue?
The problem is that you have overridden toString() in your enum.
If you look at the JSON being produced, the keys to your Map<AlgorithmsEnum, JobIOStatus> are the lowercase names you're creating. That won't work. Gson has no idea how to recreate the enum from those when you attempt to deserialize the JSON.
If you remove your toString() method it will work just fine.
Alternatively you can use the .enableComplexMapKeySerialization() method in GsonBuilder when serializing which will ignore your toString() method and produce JSON using the default representations of your enum values which is what is required.
There are "well" known :) issues of Gson to serialize Map when the key is derived from object and its not a "native" data type.
Please use this
GsonBuilder builder = new GsonBuilder();
Gson gson = builder.enableComplexMapKeySerialization().create();
Collection<DocumentBO> obj = gson.fromJson(str, type);

Categories