BeanUtils: populate String Date formatted map field to Date property - java

I've this Map<String, Object>:
Map<String, Object> map = new HashMap<String, Object>();
map.put("date", "02/11/2018#11:29:03.463+0000");
My bean is:
public class MyBean {
private Date date;
// setters & getters
}
I'm trying to populate my map to bean:
MyBean bean = new MyBean();
BeanUtilsBean.getInstance().populate(bean, map);
I'm getting this error message:
ConversionException: DateConverter does not support default String to 'Date' conversion.
IMPORTANT: I can't change string format.
How could I solve it?

You can create an instance of DateTimeConverter and pass in your custom patterns. Here is an example:
DateTimeConverter dateConverter = new DateConverter(null);
dateConverter.setPatterns(new String[] {"dd/MM/yyyy#HH:mm:ss.SSSZ"});
ConvertUtils.register(dateConverter, Date.class);

You have to register converter:
class MyDateConverter implements Converter {
private final DateFormat format = new SimpleDateFormat("dd/MM/yyyy#HH:mm:ss.SSSZ");
public Object convert(Class type, Object value) {
if(value == null) {
return null;
} else { // parse your date format with date formatter
try {
return format.parse((String) value);
} catch (ParseException e) {
throw new RuntimeException(e);
}
}
}
}
And use it like:
ConvertUtilsBean convertUtilsBean = new ConvertUtilsBean();
convertUtilsBean.register(new MyDateConverter(), Date.class);
BeanUtilsBean beanUtilsBean =
new BeanUtilsBean(convertUtilsBean, new PropertyUtilsBean());
MyBean bean = new MyBean();
beanUtilsBean.populate(bean, map);

Related

Specify an adapter for multiple xsd types

I'm trying to map different formats onto 3 xsd types, xs:dateTime, xs:date, and xs:time. I'm doing a bit of codegen in my project, and do not have a bindings file, though I do have a package-info.
#javax.xml.bind.annotation.adapters.XmlJavaTypeAdapters({
#javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter(value = com.codegen.pojo.lib.adapters.XmlDateTimeFormatter.class, type = com.codegen.pojo.lib.types.IXmlDateTime.class)
})
#javax.xml.bind.annotation.XmlSchemaTypes({
#javax.xml.bind.annotation.XmlSchemaType(name = "dateTime", type = com.codegen.pojo.lib.types.XmlDateTime.class),
#javax.xml.bind.annotation.XmlSchemaType(name = "date", type = com.codegen.pojo.lib.types.XmlDate.class),
#javax.xml.bind.annotation.XmlSchemaType(name = "time", type = com.codegen.pojo.lib.types.XmlTime.class)
})
#com.codegen.pojo.lib.annotations.XMLDateTimeFormat(format = "yyyy-MM-dd'T'HH:mm:ss.SSSXXXX")
#com.codegen.pojo.lib.annotations.XMLDateFormat(format = "yyyy-MM-dd'T'")
#com.codegen.pojo.lib.annotations.XMLTimeFormat(format = "'T'HH:mm:ss.SSSXXXX")
package my_pkg;
All 3 of the Xml(Date/Time) classes mentioned in the XmlSchemaType annotations implement the IXmlDateTime interface.
The adapter:
public class XmlDateTimeFormatter extends XmlAdapter<String, IXmlDateTime> {
private final DateTimeFormatter dateTimeFormat;
private final DateTimeFormatter dateFormat;
private final DateTimeFormatter timeFormat;
public XmlDateTimeFormatter(String dateTimeFormatPattern, String dateFormatPattern, String timeFormatPattern) {
dateTimeFormat = dateTimeFormatPattern == null ? null : DateTimeFormatter.ofPattern(dateTimeFormatPattern);
dateFormat = dateFormatPattern == null ? null : DateTimeFormatter.ofPattern(dateFormatPattern);
timeFormat = timeFormatPattern == null ? null : DateTimeFormatter.ofPattern(timeFormatPattern);
}
#Override
public String marshal(IXmlDateTime dateTime) throws Exception {
if (dateTime instanceof XmlDateTime) {
return dateTimeFormat.format(dateTime.getCalendar().toGregorianCalendar().toZonedDateTime());
} else if (dateTime instanceof XmlDate) {
return dateFormat.format(dateTime.getCalendar().toGregorianCalendar().toZonedDateTime());
} else if (dateTime instanceof XmlTime) {
return timeFormat.format(dateTime.getCalendar().toGregorianCalendar().toZonedDateTime());
}
return null;
}
#Override
public IXmlDateTime unmarshal(String dateTime) throws Exception {
XMLGregorianCalendar calendar = DatatypeFactory.newInstance().newXMLGregorianCalendar(dateTime);
QName xmlFormat = calendar.getXMLSchemaType();
if (xmlFormat.equals(DatatypeConstants.DATETIME)) {
return new XmlDateTime(calendar);
} else if (xmlFormat.equals(DatatypeConstants.DATE)) {
return new XmlDate(calendar);
} else if (xmlFormat.equals(DatatypeConstants.TIME)) {
return new XmlTime(calendar);
}
return null;
}
The adapter is added to a Marshaller/Unmarshaller created from a JAXBContext. Unfortunately, neither the marshal or unmarshal get called. Any thoughts on how I could fix this?

Custom converter in ModelMapper not being fired

I am trying to conver a String to a Date but the converter doesn't seem to be fired and I'm getting a MappingException. This is my code:
#Bean
public ModelMapper modelMapper() {
ModelMapper modelMapper = new ModelMapper();
Converter<String, Date> dateConverter = context -> {
Date date;
try {
date = new SimpleDateFormat("dd/MM/yyyy").parse(context.getSource());
} catch (ParseException e) {
date = null;
}
System.out.println("working");
return date;
};
modelMapper.addConverter(dateConverter);
return modelMapper;
}
And then I just use it like this but I continue getting the same error:
Date date = this.modelMapper.map("20/12/2019", Date.class);
1) Converter org.modelmapper.internal.converter.DateConverter#27c7dc27 failed to convert java.lang.String to java.util.Date.
It's not even printing the working word.
What am I missing?
Thanks.
Okay, for some reason my dumb IDE (IntelliJ) adviced me to change the code above for how it is in the question. That's what's making it not work, having it as a lambda, it seems. With this code it works perfectly:
Converter<String, Date> dateConverter = new Converter<String, Date>()
{
public Date convert(MappingContext<String, Date> context)
{
Date date = null;
try {
date = new SimpleDateFormat("dd/MM/yyyy").parse(context.getSource());
} catch (ParseException e) {
System.out.println(e.toString());
}
return date;
}
};

Jackson ObjectMapper from MongoDB BSON

I got a JSON that I serialize to a MongoDB BasicDBObject and insert it into the DB:
String serialized = "";
try {
serialized = OBJECT_MAPPER.writeValueAsString(customEx.getOut().getBody());
} catch (JsonProcessingException e) {
e.printStackTrace();
}
collection.update(upsertQuery, BasicDBObject.parse(serialized), true, false);
On reading the DBObject from the DB I want to convert it to a POJO using the ObjectMappers' readValue() with a given class:
public static <T> T fromDB(DBObject o, Class<T> clazz) {
try {
return OBJECT_MAPPER.readValue(o.toString(), clazz);
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
The class I want to convert it to is generated from an xsd sheme and also contains timestamps/long values as following:
#XmlRootElement(name = "ItemType")
public class ItemType {
#XmlSchemaType(name = "dateTime")
protected XMLGregorianCalendar date;
[...]
However this worked fine for older Java MongoDB versions. Now the long values are serialized as BSON looking like this:
"date": {
"$numberLong": "1551172199214"
}
When I try to deserialize this using jacksons ObjectMapper I get
Cannot deserialize instance of javax.xml.datatype.XMLGregorianCalendar out of START_OBJECT token
The reason for this is clear to me because the long value is in an own BSON - Style object.
So far I already tried using BsonDocument like this:
public static <T> T fromDB(DBObject o, Class<T> clazz) {
try {
BsonDocument parse = BsonDocument.parse(o.toString());
return OBJECT_MAPPER.readValue(parse.toJson(), clazz);
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
But that still does not convert the BSON parts to JSON.
Is there any way to deserialize the BSON to a given Class using Jacksons ObjectMapper?
Or just convert it to a DBObject without using BSON parts?
If calendar field is wrapped we need to unwrap it. We can extend already implemented CoreXMLDeserializers.GregorianCalendarDeserializer:
class XmlGregorianCalendarDeserializer extends CoreXMLDeserializers.GregorianCalendarDeserializer {
#Override
public XMLGregorianCalendar deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException {
jp.nextToken(); // Skip FIELD_NAME
jp.nextToken(); // Skip VALUE_STRING
XMLGregorianCalendar calendar = super.deserialize(jp, ctxt);
jp.nextToken(); // Skip END_OBJECT
return calendar;
}
}
If XMLGregorianCalendar is always wrapped we can register this deserialiser using SimpleModule. See below example:
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ext.CoreXMLDeserializers;
import com.fasterxml.jackson.databind.module.SimpleModule;
import java.io.IOException;
import javax.xml.datatype.XMLGregorianCalendar;
public class Test {
public static void main(String[] args) throws Exception {
SimpleModule wrappedCalendarModule = new SimpleModule();
wrappedCalendarModule.addDeserializer(XMLGregorianCalendar.class, new XmlGregorianCalendarDeserializer());
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(wrappedCalendarModule);
String json = "{\n"
+ " \"date\":{\n"
+ " \"$numberLong\":\"1551172199214\"\n"
+ " },\n"
+ " \"name\":\"Rick\"\n"
+ "}";
System.out.println(mapper.readValue(json, Wrapper.class));
}
}
class Wrapper {
private XMLGregorianCalendar date;
private String name;
// getters, setters, toString
}
Above code prints:
Wrapper{date=2019-02-26T09:09:59.214Z, name='Rick'}
It looks like you need to configure the ObjectMapper to deserialize the XMLGregorianCalendar differently:
public class XMLGregorianCalendarDeserializer extends JsonDeserializer<XMLGregorianCalendar> {
private ObjectMapper objectMapper = new ObjectMapper();
#Override
public XMLGregorianCalendar deserialize(
JsonParser jsonParser, DeserializationContext deserializationContext)
throws IOException, JsonProcessingException {
Map<String, String> bsonStringAsMap =
objectMapper.readValue(
jsonParser.readValueAsTree().toString(), new TypeReference<Map<String, String>>() {});
String timestampString = bsonStringAsMap.get("$numberLong");
if (timestampString != null && !timestampString.isEmpty()) {
long timestamp = Long.parseLong(timestampString);
Date date = new Date(timestamp);
GregorianCalendar gregorianCalendar = new GregorianCalendar();
gregorianCalendar.setTime(date);
return DatatypeFactory.newInstance().newXMLGregorianCalendar(gregorianCalendar);
}
return null;
}
}
Then you can annotate the field with this and deserialize as you have been:
#JsonDeserialize(using=XMLGregorianCalendarDeserializer.class)
#XmlSchemaType(name = "dateTime")
protected XMLGregorianCalendar date;

How to convert object to query string in Java?

I am trying to convert the following object to query string, so that can be used with GET request.
Class A {
String prop1;
String prop2;
Date date1;
Date date2;
ClassB objB;
}
Class B {
String prop3;
String prop4;
}
We can do that first object to Map then convert map to MultiValueMap and use URIComponentsBuilder.fromHttpUrl("httpL//example.com").queryParams(multiValueMap).build();
Is there shorter and better way of converting object to query string so that be used with GET request in Spring Project for Junit Test?
Why convert to Map then MultiValueMap, instead of just building it directly?
DateFormat dateFmt = new SimpleDateFormat("whatever date format you want");
URIComponentsBuilder.fromHttpUrl("httpL//example.com")
.queryParam("prop1", a.prop1)
.queryParam("prop2", a.prop2)
.queryParam("date1", dateFmt.format(a.date1))
.queryParam("date2", dateFmt.format(a.date2))
.queryParam("prop3", a.objB.prop3)
.queryParam("prop4", a.objB.prop4)
.build();
You could write your own method that uses java.lang.reflect. Here's an example
public static String getRequestString(String urlString, Class clazz, Object o){
String queryString = "?";
try {
for (Field f : clazz.getDeclaredFields()) {
f.setAccessible(true);
queryString += queryString.concat(f.getName() + "=" + String.valueOf(f.get(o)) + "&");
}
}catch (Exception e){
e.printStackTrace();
}
return urlString + queryString.substring(0,queryString.length()-1);
}
OpenFeign has the annotation #QueryMap to generate query params dinamicaly based on a object attributes:
public interface Api {
#RequestLine("GET /find")
V find(#QueryMap CustomPojo customPojo);
}
See more at:
https://github.com/OpenFeign/feign#dynamic-query-parameters
This is how i would do it,
Create Map, populate and then iterate over map items and append to builder this seems to be working for me. It does not cover support for nested objects. Should be simple with recursion.
public String getRequestString(Class clazz, Object o) {
StringBuilder queryStringBuilder = new StringBuilder();
final Map<String, String> queryParams = new LinkedHashMap<>();
try {
for (Field f : clazz.getDeclaredFields()) {
f.setAccessible(true);
queryParams.put(f.getName(), String.valueOf(f.get(o)));
}
for (Map.Entry<String, String> entry : queryParams.entrySet()) {
queryStringBuilder.append(testStringUtils.toSnakeCase(entry.getKey()));
queryStringBuilder.append("=");
queryStringBuilder.append(entry.getValue());
queryStringBuilder.append("&");
}
logger.info("Map: " + queryParams);
} catch (Exception e) {
e.printStackTrace();
}
final String queryString = queryStringBuilder.toString();
logger.info("Query string : " + queryString.substring(0, queryString.length() - 1));
return "?" + queryString.substring(0, queryString.length() - 1);

Overriding XmlAdapter for 3rd party library class

I am generating xml using jaxbMarshaller for a third party library class. Since library XmlAdapter which converts Calendar object to string is not using TimeZone field, so marshaller is generating wrong xml for every Calendar field of pojo class.
3rd Party library XmlAdapter is using below class for Calendar to string conversion:
public class DateConversion {
public static String printDate(Calendar value) {
if(value != null) {
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
return format.format(value.getTime());
}
return null;
}
}
So I want to override the behavior of XmlAdapter for Calendar field and tried below example but seems it is not working:
my custom XmlAdapter is using below class for conversion:
public class DateConversion {
public static String printDate(Calendar value, TimeZone timeZone) {
if(value != null) {
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
format.setTimeZone(timeZone);
return format.format(value.getTime());
}
return null;
}
}
and then I have done registry like:
public #Nullable
String toPdxmlString(final #NotNull Deals input) {
try {
final Marshaller marshaller = jaxbContext.createMarshaller();
final DateFormatterAdapter dateFormatterAdapter = new DateFormatterAdapter(PdxmlDateTimeUtil.FXONLINE_DEFAULT_DEAL_TIMEZONE);
marshaller.setAdapter(dateFormatterAdapter);
StringWriter writer = new StringWriter();
marshaller.marshal(input, writer);
return writer.toString();
} catch (JAXBException exception) {
LOGGER.error("Unable to marshall the given input Deals: {}, into String using JAXB Context: {}, ... ", input, jaxbContext, exception);
}
return null;
}
Can anyone help me to know if this is doable or not, if yes where I am going wrong?
So I found my solution. I extended XmlAdapter of 3rd party library and pluged in TimeZone field in DateConversion like:
public class DateFormatterAdapter extends Adapter2 {
private final TimeZone timeZone;
public DateFormatterAdapter(final TimeZone timeZone) {
this.timeZone = timeZone;
}
#Override
public Calendar unmarshal(String value) {
return javax.xml.bind.DatatypeConverter.parseDate(value);
}
#Override
public String marshal(Calendar calendar) {
return DateConversion.printDate(calendar, timeZone);
}
}
At last registered the extended XmlAdapter as:
public #Nullable
String toPdxmlString(final #NotNull Deals input) {
try {
final Marshaller marshaller = jaxbContext.createMarshaller();
final DateFormatterAdapter dateFormatterAdapter = new DateFormatterAdapter(PdxmlDateTimeUtil.FXONLINE_DEFAULT_DEAL_TIMEZONE);
marshaller.setAdapter(Adapter2.class, dateFormatterAdapter);
StringWriter writer = new StringWriter();
marshaller.marshal(input, writer);
return writer.toString();
} catch (JAXBException exception) {
LOGGER.error("Unable to marshall the given input Deals: {}, into String using JAXB Context: {}, ... ", input, jaxbContext, exception);
}
return null;
}

Categories