Java - JAXB problem marshalling Local Date [duplicate] - java

I'm building a series of linked classes whose instances I want to be able to marshall to XML so I can save them to a file and read them in again later.
At present I'm using the following code as a test case:
import javax.xml.bind.annotation.*;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import java.time.LocalDate;
public class LocalDateExample
{
#XmlRootElement
private static class WrapperTest {
public LocalDate startDate;
}
public static void main(String[] args) throws JAXBException
{
WrapperTest wt = new WrapperTest();
LocalDate ld = LocalDate.of(2016, 3, 1);
wt.startDate = ld;
marshall(wt);
}
public static void marshall(Object jaxbObject) throws JAXBException
{
JAXBContext context = JAXBContext.newInstance(jaxbObject.getClass());
Marshaller marshaller = context.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
marshaller.marshal(jaxbObject, System.out);
}
}
The XML output is:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<wrapperTest>
<startDate/>
</wrapperTest>
Is there a reason why the startDate element is empty? I would like it to contain the string representation of the date (i.e. toString()). Do I need to write some code of my own in order to do this?
The output of java -version is:
openjdk version "1.8.0_66-internal"
OpenJDK Runtime Environment (build 1.8.0_66-internal-b17)
OpenJDK 64-Bit Server VM (build 25.66-b17, mixed mode)

You will have to create an XmlAdapter like this:
public class LocalDateAdapter extends XmlAdapter<String, LocalDate> {
public LocalDate unmarshal(String v) throws Exception {
return LocalDate.parse(v);
}
public String marshal(LocalDate v) throws Exception {
return v.toString();
}
}
And annotate your field using
#XmlJavaTypeAdapter(value = LocalDateAdapter.class)
See also javax.xml.bind.annotation.adapters.XmlJavaTypeAdapters if you want to define your adapters on a package level.

http://blog.bdoughan.com/2011/05/jaxb-and-joda-time-dates-and-times.html describes the hole setup.
Joda-Time provides an alternative to the Date and Calendar classes currently provided in Java SE. Since they are provided in a separate library JAXB does not provide a default mapping for these classes.
To register the adapter for all files in a package. you can add package-info.java in the package you want to register it.
#XmlJavaTypeAdapters({
#XmlJavaTypeAdapter(type=LocalDate.class,
value=LocalDateAdapter.class),
})
package PACKAGE_NAME;
import java.time.LocalDate;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapters;
The adapter looks like:
import javax.xml.bind.annotation.adapters.XmlAdapter;
import java.time.LocalDate;
public class LocalDateAdapter extends XmlAdapter<String, LocalDate>{
public LocalDate unmarshal(String v) throws Exception {
return LocalDate.parse(v);
}
public String marshal(LocalDate v) throws Exception {
return v.toString();
}
}

As Nikolay Antipov mentioned in this commment there is already a well-tested threeten library that provides 14 type adapters (as of 2021-04-26) where one of them is the:
LocalDateXmlAdapter
and can be used e.g. like this on some Java field (generally not recommended by me though):
#XmlJavaTypeAdapter(value = LocalDateXmlAdapter.class) myLocalDt;
Instead I would recommend the package-info.java approach to do it implicitly in each package on the package level so it is applied automagically to e.g. all fields of the LocalDate type in all package classes:
create a file named src/java/my/xmlconv/classes/package-info.java
its content:
#XmlJavaTypeAdapters({
#XmlJavaTypeAdapter(value=LocalDateTimeXmlAdapter.class, type=LocalDateTime.class)
})
package my.xmlconv.classes;
import java.time.LocalDateTime;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapters;
import io.github.threetenjaxb.core.LocalDateTimeXmlAdapter;

Related

Why is gson looking for java.time.ZoneRegion when serializing java.time.ZoneId?

I'm trying to understand what Gson is doing here.
Here's my simple test case.
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.TypeAdapter;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
import org.junit.Test;
import java.io.IOException;
import java.time.ZoneId;
public class TestSuite
{
public class TestItem
{
private ZoneId TimeZone = ZoneId.of("America/New_York");
}
class ZoneIdAdapter extends TypeAdapter<ZoneId>
{
#Override
public void write(final JsonWriter jsonWriter, final ZoneId timeZone) throws IOException
{
jsonWriter.value(timeZone.getId());
}
#Override
public ZoneId read(final JsonReader jsonReader) throws IOException
{
return ZoneId.of(jsonReader.nextString());
}
}
#Test
public void canSerializeTestItem()
{
Gson gson = new GsonBuilder()
.registerTypeAdapter(ZoneId.class, new ZoneIdAdapter())
.create();
gson.toJson(new TestItem());
}
}
For me using Java 17 and Gson 2.10 that fails with the following:
com.google.gson.JsonIOException: Failed making field 'java.time.ZoneRegion#id' accessible; either increase its visibility or write a custom TypeAdapter for its declaring type.
I'm failing to understand why gson is concerning itself with java.time.ZoneRegion#id when I thought* I already told it how to serialize a ZoneId (I'm guessing ZoneRegion is some internal to to java.time.ZoneId? I can't seem to find it.).
And what's more confusing to me, is that if I change to the following:
public class TestItem
{
#Expose
private ZoneId timeZone = ZoneId.of("America/New_York");
}
// ... omitted other stuff
Gson gson = new GsonBuilder()
.excludeFieldsWithoutExposeAnnotation()
.registerTypeAdapter(ZoneId.class, new ZoneIdAdapter())
.create();
gson.toJson(new TestItem());
It then works as I would expect it to (doesn't error).
Is there a better way to 'teach' gson how to serialize the third party types I'm concerned with?
The actual concrete type that ZoneId.of() returns is a ZoneRegion, and registerTypeAdapter only registers an adapter for a single type.
As ZoneRegion is not a public class you can't (easily) register an adapter for it -- and of course you want a configuration which works even if you have an instance of TestItem where timeZone has a different concrete type.
You can use registerTypeHierarchyAdapter instead. That registers an adapter for a class and all of its subclasses.
I don't know why #Expose changes the behaviour.

Unmarshalling LocalDate/LocalDateTime with MOXy

How can I get MOXy to unmarshal JSON into LocalDate and LocalDateTime?
I've got an #GET method which produces a sample instance with three fields of types LocalDate, LocalDateTime and Date, respectively.
Hitting that endpoint, I get:
{
"localDate": "2017-07-11",
"localDateTime": "2017-07-11T10:11:10.817",
"date": "2017-07-11T10:11:10.817+02:00"
}
I then POST the above data to my #POST method, which simply returns the data again:
{
"date": "2017-07-11T10:11:10.817+02:00"
}
As you can see, both localDate and localDateTime are lost in the process, because MOXy does not initialize those two fields.
What gives? MOXy seems to support serialization of these types, but not deserialization?
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.Date;
#Path("/test/date")
public class DateTest {
public static class Data {
public LocalDate localDate;
public LocalDateTime localDateTime;
public Date date;
}
#GET
#Path("roundtrip")
public Response roundtrip() {
Data sample = getSample();
return roundtrip(sample);
}
#POST
#Path("roundtrip")
#Consumes(MediaType.APPLICATION_JSON)
public Response roundtrip(Data t) {
return Response.status(Response.Status.OK).entity(t).build();
}
protected Data getSample() {
final Data data = new Data();
data.localDate = LocalDate.now();
data.localDateTime = LocalDateTime.now();
data.date = new Date();
return data;
}
}
Moxy version: jersey-media-moxy-2.25.1
According to peeskillet's suggestion I implemented the following adapter class:
public class LocalDateTimeAdapter extends XmlAdapter<String, LocalDateTime>{
private static final DateTimeFormatter DTF = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");
#Override
public String marshal(LocalDateTime localDateTime) throws Exception {
return localDateTime.format(DTF);
}
#Override
public LocalDateTime unmarshal(String string) throws Exception {
return LocalDateTime.parse(string, DTF);
}
}
In addition, I created package-info.java in the same package where my classes for MOXy and the adapter (in a subpackage) are located with the following content:
#XmlJavaTypeAdapters({
#XmlJavaTypeAdapter(type=LocalDateTime.class,
value=LocalDateTimeAdapter.class)
})
package api;
import java.time.LocalDateTime;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapters;
import api.adapter.LocalDateTimeAdapter;
Thus, marshalling and unmarshalling works without problems. And with DTF you can specify the format that shall be applied.

JsonInclude.Include.NON_DEFAULT not working with custom serializer

I want to both use a custom serializer and have the JsonInclude.Include.NON_DEFAULT designation be honored. When I don't use a custom serializer it is honored but when I do use a custom serializer it is not.
This is Jackson 2.2.2. I do not presently have the option to switch to a newer version of Jackson.
Here's a simple example that shows the problem:
import com.fasterxml.jackson.annotation.JsonGetter;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import java.io.IOException;
import java.util.EnumSet;
import java.util.Set;
import java.util.stream.Collectors;
public class JacksonSerialization
{
public static void main(String[] args) throws Exception {
ObjectMapper serializer = new ObjectMapper();
Foo foo = new Foo();
foo.setFlags(EnumSet.of(Flag.CC, Flag.BB));
System.out.println(serializer.writeValueAsString(foo));
foo = new Foo();
System.out.println(serializer.writeValueAsString(foo));
}
public static enum Flag
{
AA,
BB,
CC
}
#JsonInclude(JsonInclude.Include.NON_DEFAULT)
public static class Foo
{
private Set<Flag> flags;
public Foo() {
flags = EnumSet.of(Flag.AA);
}
#JsonGetter("f")
#JsonSerialize(using = FlagSetSerializer.class)
public Set<Flag> getFlags() {
return flags;
}
public void setFlags(Set<Flag> theFlags) {
flags = theFlags;
}
}
public static class FlagSetSerializer extends JsonSerializer<Set<Flag>>
{
#Override
public void serialize(Set<Flag> value,
JsonGenerator jsonGenerator,
SerializerProvider serializerProvider) throws IOException {
String csv = value.stream()
.map(Flag::toString)
.collect(Collectors.joining(","));
jsonGenerator.writeString(csv);
}
}
}
And here's the output:
{"f":"BB,CC"}
{"f":"AA"}
Note that f is being serialized even when it has the default value. If I comment out the #JsonSerialize annotation then I get the following output:
{"f":["BB","CC"]}
{}
Then f properly does not get serialized. But of course things are not being serialized in the format I want.
So how do I get the custom serializer to honor the class's #JsonInclude annotation?
You probably want to implement public boolean isEmpty(SerializerProvider provider, T value) as per the documentation, which says:
public boolean isEmpty(SerializerProvider provider, T value)
Method called to check whether given serializable value is considered
"empty" value (for purposes of suppressing serialization of empty
values).
Default implementation will consider only null values to be
empty.
As per https://github.com/FasterXML/jackson-databind/issues/730
Another possible source of trouble is that you talk about NON_EMPTY but you code uses NON_DEFAULT.
And rather too much digging in the debugger leads me to suggest
#JsonSerialize(using = FlagSetSerializer.class, include = JsonSerialize.Inclusion.NON_DEFAULT)
Which seems to pass your tests.
The problem seems to be in JacksonAnnotationInspector#findSerializationInclusion, which first looks for a #JsonInclude attribute on the property, and when it fails to find that, it looks for a #JsonSerialize annotation. #JsonSerialize includes a deprecated include property, which has a default value of ALWAYS.
I've not looked into it too deeply, but I suspect a deprecation/refactor managed to slice off some functionality. C'est la vie.

What package-info do I annotate with XmlJavaTypeAdapters?

I've studied Blaise Doughan's answer to a question on this subject but have a further question.
XmlJavaTypeAdapters lets you list a bunch of XmlJavaTypeAdapter annotations, each of which governs how a non-bindable type is mapped to a bindable type by JAXB.
You can use this annotation at the package level. When you do so, every XmlJavaTypeAdapter annotation needs its type() attribute fully specified.
There does not appear to be a requirement that the package that is being annotated have anything to do with the package of the non-bindable types being adapted. That is convenient and nice.
That, however, leads me to my next question: if there is no relationship between the annotated package and the package of the type being adapted, how does JAXB discover package-level XmlJavaTypeAdapters annotations? How, in other words, does it know which packages to consult for potential XmlJavaTypeAdapters annotations? May I make a random package in, say, a .jar file in my .ear file's lib directory that contains a single, ginormous package-info class that is annotated with all the adapters for all of my non-bindable types?
When the JAXB runtime loads a JAXB-annotated class, it looks for a package-info.java in the same package as that class, and checks that to look for package-level annotations. So while XmlJavaTypeAdapters doesn't have to reside in the same package as the "non-bindable" types, it does have to reside in the same package as the "bindable" types.
For example, say I have a JAXB-annotated class A, in package X, which has a property of type B in package Y. In order to bind the B property, let's say a type adapter is required. That adapter can be specified in A itself, or it can be specified in the package-info.java in package X. Package Y is pretty much arbitrary, and is of no interest to the JAXB runtime.
I hope that makes sense.
There does not appear to be a requirement that the package that is
being annotated have anything to do with the package of the
non-bindable types being adapted. That is convenient and nice.
This is correct. When #XmlJavaTypeAdapter is used at the package level it means apply this adapter to all properties of the specified type for classes that reside in this package. I'll demonstrate below with an example.
forum8735737.bar.package-info
For this package we will specify an XmlAdapter that will be applied to all fields/properties of type String within this package.
#XmlJavaTypeAdapters({
#XmlJavaTypeAdapter(value=StringAdapter.class, type=String.class)
})
package forum8735737.bar;
import javax.xml.bind.annotation.adapters.*;
forum8735737.bar.StringAdapter
Our XmlAdapter will simply convert all instances of String to upper case when marshalling:
package forum8735737.bar;
import javax.xml.bind.annotation.adapters.XmlAdapter;
public class StringAdapter extends XmlAdapter<String, String> {
#Override
public String unmarshal(String v) throws Exception {
return v;
}
#Override
public String marshal(String v) throws Exception {
if(null == v) {
return v;
}
return v.toUpperCase();
}
}
forum8735737.bar.Bar
Bar represents a POJO in this package with a property of type String:
package forum8735737.bar;
import javax.xml.bind.annotation.XmlRootElement;
#XmlRootElement
public class Bar {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
forum8735737.foo.Foo
Foo represents a domain object with a property of type String that exists in a different package. The XmlAdapter we registered for the forum8735737.bar package will not apply to this class:
package forum8735737.foo;
import javax.xml.bind.annotation.XmlRootElement;
#XmlRootElement
public class Foo {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Demo
The following code will create instances of both Foo and Bar and marshal them to XML:
package forum8735737;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.Marshaller;
import forum8735737.bar.Bar;
import forum8735737.foo.Foo;
public class Demo {
public static void main(String[] args) throws Exception {
JAXBContext jc = JAXBContext.newInstance(Foo.class, Bar.class);
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
Foo foo = new Foo();
foo.setName("Foo");
marshaller.marshal(foo, System.out);
Bar bar = new Bar();
bar.setName("Bar");
marshaller.marshal(bar, System.out);
}
}
Output
Notice how the value of the name element within bar has been converted to upper case:
<?xml version="1.0" encoding="UTF-8"?>
<foo>
<name>Foo</name>
</foo>
<?xml version="1.0" encoding="UTF-8"?>
<bar>
<name>BAR</name>
</bar>

JAXB: How to customize Xml serialization of double fields

I have a legacy class, with a lot of public double fields. All double fields are initialized with Double.MAX_VALUE to indicate that they are empty. (The legacy serialization is coded to ignore the field and not serialize if field is equals to Double.MAX_VALUE).
We are now trying to serialize this class to Xml using JAXB Marshaller. It is working fine, except that we want to prevent generating Xml for fields which equal Double.MAX_VALUE.
We aren't using a separate JAXB schema, just marking up our classes with various javax.xml.bind.annotation Annotations. If a schema is used, you can add a <javaType> element to specify a custom DataType converter. Is there any way to do this using Annotations or programmatically?
After trying approach recommended below, I still can't get XmlAdapter picked up:
#XmlJavaTypeAdapters({
#XmlJavaTypeAdapter(value=EmptyDoubleValueHandler.class, type=Double.class), #XmlJavaTypeAdapter(value=EmptyDoubleValueHandler.class, type=double.class)})
package tta.penstock.data.iserver;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapters;
My top level class is: tta.penstock.data.iserver.OrderBlotter, which contains a list of tta.penstock.data.iserver.OrderResponseWrappers which extends com.eztech.OrderResponse. All the double fields are contained in com.eztech.OrderResponse.
My unit test code does the following:
JAXBContext context = JAXBContext.newInstance(new Class[] { OrderBlotter.class, OrderResponseWrapper.class, OrderResponse.class});
Marshaller marshaller = context.createMarshaller();
StringWriter stringWriter = new StringWriter();
marshaller.marshal(blotter, stringWriter);
System.out.println("result xml=\n" + stringWriter.toString());
But the double values still don't get handled by the XmlAdapter. I know I'm missing something basic, but I'm not sure what it is.
You could use an XmlAdapter:
http://bdoughan.blogspot.com/2010/07/xmladapter-jaxbs-secret-weapon.html
The XmlAdapter
package example;
import javax.xml.bind.annotation.adapters.XmlAdapter;
public class DoubleAdapter extends XmlAdapter<Double, Double>{
#Override
public Double unmarshal(Double v) throws Exception {
return v;
}
#Override
public Double marshal(Double v) throws Exception {
if(Double.MAX_VALUE == v) {
return null;
} else {
return v;
}
}
}
The Model Object
package example;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
#XmlRootElement
public class Root {
#XmlJavaTypeAdapter(DoubleAdapter.class)
public Double maxDouble = Double.MAX_VALUE;
#XmlJavaTypeAdapter(DoubleAdapter.class)
public Double aDouble = 123d;
}
Demo Code
package example;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.Marshaller;
public class Demo {
public static void main(String[] args) throws Exception {
JAXBContext jc = JAXBContext.newInstance(Root.class);
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(new Root(), System.out);
}
}
UPDATE
StaxMan's suggestion is a good one. If you specify the following package level annotation you can avoid the need of individually annotating all the Double properties
package-info.java
#XmlJavaTypeAdapters({
#XmlJavaTypeAdapter(type=Double.class, value=DoubleAdapter.class)
})
package example;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapters;
Write a getter that returns null, instead of Double.MAX_VALUE? (if type is 'double', need to change it to 'Double' first, to allow nulls).
Since JAXB by default ignores writing out of nulls, that should achieve what you are trying to do. This assumes you can modify legacy class in question.

Categories