You need to write custom serialiser. To do that, extend com.fasterxml.jackson.databind.JsonSerializer class. Also, to create extra wrap element use startWrappedValue method. Example code could look like below:
import com.fasterxml.jackson.annotation.JsonRootName;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import com.fasterxml.jackson.dataformat.xml.ser.ToXmlGenerator;
import javax.xml.namespace.QName;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
public class XmlMapperApp {
public static void main(String[] args) throws Exception {
XmlMapper xmlMapper = new XmlMapper();
xmlMapper.enable(SerializationFeature.INDENT_OUTPUT);
xmlMapper.setDefaultUseWrapper(false);
Assets assets = new Assets();
assets.getAssets().add(new Asset());
assets.getAssets().add(new Asset());
System.out.println(xmlMapper.writeValueAsString(assets));
}
}
class AssetXMLSerializer extends JsonSerializer<Asset> {
private final QName wrapper = new QName("val1_val2");
@Override
public void serialize(Asset value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
ToXmlGenerator xmlGen = (ToXmlGenerator) gen;
xmlGen.writeStartObject();
xmlGen.startWrappedValue(wrapper, wrapper);
xmlGen.writeStringField("val1", value.getVal1());
xmlGen.writeStringField("val2", value.getVal2());
xmlGen.finishWrappedValue(wrapper, wrapper);
xmlGen.writeEndObject();
}
}
@JsonRootName("ASSETS")
class Assets {
@JacksonXmlProperty(localName = "ASSET")
private List<Asset> assets = new ArrayList<>();
// getters, setters, toString
}
@JsonSerialize(using = AssetXMLSerializer.class)
class Asset {
private String val1;
private String val2;
// getters, setters, toString
}
Above code prints:
<ASSETS>
<ASSET>
<val1_val2>
<val1>x</val1>
<val2>y</val2>
</val1_val2>
</ASSET>
<ASSET>
<val1_val2>
<val1>x</val1>
<val2>y</val2>
</val1_val2>
</ASSET>
</ASSETS>
Solution with using default serialiser for a huge bean
To use default bean serialiser for an Asset class you need to use BeanSerializerModifier and register it using SimpleModule. We need to invoke serializeFields method which is protected, so I created ExpandXmlBeanSerializer just to make it public so we can use it in our implementation:
import com.fasterxml.jackson.annotation.JsonRootName;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.BeanDescription;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializationConfig;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.BeanSerializerModifier;
import com.fasterxml.jackson.databind.ser.std.BeanSerializerBase;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import com.fasterxml.jackson.dataformat.xml.ser.ToXmlGenerator;
import com.fasterxml.jackson.dataformat.xml.ser.XmlBeanSerializer;
import javax.xml.namespace.QName;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class XmlMapperApp {
public static void main(String[] args) throws Exception {
SimpleModule assetModule = new SimpleModule();
assetModule.setSerializerModifier(new LoopBackBeanSerializerModifier());
XmlMapper xmlMapper = new XmlMapper();
xmlMapper.registerModule(assetModule);
xmlMapper.enable(SerializationFeature.INDENT_OUTPUT);
xmlMapper.setDefaultUseWrapper(false);
Assets assets = new Assets();
assets.getAssets().add(new Asset("x0", "y0"));
assets.getAssets().add(new Asset("x1", "y1"));
System.out.println(xmlMapper.writeValueAsString(assets));
}
}
class LoopBackBeanSerializerModifier extends BeanSerializerModifier {
@Override
public JsonSerializer<?> modifySerializer(SerializationConfig config, BeanDescription beanDesc, JsonSerializer<?> serializer) {
if (beanDesc.getBeanClass() == Asset.class) {
return new AssetXMLSerializer(new ExpandXmlBeanSerializer((BeanSerializerBase) serializer));
}
return serializer;
}
}
class ExpandXmlBeanSerializer extends XmlBeanSerializer {
public ExpandXmlBeanSerializer(BeanSerializerBase src) {
super(src);
}
@Override
public void serializeFields(Object bean, JsonGenerator gen0, SerializerProvider provider) throws IOException {
super.serializeFields(bean, gen0, provider);
}
}
class AssetXMLSerializer extends JsonSerializer<Asset> {
private final QName wrapper;
private final ExpandXmlBeanSerializer baseSerializer;
public AssetXMLSerializer(ExpandXmlBeanSerializer baseSerializer) {
this.baseSerializer = Objects.requireNonNull(baseSerializer);
String fields = String.join("_",
Stream.of(Asset.class.getDeclaredFields())
.map(Field::getName)
.collect(Collectors.toList()));
this.wrapper = new QName(fields);
}
@Override
public void serialize(Asset value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
ToXmlGenerator xmlGen = (ToXmlGenerator) gen;
xmlGen.writeStartObject();
xmlGen.startWrappedValue(wrapper, wrapper);
baseSerializer.serializeFields(value, gen, serializers);
xmlGen.finishWrappedValue(wrapper, wrapper);
xmlGen.writeEndObject();
}
}
@JsonRootName("ASSETS")
class Assets {
@JacksonXmlProperty(localName = "ASSET")
private List<Asset> assets = new ArrayList<>();
// getters, setters, toString
}
class Asset {
private String val1;
private String val2;
public Asset(String val1, String val2) {
this.val1 = val1;
this.val2 = val2;
}
// getters, setters, toString
}
See also:
- Jackson xml and json root element
- Using Jackson to add XML attributes to manually-built node-tree
- Multiple Jackson XML Custom (XMLStreamWriter) Serialisers throws Exception
SpringBoot: Consume & Produce XML with a Custom Serializer + Deserializer
Jackson deserialization SNS message error MismatchedInputException