I have the following DTO and VO:
VO
public class ProjectVO {
private Date fechaInicio;
private Date fechaFin;
}
DTO
public class ProjectDTO {
private String fechaInicio;
private String fechaFin;
}
And the following converter to convert the strings to dates:
Converter<String, Date> dateConverter = new Converter<String, Date>()
{
public Date convert(MappingContext<String, Date> context)
{
Date date;
try {
date = new SimpleDateFormat("dd/MM/yyyy").parse(context.getSource());
} catch (ParseException e) {
throw new DateFormatException();
}
return date;
}
};
modelMapper.addConverter(dateConverter);
If I convert a single String to a date using modelmapper it'll work perfectly with this converter.
But now I need to convert my ProjectDTO object to a ProjectVO one and I am getting an error saying that it cannot convert a String to a Date. I suspect this is because the Date is inside the Project object. Am I right? How can I solve this?
Thanks.
Okay it works like a charm and automatically detects it if I use this:
modelMapper.createTypeMap(String.class, Date.class);
and then add the custom converter I created :)
Related
In my java dto i am converting json date ("dispOn": "11/28/2020",) to java.util.Date.since i am not sending any timezone jackson is taking UTC as default time zone but when desterilizing it is taking the offset of CST(My local time)as a result, date in my code(11/27/2020) is one day behind
import java.util.Date;
#JsonFormat(pattern = "MM/dd/yyyy")
private Date dispOn;
How can i get the same date sending in json into my code
As Andreas mentioned in his comment, it is sufficient to change from Date to LocalDate.
For additional info, I used LocalDateTime to show you how the format should be configured. I'm also using Lombok's annotations on class-level for logging and getter/setter generation.
Keep in mind that LocalDate and LocalDateTime are only available since Java 8.
Below a complete example of a DTO in which I parse a JSON
#Slf4j
#Getter
#Setter
#JsonIgnoreProperties(ignoreUnknown = true) // optional annotation
public class MyMessage implements Deserializer<MyMessage> {
#JsonProperty("EVENT_TIMESTAMP")
#JsonFormat(shape = JsonFormat.Shape.STRING,
pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS")
private LocalDateTime eventTimestamp;
#JsonProperty("MESSAGE")
private String message;
#Override
public void configure(Map<String, ?> configs, boolean isKey) {
Deserializer.super.configure(configs, isKey);
}
#Override
public MyMessage deserialize(String s, byte[] bytes) {
ObjectMapper mapper = new ObjectMapper();
MyMessage event = null;
try {
event = mapper
.registerModule(new JavaTimeModule())
.readValue(bytes, MyMessage.class);
} catch (Exception e) {
log.error("Something went wrong during the deserialization of the MyMessage: {}", e.getMessage());
}
return event;
}
#Override
public MyMessage deserialize(String message, Headers headers, byte[] data) {
return Deserializer.super.deserialize(message, headers, data);
}
#Override
public void close() {
Deserializer.super.close();
}
And the JSON I'm sending:
{
"EVENT_TIMESTAMP": "2022-03-15T14:14:05.945",
"MESSAGE": "Test 123"
}
I've got a json file which has got a Date field. I'm transforming this to a required Date format(dd/MM/yyyy) using JsonDeserializer and registering the class as a TypeAdapter in GsonBuilder. Now, I want to transform this field to different date formats(say ISO8601) for different use cases. Instead of creating new JsonDeserializer's for different date formats, Is there anyway I can pass date format as one of the parameters to JsonSerializer class to make it extensible.
Deserializer class:
public class DateDeserializer implements JsonDeserializer<Date> {
private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("dd/MM/yyyy");
#Override
public Date deserialize(JsonElement jsonElement, Type type, JsonDeserializationContext jsonDeserializationContext) {
String dateString = jsonElement.getAsString();
if (!StringUtils.isEmpty(dateString)) {
try {
return DATE_FORMAT.parse(dateString);
} catch (ParseException e) {
}
}
}
GsonBuilder:
public static final Gson GSON = new GsonBuilder()
.registerTypeAdapter(Date.class, new DateDeserializer())
.serializeNulls()
.create();
I have the following class that includes a list of Date which I want to mashall. I have created the JaxBAdapter for the Date class, but it seems it is not called.
I think the problem is the fact that I'm using a list and not just a Date variable.
Could you give me some hint on how should I code the adapter or annotate the class so each element of the list is mashalled using the adapter?
Will it work for JSON serialization as well? I'm planning to use this classes on my REST webservice.
Root.java
#XmlRootElement(name = "root")
#XmlAccessorType(XmlAccessType.FIELD)
public class Root {
#XmlElementWrapper(name="timePeriods")
#XmlElement(name = "timePeriod")
#JsonProperty(value = "timePeriod")
#XmlJavaTypeAdapter(value = JaxBDateThreadSafeAdapter.class, type = Date.class)
private List<Date> timePeriod;
public Root() {
this(new ArrayList<String>(), new ArrayList<Date>(2));
}
public Root(List<Date> timePeriod) {
this.timePeriod = new ArrayList<Date>(timePeriod);
}
}
JaxBAdapter
public class JaxBDateThreadSafeAdapter extends XmlAdapter<String, Date> {
/**
* Thread safe {#link DateFormat}.
*/
private static final ThreadLocal<DateFormat> DATE_FORMAT_TL =
new ThreadLocal<DateFormat>() {
#Override
protected DateFormat initialValue() {
// return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
// ISO 8601 format
return new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ");
}
};
#Override
public String marshal(Date v) throws Exception {
return DATE_FORMAT_TL.get().format(v);
}
#Override
public Date unmarshal(String v) throws Exception {
return DATE_FORMAT_TL.get().parse(v);
}
}
The #XmlJavaTypeAdapter works as such with java.util.List as well. But the problem in the adapter class is the date format that is used. JAXB (atleast 2.x onwards) is not strict and dont report such errors and quietly suppresses it.
return new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ");
change to
return new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
I have mapped a custom deserializer to convert Strings on dd/MM/yyyy pattern to LocalDate so I can call my services with a more readable signature..
This is my dto class that is used as a Jersey #BeanParam to transport data between layers:
public class ProdutoFilterDto implements FilterDto {
private static final long serialVersionUID = -4998167328470565406L;
#QueryParam("dataInicial")
#JsonDeserialize(using = CustomLocalDateDeserializer.class)
private LocalDate dataInicial;
#QueryParam("dataInicial")
#JsonDeserialize(using = CustomLocalDateDeserializer.class)
private LocalDate dataFinal;
public LocalDate getDataInicial() {
return dataInicial;
}
public void setDataInicial(LocalDate dataInicial) {
this.dataInicial = dataInicial;
}
public LocalDate getDataFinal() {
return dataFinal;
}
public void setDataFinal(LocalDate dataFinal) {
this.dataFinal = dataFinal;
}
}
and this is my custom deserializer:
public class CustomLocalDateDeserializer extends JsonDeserializer<LocalDate> {
#Override
public LocalDate deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/yyyy");
final String data = p.getValueAsString();
return (LocalDate) formatter.parse(data);
}
}
Its being used on this jersey service:
#Path("produto")
#Produces(MediaType.APPLICATION_JSON)
#Consumes(MediaType.APPLICATION_JSON)
public class ProdutoService {
...
#GET
#Path("query")
#Override
public Response query(
#QueryParam("offset") #DefaultValue(value = "0") Integer offSet,
#QueryParam("limit") #DefaultValue(value = "10") Integer limit,
#BeanParam ProdutoFilterDto filter) { ... }
...
}
I am calling like this:
${host goes here}/produto/query?dataInicial=11/09/1992
The problem is that the deserializer method is never called and the bean param variable remains null..
MessageBodyReaders aren't used for #QueryParam. You seem to be expecting the Jackson MessageBodyReader to handle this deserialization, but it doesn't work like that.
Instead you will want to use a ParamConverter, which will need to be registered through a ParamConverterProvider. For example:
#Provider
public class LocalDateParamConverterProvider implements ParamConverterProvider {
final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/yyyy");
#Override
public <T> ParamConverter<T> getConverter(
Class<T> rawType, Type genericType, Annotation[] antns) {
if (LocalDate.class == rawType) {
return new ParamConverter<T>() {
#Override
public T fromString(String string) {
try {
LocalDate localDate = LocalDate.parse(string, formatter);
return rawType.cast(localDate);
} catch (Exception ex) {
throw new BadRequestException(ex);
}
}
#Override
public String toString(T t) {
LocalDate localDate = (LocalDate) t;
return formatter.format(localDate);
}
};
}
return null;
}
}
Now LocalDate will work with #QueryParam and other #XxxParams also.
Some things to note:
If your goal is to parse both your #XxxParams and your JSON body into a bean this will not work. I'm not sure how that would work, but I'm sure it would involve a lot of hacking, and I wouldn't recommend it.
Your cast to (LocalDate) won't work. It's an illegal cast to java.time.format.Parsed. See correct way in code example.
Related to the above point. I was pulling out my hair for a good hour trying to figure out why I was getting a 404, using your parse code. With a 404, the last place I thought to look was in the ParamConverter. But it seems any uncaught exceptions that are thrown in the ParamConverter, will cause a 404. Doesn't make much sense right? The head pounding led me to this, which led me to this, which seems to be a poor specification
"if the field or property is annotated with
#MatrixParam, #QueryParam or #PathParam then an implementation MUST generate an instance of
NotFoundException (404 status) that wraps the thrown exception and no entity
"
Moral of the story: make sure to catch any possible exceptions in the ParamConverter!
See Also:
Good article on ParamConverters
I have a model object with an XMLGregorianCalendar field. How can I bind it to an input field?
For string fields I'm using:
#springFormInput("model.object.stringfield" "")
but can't work out the corresponding code for an XMLGregorianCalendar
You may be better off converting the XMLGregorianCalendar to something easier to handle like Calendar or Date before handing it off to the presentation layer.
Here's a solution. It uses jodatime but could probably be changed not to:
For the view (velocity in this case):
#springFormInput("model.object.xmlgregoriancalendar.field" "")
For the controller:
#InitBinder
public void binder(WebDataBinder binder) {
binder.registerCustomEditor(XMLGregorianCalendar.class, new PropertyEditorSupport() {
public void setAsText(String value) {
setValue(createXMLGregorianCalendar(value));
}
public String getAsText() {
return new SimpleDateFormat("dd/MM/yyyy").format(((XMLGregorianCalendar)getValue()).toGregorianCalendar().getTime());
}
});
}
private XMLGregorianCalendar createXMLGregorianCalendar(String date) {
LocalDateTime result = DateTimeFormat.forPattern("dd/MM/yyyy").parseDateTime(date).toLocalDateTime();
return xmlDF().newXMLGregorianCalendar(result.toDateTime().toGregorianCalendar());
}
private static DatatypeFactory xmlDF() {
try {
return DatatypeFactory.newInstance();
} catch (DatatypeConfigurationException ex) {
throw new RuntimeException(ex);
}
}