JAXB LocalDateTime unmarshalling results in null - java

I have read every topic about the matter on Stack, checked if all the variable names are same as in XML, checked if adapter is correct, but still can't find the solution. I need to unmarshall xml file to an object. I am using JAXB and it works, except for the LocalDateTime field. The code:
#Getter
#Setter
#ToString
#NoArgsConstructor
#EqualsAndHashCode
#XmlRootElement(name = "Alert")
#XmlAccessorType(XmlAccessType.FIELD)
public class ParsedAlert {
#XmlElement(name = "abcDate", required = true)
#XmlJavaTypeAdapter(value = LocalDateTimeAdapter.class)
private LocalDateTime abcDate;
//other fields that work fine
static ParsedAlert unmarshallEmail(String path)
throws JAXBException {
JAXBContext context = JAXBContext.newInstance(ParsedAlert.class);
try {
return (ParsedAlert)
context.createUnmarshaller().unmarshal(new FileReader(path));
} catch (JAXBException | FileNotFoundException ex) {
throw new AlertException(
String.format("Message: [%s]", ex.getMessage()));
}
}
Here is adapter class:
public class LocalDateTimeAdapter extends XmlAdapter<String, LocalDateTime> {
private DateTimeFormatter dateFormat = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
#Override
public LocalDateTime unmarshal(String v) throws Exception {
return LocalDateTime.parse(v,dateFormat);
}
#Override
public String marshal(LocalDateTime v) {
return v.format(dateFormat);
}
}
Part of the XML file:
<?xml version="1.0" encoding="utf-8"?>
<Alert>
<abcDate>2015-08-29T14:00:18</abcDate>
</Alert
Result is:
ParsedAlert(abcDate=null)
But it should be:
ParsedAlert(abcDate=2015-08-29T14:00:18)

Related

How to handle multiple date formats with springboot and jackson

In the json of the post request I have several different date formats. I'm having troubled deserializing all at the same time. I've created a configuration class that will handle one or the other just fine. How do I add additional deserializers to handle the other formats?
I don't have access to the POJO to add any annotations there.
Here's an error I get for one of the dates I'm unable to deserialize
JSON parse error: Cannot deserialize value of type java.time.LocalDateTime from String "09/03/2020 10:59:48": Failed to deserialize java.time.LocalDateTime:
#Configuration
public class JacksonConfig {
#Bean
#Primary
public ObjectMapper objectMapper() {
JavaTimeModule module = new JavaTimeModule();
LocalDateTimeDeserializer localDateTimeDeserializer = new LocalDateTimeDeserializer(
DateTimeFormatter.ofPattern("MM/dd/yyyy HH:mm:ss"));
module.addDeserializer(LocalDateTime.class, localDateTimeDeserializer);
return Jackson2ObjectMapperBuilder.json().modules(module)
.featuresToDisable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS).build();
}
}
I was able to resolve my issue by overriding the LocalDateTimeDeserializer's deserialize method. I modified the solution from Configure Jackson to parse multiple date formats
public class MultiDateDeserializer extends LocalDateTimeDeserializer {
public MultiDateDeserializer() {
this(null);
}
public MultiDateDeserializer(DateTimeFormatter formatter) {
super(formatter);
}
private static final long serialVersionUID = 1L;
private static final String[] DATE_FORMATS = new String[] { "yyyy-MM-dd'T'HH:mm:ss", "MM/dd/yyyy HH:mm:ss" };
#Override
public LocalDateTime deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
JsonNode node = p.getCodec().readTree(p);
final String date = node.textValue();
for (String DATE_FORMAT : DATE_FORMATS) {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(DATE_FORMAT, Locale.ROOT);
try {
return LocalDateTime.parse(date, formatter);
} catch (DateTimeParseException e) {
}
}
throw new ParseException(0,
"Unparseable date: \"" + date + "\". Supported formats: " + Arrays.toString(DATE_FORMATS));
}
}
And then in my JacksonConfig I have...
#Configuration
public class JacksonConfig {
#Bean
#Primary
public ObjectMapper objectMapper() {
JavaTimeModule module = new JavaTimeModule();
MultiDateDeserializer multiDateDeserializer = new MultiDateDeserializer();
module.addDeserializer(LocalDateTime.class, multiDateDeserializer);
return Jackson2ObjectMapperBuilder.json().modules(module)
.featuresToDisable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS).build();
}
}

Cannot deserialize value of type `java.time.Instant` - jackson

Having class like this
#AllArgsConstructor
#NoArgsConstructor
#Getter
#Setter
public final class ActiveRecoveryProcess {
private UUID recoveryId;
private Instant startedAt;
}
I'm getting com.fasterxml.jackson.databind.exc.InvalidFormatException with message Cannot deserialize value of typejava.time.Instantfrom String "2020-02-22T16:37:23": Failed to deserialize java.time.Instant: (java.time.format.DateTimeParseException) Text '2020-02-22T16:37:23' could not be parsed at index 19
JSON input
{"startedAt": "2020-02-22T16:37:23", "recoveryId": "6f6ee3e5-51c7-496a-b845-1c647a64021e"}
Jackson configuration
#Autowired
void configureObjectMapper(final ObjectMapper mapper) {
mapper.registerModule(new ParameterNamesModule())
.registerModule(new Jdk8Module())
.registerModule(new JavaTimeModule());
mapper.findAndRegisterModules();
}
EDIT
JSON is generated from postgres
jsonb_build_object(
'recoveryId', r.recovery_id,
'startedAt', r.started_at
)
where r.started_at is TIMESTAMP.
The String you're trying to parse, 2020-02-22T16:37:23, doesn't end in Z. Instant expects this as it stands for UTC. It simply cannot be parsed. Concat the String with Z to resolve the issue.
String customInstant = "2020-02-22T16:37:23";
System.out.println("Instant of: " + Instant.parse(customInstant.concat("Z")));
One way to do this is to create a Converter.
public final class NoUTCInstant implements Converter<LocalDateTime, Instant> {
#Override
public Instant convert(LocalDateTime value) {
return value.toInstant(ZoneOffset.UTC);
}
#Override
public JavaType getInputType(TypeFactory typeFactory) {
return typeFactory.constructType(LocalDateTime.class);
}
#Override
public JavaType getOutputType(TypeFactory typeFactory) {
return typeFactory.constructType(Instant.class);
}
}
Then annotate the field.
#JsonDeserialize(converter = NoUTCInstant.class)
private Instant startedAt;

#JsonDeserializer in Mixin

Consider the following example:
I have a json string = {"timestamp":1504111920} which needs to be converted to CodeTimestamp class. The timestamp present in above json string is in epoch second.
CodeTimestamp class:
#Getter
#Setter
#NoArgsConstructor
class CodeTimestamp {
private Date timestamp;
}
By directly using fasterxml jackson mapper, I'll not be able to get the correct date since it assumes timestamp to be in epoch millisecond. So, I would need to write a custom deserializer.
However, I cannot edit/modify CodeTimestamp class. Is there any way to write JsonDeserializer in mixin?
I'm facing issues while deserializing. Following is the code:
public abstract class StreamRecordMixIn {
#JsonDeserialize(using = UnixTimestampDeserializer.class)
private Date approximateCreationDateTime;
}
public class UnixTimestampDeserializer extends JsonDeserializer<Date> {
#Override
public Date deserialize(JsonParser parser, DeserializationContext context)
throws IOException, JsonProcessingException {
String unixTimestamp = parser.getText().trim();
return new Date(TimeUnit.SECONDS.toMillis(Long.valueOf(unixTimestamp)));
}
}
Code to initialize and use object mapper:
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true);
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
objectMapper.addMixIn(CodeTimestamp.class, StreamRecordMixIn.class);
CodeTimestamp codeTimeStamp = objectMapper.readValue(payload, CodeTimestamp.class);
Error:
Caused by: java.lang.IllegalArgumentException: Class com.test.TestConverter$UnixTimestampDeserializer has no default (no arg) constructor
at com.fasterxml.jackson.databind.util.ClassUtil.createInstance(ClassUtil.java:378)
at com.fasterxml.jackson.databind.deser.DefaultDeserializationContext.deserializerInstance(DefaultDeserializationContext.java:218)
at com.fasterxml.jackson.databind.deser.BasicDeserializerFactory.findDeserializerFromAnnotation(BasicDeserializerFactory.java:1735)
at com.fasterxml.jackson.databind.deser.BeanDeserializerFactory.constructSettableProperty(BeanDeserializerFactory.java:730)
at com.fasterxml.jackson.databind.deser.BeanDeserializerFactory.addBeanProps(BeanDeserializerFactory.java:507)
at com.fasterxml.jackson.databind.deser.BeanDeserializerFactory.buildBeanDeserializer(BeanDeserializerFactory.java:229)
at com.fasterxml.jackson.databind.deser.BeanDeserializerFactory.createBeanDeserializer(BeanDeserializerFactory.java:142)
at com.fasterxml.jackson.databind.deser.DeserializerCache._createDeserializer2(DeserializerCache.java:403)
at com.fasterxml.jackson.databind.deser.DeserializerCache._createDeserializer(DeserializerCache.java:352)
at com.fasterxml.jackson.databind.deser.DeserializerCache._createAndCache2(DeserializerCache.java:264)
... 23 more
The mistake here is custom deserializer not declared as static. So if I used it as mentioned below, it works.
public static class UnixTimestampDeserializer extends JsonDeserializer<Date> {
#Override
public Date deserialize(JsonParser parser, DeserializationContext context)
throws IOException, JsonProcessingException {
String unixTimestamp = parser.getText().trim();
return new Date(TimeUnit.SECONDS.toMillis(Long.valueOf(unixTimestamp)));
}
}

Display XML correctly in Firefox with Jackson

I'm converting a JSON file into an ArrayList and then to XML by using Jackson. It is displayed in Firefox but just as a normal String. By using the inspect element tool I get the whole formatted xml though. Which function can I use to display it correctly on the browser?
My method:
private void init() throws JsonParseException, JsonMappingException, IOException {
ObjectMapper mapper = new ObjectMapper();
InputStream is = MyClass[].class.getResourceAsStream("/config/myList.json");
myList= Arrays.asList(mapper.readValue(is, MyClass[].class));
XmlMapper xmlMapper = new XmlMapper();
for(MyClass test : myList){
String asXml += xmlMapper.writeValueAsString(test);
}
LOGGER.info("asXml: {}.", asXml);
}
Desired output in browser:
<myclass xmlns="">
<myclass>XyClass</ci>
<myname>XyName</ci>
...
</myclass>
Actual output:
XyClassXyName...
Quite simplified the class looks like this:
#JacksonXmlRootElement(localName ="MyClass")
#JsonIgnoreProperties(ignoreUnknown = true)
#JsonAutoDetect(fieldVisibility = Visibility.ANY, getterVisibility = Visibility.NONE, setterVisibility = Visibility.NONE)
#JsonInclude(JsonInclude.Include.NON_NULL)
public class MyClass {
#XmlElement(required = true)
private String class;
#XmlElement(required = true)
private String name;
//....
//standard constructor
public MyClass() { }
public CI(String class, String name){
this.class = class;
this.name = name;
}
public String getClass() {
return class;
}
public String getName() {
return name;
}
public void setClass(String class) {
this.class = class;
}
public void setName(String name) {
this.name = name;
}
}
Another weird thing is that I have the exact annotations in another class, trying the same thing with that and there the browser does not display anything... Thanks for any help.
Well my mistake derived from two things basically. Most important is the definiton of the XML Root Element (not only as annotation in your "MyClass"). Define a global String to create a XML Root element, otherwise your document won't be well-formed and the mistake "junk after document element" will be shown.
private String asXml ="<?xml version=\"1.0\" encoding=\"utf-8\"?><MyList>";
I've adapted the method to:
private void init() throws JsonParseException, JsonMappingException, IOException {
ObjectMapper mapper = new ObjectMapper();
InputStream is = MyClass[].class.getResourceAsStream("/config/myList.json");
myList= Arrays.asList(mapper.readValue(is, MyClass[].class));
XmlMapper xmlMapper = new XmlMapper();
for(MyClass test : myList){
String asXml += xmlMapper.writer().with(SerializationFeature.WRAP_ROOT_VALUE).withRootName("MyClass").writeValueAsString(test);
}
LOGGER.info("asXml: {}.", asXml);
asXml += "</MyList>";
}
And don't forget to add the correct MediaType in your RestController:
#RequestMapping(value="/display", method=RequestMethod.GET, produces=MediaType.APPLICATION_XML_VALUE)
public #ResponseBody String getList(Model model) {
return service.getAsXmlString();
}

JAXB null instead empty string during marshaling

How I can print 'null' as field value, when marshalling the string?
Example: error and error_code are Strings, and i want to use 'null' as a value indicating that there is no value/errors happened on the server side.
{
"error_code": null,
"error": null
}
Today, I have to use EMPTY values, so that "error_code" or "error" these fields generally fall into json, and if they were not explicitly initialized as this.errorCode = StringUtils.EMPTY;
So today, I have next json:
{
"error_code": "",
"error": ""
}
This is how that looks in a code:
#XmlRootElement()
#XmlAccessorType(XmlAccessType.FIELD)
public class Response
{
#SuppressWarnings("unused")
private static final Logger log = LoggerFactory.getLogger(Response.class);
public static final String ERROR_FIELD_NAME = "error";
public static final String ERROR_CODE_FIELD_NAME = "error_code";
// #XmlJavaTypeAdapter(CafsResponse.EmptyStringAdapter.class)
#XmlElement(name = Response.ERROR_CODE_FIELD_NAME)
private String errorCode;
// #XmlJavaTypeAdapter(CafsResponse.EmptyStringAdapter.class)
#XmlElement(name = Response.ERROR_FIELD_NAME)
private String errorMessage;
// Empty Constructor
public Response()
{
this.errorCode = StringUtils.EMPTY; // explicit initialization, otherwise error_code will not appear as part of json, how to fix this this ?
this.errorMessage = StringUtils.EMPTY;
}
etc...
// Empty Constructor
public Response()
{
this.errorCode = null; // this variant dosn't work either, and error_code again didn't get to json
this.errorMessage = null;
}
See, #XmlJavaTypeAdapter, i thought that this potentially could help me - but no :)
Instead of null value, i'm getting "null" as string.
if (StringUtils.isEmpty(str))
{
return null;
}
return str;
{
"error_code": "null", // this is not whta i wanted to get.
"error": "null"
}
Any help on this? - ask me if something is not clear.
full list:
/**
* Empty string Adapter specifying how we want to represent empty strings
* (if string is empty - treat it as null during marhsaling)
*
*/
#SuppressWarnings("unused")
private static class EmptyStringAdapter extends XmlAdapter<String, String>
{
#Override
public String unmarshal(String str) throws Exception
{
return str;
}
#Override
public String marshal(String str) throws Exception
{
if (StringUtils.isEmpty(str))
{
return null;
}
return str;
}
}
Note: I'm the EclipseLink JAXB (MOXy) lead and a member of the JAXB (JSR-222) expert group.
You could use MOXy as your JSON provider to support this use case. Below is an example:
Response
MOXy will marshal properties marked with #XmlElement(nillable=true) to the representation you are looking for
(see: http://blog.bdoughan.com/2012/04/binding-to-json-xml-handling-null.html).
package forum11319741;
import javax.xml.bind.annotation.*;
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
public class Response {
public static final String ERROR_FIELD_NAME = "error";
public static final String ERROR_CODE_FIELD_NAME = "error_code";
#XmlElement(name = Response.ERROR_CODE_FIELD_NAME, nillable = true)
private String errorCode;
#XmlElement(name = Response.ERROR_FIELD_NAME, nillable = true)
private String errorMessage;
}
jaxb.properties
To use MOXy as your JAXB provider you need to include a file called jaxb.properties in the same package as your domain model with the following entry (see: http://blog.bdoughan.com/2011/05/specifying-eclipselink-moxy-as-your.html):
javax.xml.bind.context.factory=org.eclipse.persistence.jaxb.JAXBContextFactory
Demo
package forum11319741;
import javax.xml.bind.*;
public class Demo {
public static void main(String[] args) throws Exception {
JAXBContext jc = JAXBContext.newInstance(Response.class);
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.setProperty("eclipselink.media-type", "application/json");
marshaller.setProperty("eclipselink.json.include-root", false);
Response response = new Response();
marshaller.marshal(response, System.out);
}
}
Output
{
"error_code" : null,
"error" : null
}
MOXy and JAX-RS
You can use the MOXyJsonProvider class to enable MOXy as your JSON provider in your JAX-RS application (see: http://blog.bdoughan.com/2012/05/moxy-as-your-jax-rs-json-provider.html).
package org.example;
import java.util.*;
import javax.ws.rs.core.Application;
import org.eclipse.persistence.jaxb.rs.MOXyJsonProvider;
public class CustomerApplication extends Application {
#Override
public Set<Class<?>> getClasses() {
HashSet<Class<?>> set = new HashSet<Class<?>>(2);
set.add(MOXyJsonProvider.class);
set.add(CustomerService.class);
return set;
}
}
For More Information
http://blog.bdoughan.com/2012/04/binding-to-json-xml-handling-null.html

Categories

Resources