Is it possible to express this with Jackson Dataformat XML?

21 views
Skip to first unread message

Mark Raynsford

unread,
Sep 11, 2021, 1:28:27 PM9/11/21
to jackso...@googlegroups.com
Hello!

I'm trying to use Jackson to serialize/deserialize values in a format
that has an existing XML schema. I'm having difficulty getting
serialized values to match the schema, and the rather sparse
documentation isn't helping matters. Here's a small example (the
Location and LocationID types are simplified versions of the types
that appear in the real schema):

~~~
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;

import java.io.IOException;
import java.util.Objects;
import java.util.UUID;

public final class SerialDemo2
{
private SerialDemo2()
{

}

record LocationID(UUID id) {
LocationID {
Objects.requireNonNull(id, "id");
}
}

@JacksonXmlRootElement(namespace = "urn:com.io7m.cardant.inventory:1")
record Location(
@JsonProperty(required = true)
@JacksonXmlProperty(isAttribute = true, localName = "id")
LocationID id,

@JsonProperty(required = false)
@JacksonXmlProperty(isAttribute = true, localName = "parent")
LocationID parent
) {
Location {
Objects.requireNonNull(id, "id");
}
}

public static void main(
final String[] args)
throws IOException
{
final var mapper =
XmlMapper.builder()
.build();

System.out.println("Expected: ");
System.out.println();
System.out.println("""
<Location xmlns="urn:com.io7m.cardant.inventory:1"
id="6e3f4213-db36-4ea3-91ba-1ce6917cbcbb"
parent="265f34b3-8c86-4a1f-b23a-bb104238bfc6"/>
""");

System.out.println("Received: ");
System.out.println();
mapper.writeValue(
System.out,
new Location(
new LocationID(UUID.randomUUID()),
new LocationID(UUID.randomUUID())
)
);
System.out.println();
}
}
~~~

The output of the above is:

~~~
Expected:

<Location xmlns="urn:com.io7m.cardant.inventory:1"
id="6e3f4213-db36-4ea3-91ba-1ce6917cbcbb"
parent="265f34b3-8c86-4a1f-b23a-bb104238bfc6"/>

Received:

<Location xmlns="urn:com.io7m.cardant.inventory:1"><id xmlns=""><id>c09fb552-e36c-4213-b56a-7729cd5b7999</id></id><parent xmlns=""><id>58c399ae-0256-4dbd-a00e-0c1c01189559</id></parent></Location>
~~~

I've tried various combinations of the XML properties, and it's not
clear how (or even if) I can use the existing JSON properties to get
the right input/output mapping.

--
Mark Raynsford | https://www.io7m.com

Tatu Saloranta

unread,
Sep 12, 2021, 12:19:28 AM9/12/21
to jackson-user
The main (or initial?) problem here is just that type LocationID is
taken to be a POJO with properties (since Records by definition are),
instead of something behaving like String value. The issue is that
POJOs cannot be serialized as attributes but only as elements.
In theory it is possible to annotate Records to work like Strings,
too, but it might be easier to first try to create equivalent "simple"
pojo with
something like:

static class LocationId {
private UUID id;

@JsonCreator(mode = JsonCreator.Mode.DELEGATING)
public LocationId(UUID id) { this.id = id; }

@JsonValue // method name can be anything
protected UUID id() { return id; }
}

which should then serialize as a basic String value, and deserialized
similarly from a String value.

I have not verified above but conceptually this should work.

-+ Tatu +-

rm....@gmail.com

unread,
Sep 12, 2021, 4:30:10 AM9/12/21
to Tatu Saloranta, jackso...@googlegroups.com
On 2021-09-11T21:19:15 -0700
Tatu Saloranta <ta...@fasterxml.com> wrote:
>
> The main (or initial?) problem here is just that type LocationID is
> taken to be a POJO with properties (since Records by definition are),
> instead of something behaving like String value. The issue is that
> POJOs cannot be serialized as attributes but only as elements.
> In theory it is possible to annotate Records to work like Strings,
> too, but it might be easier to first try to create equivalent "simple"
> pojo with
> something like:
>
> static class LocationId {
> private UUID id;
>
> @JsonCreator(mode = JsonCreator.Mode.DELEGATING)
> public LocationId(UUID id) { this.id = id; }
>
> @JsonValue // method name can be anything
> protected UUID id() { return id; }
> }
>
> which should then serialize as a basic String value, and deserialized
> similarly from a String value.

It works! Thank you!

Will records get some kind of special handling post JDK 17? I often use
these kind of wrappers for extra type safety, and it's a shame that
they can't be expressed as records in this case.

Tatu Saloranta

unread,
Sep 12, 2021, 7:47:03 PM9/12/21
to rm....@gmail.com, jackson-user
On Sun, Sep 12, 2021 at 1:30 AM <rm....@gmail.com> wrote:
>
> On 2021-09-11T21:19:15 -0700
> Tatu Saloranta <ta...@fasterxml.com> wrote:
> >
> > The main (or initial?) problem here is just that type LocationID is
> > taken to be a POJO with properties (since Records by definition are),
> > instead of something behaving like String value. The issue is that
> > POJOs cannot be serialized as attributes but only as elements.
> > In theory it is possible to annotate Records to work like Strings,
> > too, but it might be easier to first try to create equivalent "simple"
> > pojo with
> > something like:
> >
> > static class LocationId {
> > private UUID id;
> >
> > @JsonCreator(mode = JsonCreator.Mode.DELEGATING)
> > public LocationId(UUID id) { this.id = id; }
> >
> > @JsonValue // method name can be anything
> > protected UUID id() { return id; }
> > }
> >
> > which should then serialize as a basic String value, and deserialized
> > similarly from a String value.
>
> It works! Thank you!

Great! I am happy this is the case & thank you for verifying and
helping others possibly find it too.

> Will records get some kind of special handling post JDK 17? I often use
> these kind of wrappers for extra type safety, and it's a shame that
> they can't be expressed as records in this case.

Jackson 2.12 does support Record types, actually, but the challenge is
in how to annotate them with `@JsonValue`
and `@JsonCreator`.

Adding `@JsonCreator` is possible if you explicitly add the 1-argument
constructor, but that is obviously kind of code Records are meant to
help eliminate.
Similarly `@JsonValue` would be possible to add if you add an explicit accessor.

In a way... Hmmh. I am almost tempted to consider specific kind of new
annotation that would essentially allow marking this style of 1-field
Record
as "wrapper" record. I think that'd be perfectly doable.

Could I ask you to file an issue for jackson-databind, to request such
annotation (annotation goes in jackson-annotations, but handling code
is all
databind), if that makes sense to you? Perhaps I could even think of
how to extend handling to POJOs... but start with Records for sure.

-+ Tatu +-

rm....@gmail.com

unread,
Sep 13, 2021, 4:53:49 AM9/13/21
to Tatu Saloranta, jackso...@googlegroups.com
On 2021-09-12T16:46:50 -0700
Done: https://github.com/FasterXML/jackson-databind/issues/3274
Reply all
Reply to author
Forward
0 new messages