Search code examples
javajacksonjackson-dataformat-xml

Serializing a list produced "Item" nodes


i a rather simple java object model. A booking class has a list of hotels as member. (simplified source)

class Booking {
  private Integer bookingId;
  private List<Hotel> hotels;
}

I want to serialize the booking to XML by using a StdSerailizer for each class.

public class HotelTransform extends StdSerializer<Hotel> {

    protected HotelTransform(Class<Hotel> t) {
        super(t);
    }

    private static final long serialVersionUID = 4102379614389258553L;

    @Override
    public void serialize(Hotel value, JsonGenerator gen, SerializerProvider provider) throws IOException {
        gen.writeStartObject();        
        gen.writeStringField("HName", value.getHotelName());
        gen.writeStringField("Start", value.m_startDateLocal.toString());
        gen.writeStringField("End", value.m_startDateLocal.toString());
        gen.writeEndObject();
    } 
}
public class BookingTransform extends StdSerializer<Booking> {

    protected BookingTransform(Class<Booking> t) {
        super(t);
    }

    @Override
    public void serialize(Booking value, JsonGenerator gen, SerializerProvider provider) throws IOException {
        
        gen.writeStartObject();        
        gen.writeObjectField("Entries", value.getBookBookingEntry());
        gen.writeEndObject();

    }
}

The serializing itself is doen with the follwoing test code

public void simpleSmokeTest() {

        Booking booking = new Booking();
        booking.setBookingId(99);

        Hotel aHotel = new Hotel();
        aHotel.setHotelName("HEHotel");
        aHotel.setStartDateLocal(LocalDateTime.now());
        aHotel.setEndDateLocal(LocalDateTime.now());
        booking.addhotel(aHotel);

        Hotel aHotel2 = new Hotel();
        aHotel2.setHotelName("HEHotel2");
        aHotel2.setStartDateLocal(LocalDateTime.now());
        aHotel2.setEndDateLocal(LocalDateTime.now());
        booking.addhotel(aHotel2);

        ObjectMapper mapper = new XmlMapper();
        SimpleModule module = new SimpleModule();
        module.addSerializer(new BookingTransform(Booking.class));
        module.addSerializer(new HotelTransform(Hotel.class));
        mapper.enable(SerializationFeature.INDENT_OUTPUT);
        mapper.registerModule(module);

        try {
            String serialized = mapper.writeValueAsString(booking);
            m_logger.debug("Serialized to: " + serialized);
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }
    }

The generated output is:

<Booking>
  <Entries>
    <item>
      <HName>HEHotel</HName>
      <Start>2020-12-29T01:43:18.008480</Start>
      <End>2020-12-29T01:43:18.008480</End>
    </item>
    <item>
      <HName>HEHotel2</HName>
      <Start>2020-12-29T01:43:18.009463</Start>
      <End>2020-12-29T01:43:18.009463</End>
    </item>
  </Entries>
</Booking>

This is already close to what i want but i do not want the entries of my Hotel list named as "item" i want them to be named "Hotel". Like so:

<Booking>
  <Entries>
    <Hotel>
      <HName>HEHotel</HName>
      <Start>2020-12-29T01:43:18.008480</Start>
      <End>2020-12-29T01:43:18.008480</End>
    </Hotel>
    <Hotel>
      <HName>HEHotel2</HName>
      <Start>2020-12-29T01:43:18.009463</Start>
      <End>2020-12-29T01:43:18.009463</End>
    </Hotel>
  </Entries>
</Booking>

I really tries a lot of things no. Without success. Can anyone explain me how to get to solve that? By the way, using annotations inside my POJOs is no option.

Kind regards Harri E.


Solution

  • I assume the issue is because of the difference in the way, arrays are handled in JSON and in XML.

    Below is the snippet from XMLSerializerProvider._startRootArray():

    protected void _startRootArray(ToXmlGenerator xgen, QName rootName) throws IOException
        {
            xgen.writeStartObject();
            // Could repeat root name, but what's the point? How to customize?
            xgen.writeFieldName("item");
        }
    

    So in case of XML, each array element is considered as an object with default field name as item.


    One option for your current use case, is to use a Transformer specific to XML and in it, manually serialize the List as below:

    @Override
        public void serialize(Booking value, JsonGenerator gen, SerializerProvider provider) throws IOException {
            gen.writeStartObject();
            gen.writeFieldName("Entries");
            gen.writeStartObject();
            for (Hotel hotel : value.getBookBookingEntry()) {
                gen.writeFieldName("Hotel");
                gen.writeObject(hotel);
            }
            gen.writeEndObject();
            gen.writeEndObject();
        }
    

    Note: If you use this serializer for JSON, it will result in duplicate fields named Hotel within the object Entries.