Hello!

I have another example that feels like it _should_ work but actually
doesn't. Again, this is a drastically simplified example that comes
from a real existing schema, and uses sealed interfaces that are
currently a preview feature (at least until JDK 17 appears tomorrow).

~~~
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.databind.SerializationFeature;
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.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.util.Objects;

import static java.nio.charset.StandardCharsets.UTF_8;

public final class SerialDemo3
{
  private SerialDemo3()
  {

  }

  @JsonSubTypes({
    @JsonSubTypes.Type(value = Ex1Bike.class, name = "Bike"),
    @JsonSubTypes.Type(value = Ex1Car.class, name = "Car"),
    @JsonSubTypes.Type(value = Ex1RollerSkates.class, name =
  "RollerSkates") })
  public sealed interface Vehicle
  {
    @JacksonXmlRootElement(localName = "Bike")
    record Ex1Bike(
      @JacksonXmlProperty(isAttribute = true)
      boolean bell,
      @JacksonXmlProperty(isAttribute = true)
      boolean diskBrakes)
      implements Vehicle
    {

    }

    @JacksonXmlRootElement(localName = "Car")
    record Ex1Car(
      @JacksonXmlProperty(isAttribute = true)
      String manufacturer)
      implements Vehicle
    {
      public Ex1Car
      {
        Objects.requireNonNull(manufacturer, "manufacturer");
      }
    }

    @JacksonXmlRootElement(localName = "RollerSkates")
    record Ex1RollerSkates()
      implements Vehicle
    {
      public Ex1RollerSkates
      {

      }
    }
  }

  public record VehicleHolder(Vehicle vehicle)
  {
    public VehicleHolder
    {
      Objects.requireNonNull(vehicle, "vehicle");
    }
  }
  
  public static void main(
    final String[] args)
    throws Exception
  {
    final var mapper =
      XmlMapper.builder()
        .enable(SerializationFeature.INDENT_OUTPUT)
        .build();

    System.out.println("""
                         Expected:
                         <RollerSkates/>               
                         """);
    serializeAndParse(mapper, new Ex1RollerSkates());

    System.out.println("""
                         Expected:
                         <Car manufacturer="Mitsubishi"/>               
                         """);
    serializeAndParse(mapper, new Ex1Car("Mitsubishi"));

    System.out.println("""
                         Expected:
                         <Bike bell="true" diskBrakes="true"/> """);
    serializeAndParse(mapper, new Ex1Bike(true, false));

    System.out.println("""
                         Expected:
                         <VehicleHolder>
                           <Bike bell="true" diskBrakes="true"/>
                         </VehicleHolder>             
                         """);
    serializeAndParse(mapper, new VehicleHolder(new Ex1Bike(true,
    false))); }

  private static void serializeAndParse(
    final XmlMapper mapper,
    final Object input)
  {
    System.out.println("Serializing:");
    System.out.println(input);
    System.out.println();

    try (var output = new ByteArrayOutputStream()) {
      mapper.writeValue(output, input);
      output.flush();
      System.out.println("Serialized:");
      System.out.println(output.toString(UTF_8));

      System.out.println("Parsing...");
      final Object result;
      try (var stream = new ByteArrayInputStream(output.toByteArray()))
      { result = mapper.readValue(stream, input.getClass());
      }
      System.out.println("Received: " + result);
    } catch (final Exception e) {
      System.out.println(e);
    } finally {
      System.out.println("--");
      System.out.println();
    }
  }
}
~~~

The output of the program is:

~~~
Expected:
<RollerSkates/>

Serializing:
Ex1RollerSkates[]

Serialized:
<RollerSkates/>

Parsing...
com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot
construct instance of
`SerialDemo3$Vehicle$Ex1RollerSkates`
(although at least one Creator exists): no default no-arguments
constructor found at [Source: (ByteArrayInputStream); line: 1, column:
1] --

Expected:
<Car manufacturer="Mitsubishi"/>

Serializing:
Ex1Car[manufacturer=Mitsubishi]

Serialized:
<Car manufacturer="Mitsubishi"/>

Parsing...
com.fasterxml.jackson.databind.exc.ValueInstantiationException: Cannot
construct instance of `SerialDemo3$Vehicle$Ex1Car`,
problem: manufacturer at [Source: (ByteArrayInputStream); line: 1,
column: 1] --

Expected:
<Bike bell="true" diskBrakes="true"/>

Serializing:
Ex1Bike[bell=true, diskBrakes=false]

Serialized:
<Bike bell="true" diskBrakes="false"/>

Parsing...
com.fasterxml.jackson.databind.exc.InvalidDefinitionException:
Duplicate creator property "" (index 0 vs 1) for type
`SerialDemo3$Vehicle$Ex1Bike` at [Source: (ByteArrayInputStream); line:
1, column: 1] --

Expected:
<VehicleHolder>
  <Bike bell="true" diskBrakes="true"/>
</VehicleHolder>

Serializing:
VehicleHolder[vehicle=Ex1Bike[bell=true, diskBrakes=false]]

Serialized:
<VehicleHolder>
  <vehicle bell="true" diskBrakes="false"/>
</VehicleHolder>

Parsing...
com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot
construct instance of
`SerialDemo3$Vehicle`
(no Creators, like default constructor, exist): abstract types either
need to be mapped to concrete types, have custom deserializer, or
contain additional type information at [Source: (ByteArrayInputStream);
line: 2, column: 3] (through reference chain:
SerialDemo3$VehicleHolder["vehicle"])
-- ~~~

There are several issues here:

1. Deserialization fails for Ex1RollerSkates with the error "(although
   at least one Creator exists): no default no-arguments constructor
   found". This is obviously incorrect as it's a record class and the
   only and only constructor available has no arguments.

2. Deserialization fails for Ex1Car for reasons I don't fully
   understand. I've tried adding a JsonCreator annotation onto the
   constructor, but it doesn't change anything.

3. Deserialization fails for Ex1Bike, claiming there's a duplicate
   creator property.

4. Serialization of the VehicleHolder type essentially loses type
   information and creates a lowercase "vehicle" element containing
   the attributes of whatever record type was used.

5. Deserialization of the VehicleHolder type obviously fails due to
   point 4.

Interestingly, trying the following:

~~~
      mapper.readValue("""
<VehicleHolder>
  <Bile bell="true" diskBrakes="false"/>
</VehicleHolder>       
      """.getBytes(UTF_8), 
        VehicleHolder.class
      );
~~~

... Also fails, but for the same reason as point 2. Jackson appears
to either handle constructor injection for POJOs, or field injection
for mutable values, but seems to trip over when it comes to immutable
records.

-- 
Mark Raynsford | https://www.io

-- 
You received this message because you are subscribed to the Google Groups 
"jackson-user" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To view this discussion on the web visit 
https://groups.google.com/d/msgid/jackson-user/20210913110517.02065929%40sunflower.int.arc7.info.

Attachment: pgpMlk3HqCIrE.pgp
Description: OpenPGP digital signature

Reply via email to