Is there a way to use CDATA in JAXB without using EclipseLink API ?
JAXB Class :
#XmlRootElement(name = "Documentation")
#XmlAccessorType(XmlAccessType.FIELD)
public class Documentation {
#XmlJavaTypeAdapter(AdaptorCDATA.class)
#XmlValue
protected String value; //setter & getter implemeted
}
Marshaller Property : required eclipselink api org.eclipse.persistence.oxm.CharacterEscapeHandler
marshaller.setProperty(CharacterEscapeHandler.class.getName(), new org.eclipse.persistence.oxm.CharacterEscapeHandler() { // property required for CDATA
#Override
public void escape(char[] ac, int i, int j, boolean flag,
Writer writer) throws IOException {
writer.write( ac, i, j ); }
});
Adaptor Class :
import javax.xml.bind.annotation.adapters.XmlAdapter;
public class AdaptorCDATA extends XmlAdapter<String, String> {
#Override
public String marshal(String arg0) throws Exception {
return "<![CDATA[" + arg0 + "]]>";
}
#Override
public String unmarshal(String arg0) throws Exception {
return arg0;
}
}
Related
I'm looking for a way to use Jackson to create a type-tree instead of a value-tree.
I had assumed that this would be possible but I ran into an issue where Jackson creates a NullNode object when it encounters a field which has null as a value.
What I'm interested in is types, not values. Currently I'm doing the following as a workaround as I cannot provide Jackson with a class to build the tree:
package org.example.jackson.typetree;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Test;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
public class FunctioningTest {
static class SomeClass{
public Integer integer;
public String string;
}
#Test
void extractTypeTree() throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
final Constructor<?> constructor = SomeClass.class.getDeclaredConstructor();
final Object o = constructor.newInstance((Object[]) null);
final var jsonNode = new ObjectMapper().valueToTree(o);
final var fields = jsonNode.fields();
while(fields.hasNext()){
final var child = fields.next();
if(child.getValue().isIntegralNumber() || child.getValue().isTextual()){
System.out.println("Nice!");
}else if(child.getValue().isNull()){
System.out.println("Booooh...!");
}
}
}
}
As I mentioned this results in a ObjectNode instance which has 2 NullNode instances as children. What I would like however is to get an ObjectNode with a IntNode/NumericNode and a TextNode regardless of the actual value of the fields in the instance of SomeClass.
Can Jackson be used to do this?
I've put in a few more hours trying to figure out a way to traverse a Class and I've found a method that is good enough for me.
Just to highlight, Jackson does not create a type-tree. Instead it has several Visitor types which can be use to visit Serializers. Serializers within Jackson being the method by which a instance of a class is serialized to some output (JSON, YAML, etc.). When visiting these Serializers we can in turn check for properties of objects and in turn visit those.
Below is a rudimentary implementation of this mechanism, which can be adjusted to build a type-tree manually. I used jackson-databind:2.12.1 of but I believe the mechanism was introduced in jackson-databind: 2.2.0.
package org.example.jackson.type.tree;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.jsonFormatVisitors.*;
import com.fasterxml.jackson.databind.ser.SerializerFactory;
import org.junit.jupiter.api.Test;
import java.util.Set;
public class FunctioningTest {
#Test
void extractTypeTree() throws JsonMappingException {
final var objectMapper = new ObjectMapper();
final var serializerProvider = objectMapper.getSerializerProviderInstance();
final var javaType = objectMapper.constructType(SomeClass.class);
final var serializerFactory = objectMapper.getSerializerFactory();
final var typeSerializer = serializerFactory.createSerializer(serializerProvider, javaType);
final var FieldVisitor = new FieldVisitor(serializerProvider, serializerFactory);
typeSerializer.acceptJsonFormatVisitor(FieldVisitor, javaType);
}
static class SomeClass {
public Integer integer;
public String string;
public FieldClass fieldClass;
}
static class FieldClass {
public boolean aBoolean;
public double floatingPoint;
}
static class FieldVisitor extends JsonFormatVisitorWrapper.Base implements JsonFormatVisitorWrapper {
private final SerializerFactory serializerFactory;
public FieldVisitor(final SerializerProvider provider, final SerializerFactory serializerFactory) {
super(provider);
this.serializerFactory = serializerFactory;
}
#Override
public JsonObjectFormatVisitor expectObjectFormat(JavaType javaType) throws JsonMappingException {
System.out.println("FieldVisitor (Object): " + javaType);
final var objectVisitor = new ObjectVisitor(getProvider());
final var objectSerializer = serializerFactory.createSerializer(getProvider(), javaType);
final var properties = objectSerializer.properties();
while (properties.hasNext()) {
final var property = properties.next();
final var propertyType = property.getType();
final var propertySerializer = serializerFactory.createSerializer(getProvider(), propertyType);
propertySerializer.acceptJsonFormatVisitor(this, propertyType);
}
return objectVisitor;
}
#Override
public JsonArrayFormatVisitor expectArrayFormat(JavaType type) throws JsonMappingException {
return null;
}
#Override
public JsonStringFormatVisitor expectStringFormat(JavaType type) throws JsonMappingException {
System.out.println("FieldVisitor (String): " + type);
return new StringVisitor();
}
#Override
public JsonNumberFormatVisitor expectNumberFormat(JavaType type) throws JsonMappingException {
System.out.println("FieldVisitor (Number): " + type);
return new NumberVisitor();
}
#Override
public JsonIntegerFormatVisitor expectIntegerFormat(JavaType type) throws JsonMappingException {
System.out.println("FieldVisitor (Integer): " + type);
return new IntegerVisitor();
}
#Override
public JsonBooleanFormatVisitor expectBooleanFormat(JavaType type) throws JsonMappingException {
System.out.println("FieldVisitor (Boolean): " + type);
return new BooleanVisitor();
}
#Override
public JsonNullFormatVisitor expectNullFormat(JavaType type) throws JsonMappingException {
System.out.println("Null: " + type);
return null;
}
#Override
public JsonAnyFormatVisitor expectAnyFormat(JavaType type) throws JsonMappingException {
return null;
}
#Override
public JsonMapFormatVisitor expectMapFormat(JavaType type) throws JsonMappingException {
return null;
}
}
static class ObjectVisitor extends JsonObjectFormatVisitor.Base implements JsonObjectFormatVisitor {
public ObjectVisitor(SerializerProvider serializerProvider) {
super(serializerProvider);
}
#Override
public void property(BeanProperty writer) throws JsonMappingException {
System.out.println("ObjectVisitor: " + writer);
}
#Override
public void property(String name, JsonFormatVisitable handler, JavaType propertyTypeHint) throws JsonMappingException {
System.out.println("ObjectVisitor: " + String.join(", ", name, handler.toString(), propertyTypeHint.toString()));
}
#Override
public void optionalProperty(BeanProperty writer) throws JsonMappingException {
System.out.println("ObjectVisitor (optional): " + writer);
}
#Override
public void optionalProperty(String name, JsonFormatVisitable handler, JavaType propertyTypeHint) throws JsonMappingException {
System.out.println("ObjectVisitor (optional): " + String.join(", ", name, handler.toString(), propertyTypeHint.toString()));
}
}
static class StringVisitor implements JsonStringFormatVisitor {
#Override
public void format(JsonValueFormat format) {
System.out.println("StringVisitor (format): " + format);
}
#Override
public void enumTypes(Set<String> enums) {
System.out.println("StringVisitor (enums): " + enums);
}
}
static class IntegerVisitor implements JsonIntegerFormatVisitor {
#Override
public void numberType(JsonParser.NumberType type) {
System.out.println("IntegerVisitor (numberType): " + type);
}
#Override
public void format(JsonValueFormat format) {
System.out.println("IntegerVisitor (format): " + format);
}
#Override
public void enumTypes(Set<String> enums) {
System.out.println("IntegerVisitor (enums): " + enums);
}
}
static public class BooleanVisitor implements JsonBooleanFormatVisitor {
#Override
public void format(JsonValueFormat format) {
System.out.println("BooleanVisitor (format): " + format);
}
#Override
public void enumTypes(Set<String> enums) {
System.out.println("BooleanVisitor (enums): " + enums);
}
}
static class NumberVisitor implements JsonNumberFormatVisitor {
#Override
public void numberType(JsonParser.NumberType type) {
System.out.println("NumberVisitor (numberType): " + type);
}
#Override
public void format(JsonValueFormat format) {
System.out.println("NumberVisitor (format): " + format);
}
#Override
public void enumTypes(Set<String> enums) {
System.out.println("NumberVisitor (enums): " + enums);
}
}
}
Which outputs:
FieldVisitor (Object): [simple type, class io.serpentes.examples.schema.sources.jackson.FunctioningTest$SomeClass]
FieldVisitor (Integer): [simple type, class java.lang.Integer]
IntegerVisitor (numberType): INT
FieldVisitor (String): [simple type, class java.lang.String]
FieldVisitor (Object): [simple type, class io.serpentes.examples.schema.sources.jackson.FunctioningTest$FieldClass]
FieldVisitor (Boolean): [simple type, class boolean]
FieldVisitor (Number): [simple type, class double]
NumberVisitor (numberType): DOUBLE
ObjectVisitor (optional): property 'aBoolean' (field "io.serpentes.examples.schema.sources.jackson.FunctioningTest$FieldClass#aBoolean, no static serializer)
ObjectVisitor (optional): property 'floatingPoint' (field "io.serpentes.examples.schema.sources.jackson.FunctioningTest$FieldClass#floatingPoint, no static serializer)
ObjectVisitor (optional): property 'integer' (field "io.serpentes.examples.schema.sources.jackson.FunctioningTest$SomeClass#integer, no static serializer)
ObjectVisitor (optional): property 'string' (field "io.serpentes.examples.schema.sources.jackson.FunctioningTest$SomeClass#string, no static serializer)
ObjectVisitor (optional): property 'fieldClass' (field "io.serpentes.examples.schema.sources.jackson.FunctioningTest$SomeClass#fieldClass, no static serializer)
I'm developing some JavaSE GUI application which has to store and load it's data from/to XML file (source code avaliable here: https://github.com/SP8EBC/MKS_JG)
In some place of the data structure I've a HashMap which bond a base type to Short. In practice this base type is an abstract class, so my software adds object of child classes as a keys. To marshall and unmarshall these types I developed an adapter, but unfortunatelly whatever I did with annotations this adapter is never used, so the output XML file is wrong. I put some breakpoints inside methods and on Debug software newer stops on these breaks.
My questions is: What should I do inside the code to correctly handle this HashMap with my Adapter?
The class where the HashMap with base class exists is like that:
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
public class Competition {
// some other fields and methods
#XmlElement
public HashMap<LugerCompetitor, Short> startList;
}
At this point declaration of the base class looks like this
#XmlAccessorType(XmlAccessType.PUBLIC_MEMBER)
#XmlJavaTypeAdapter(value = LugerCompetitorAdapter.class)
public abstract class LugerCompetitor {
/**
* .... some commentary in polish
*/
public abstract CompetitionTypes getCompetitorType();
public abstract void setStartNumber(short num);
#XmlTransient
public abstract short getStartNumber();
public abstract String toString();
}
And one of the child classes looks like this
public class LugerDouble extends LugerCompetitor {
/**
* Saneczkarz na górze
*/
public Luger upper;
/**
* Saneczkarz na dole
*/
public Luger lower;
short startNum;
#Override
public CompetitionTypes getCompetitorType() {
// TODO Auto-generated method stub
return null;
}
public String toString() {
String out;
out = upper.surname + " / " + lower.surname;
return out;
}
#Override
public short getStartNumber() {
return this.startNum;
}
#Override
public void setStartNumber(short num) {
this.startNum = num;
}
}
As You see I wrote an Adapter which should convert totally different child objects to something standarized in the XML file. The adapter looks as follows:
public class LugerCompetitorAdapter extends XmlAdapter<LugerCompetitorAdapter.AdaptedCompetitorLuger, LugerCompetitor> {
public static class AdaptedCompetitorLuger {
#XmlAttribute
public long lugerSystemId; // LugerSingle
#XmlAttribute
public long bottomLugerSystemId; // Dwójka sankowa - dół
#XmlAttribute
public long upperLugerSystemId; // Dwójka sankowa - góra
#XmlAttribute
public long maleLugerSystemId; // Sztafeta albo drużyna
#XmlAttribute
public long femaleSystemId; // jw
#XmlAttribute
public long doubleSystemId; // jw
}
#Override
public AdaptedCompetitorLuger marshal(LugerCompetitor v) throws Exception {
AdaptedCompetitorLuger adaptedCompetitorLuger = new AdaptedCompetitorLuger();
if (v instanceof LugerSingle) {
adaptedCompetitorLuger.lugerSystemId = ((LugerSingle)v).single.getSystemId();
return adaptedCompetitorLuger;
}
if (v instanceof LugerDouble) {
adaptedCompetitorLuger.bottomLugerSystemId = ((LugerDouble)v).lower.getSystemId();
adaptedCompetitorLuger.upperLugerSystemId = ((LugerDouble)v).upper.getSystemId();
return adaptedCompetitorLuger;
}
return null;
}
#Override
public LugerCompetitor unmarshal(AdaptedCompetitorLuger v) throws Exception {
return null;
}
}
EDIT (Solution added)
So I made what I want in quite another way. I just made an adapter for whole HashMap, instead of trying do marschall and unmarschall LugerCompetitor itself. My solution is below and it seems to work when I'm generating the XML. Opposite direction still need to be developed.
#Component
public class StartListAdapter extends XmlAdapter<StartListAdapter.AdaptedStartList, Map<LugerCompetitor, Short>> {
RTE_ST rte_st;
#Autowired
#Lazy
public void setRTE(RTE_ST rte) {
rte_st = rte;
}
public static class AdaptedStartList {
#XmlElement(name="startListEntry")
public List<AdaptedEntry> adaptedList = new ArrayList<AdaptedEntry>();
}
public static class AdaptedEntry {
#XmlElement(required = false, nillable = true )
public Long lugerSystemId; // konkurencja pojedyncza K albo M
#XmlElement(required = false, nillable = true )
public Long lowerLugerSystemId; // dwójki sankowe - sankarz na dole
#XmlElement(required = false, nillable = true )
public Long upperLugerSystemId; // j/w ale sankarz na górze
#XmlElement(required = false, nillable = true )
public Long maleLugerSystemId; // M podczas sztafety albo konkurencji drużynowej
#XmlElement(required = false, nillable = true )
public Long femaleLugerSystemId; // K j/w
#XmlElement(required = true)
public short startNumber;
}
#Override
public AdaptedStartList marshal(Map<LugerCompetitor, Short> arg0) throws Exception {
AdaptedStartList out = new AdaptedStartList();
for (Entry<LugerCompetitor, Short> e : arg0.entrySet()) {
AdaptedEntry adaptedEntry = new AdaptedEntry();
LugerCompetitor k = e.getKey();
if (k instanceof LugerSingle) {
adaptedEntry.lugerSystemId = ((LugerSingle)k).single.getSystemId();
adaptedEntry.startNumber = e.getValue();
}
else if (k instanceof LugerDouble) {
adaptedEntry.lowerLugerSystemId = ((LugerDouble)k).lower.getSystemId();
adaptedEntry.upperLugerSystemId = ((LugerDouble)k).upper.getSystemId();
adaptedEntry.startNumber = e.getValue();
}
out.adaptedList.add(adaptedEntry);
}
return out;
}
#Override
public Map<LugerCompetitor, Short> unmarshal(AdaptedStartList arg0) throws Exception {
return null;
}
}
First, if possible but not necessary, convert LugerCompetitor to an interface. You can avoid all public abstract clutter:
public interface LugerCompetitor {
/**
* .... some commentary in polish
*/
CompetitionTypes getCompetitorType();
void setStartNumber(short num);
#XmlTransient
short getStartNumber();
}
Then you can define an element to represent LugerCompetitor and Short value:
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
public class LugerCompetitorValue {
#XmlElement
#XmlJavaTypeAdapter(LugerCompetitorAdapter.class)
public LugerCompetitor competitor;
#XmlElement
public Short value;
}
And add a collection of LugerCompetitorValue to your class:
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
public class Competition {
// some other fields and methods
#XmlElement
public List<LugetCompetitorValue> lugerCompetitorValues;
}
Then adapter should start working. The question, however, is how are you expecting to unmarshal the data on the client side?
I'm doing a WSDL client and want to know how I can set an XML element to be CDATA.
I'm using the wsimport to generate the source code, and the CDATA element is part of the request XML.
This is the XML class of the request:
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "", propOrder = { "dataRequest" })
#XmlRootElement(name = "ProcessTransaction")
public class ProcessTransaction {
protected String dataRequest;
public String getDataRequest() {
return dataRequest;
}
public void setDataRequest(String value) {
this.dataRequest = value;
}
}
I've already tried the #XmlAdapter, but it changes nothing on the output...
import javax.xml.bind.annotation.adapters.XmlAdapter;
public class AdaptorCDATA extends XmlAdapter<String, String> {
#Override
public String marshal(String arg0) throws Exception {
return "<![CDATA[" + arg0 + "]]>";
}
#Override
public String unmarshal(String arg0) throws Exception {
return arg0;
}
}
In the XML class:
#XmlJavaTypeAdapter(value=AdaptorCDATA.class)
protected String dataRequest;
I tried to debug, but it never steps on the AdaptorCDATA function.
The wsimport version is 2.2.9 and the jaxb-api version is 2.1.
So, as #user1516873 suggested, i moved the code to cxf, and with this, is working well. Now i'm using the "wsdl2java" to generate the code, and the jars from cxf on my project.
What is different in the code:
CdataInterceptor
import javax.xml.stream.XMLStreamWriter;
import org.apache.cxf.message.Message;
import org.apache.cxf.phase.AbstractPhaseInterceptor;
import org.apache.cxf.phase.Phase;
public class CdataInterceptor extends AbstractPhaseInterceptor<Message> {
public CdataInterceptor() {
super(Phase.MARSHAL);
}
public void handleMessage(Message message) {
message.put("disable.outputstream.optimization", Boolean.TRUE);
XMLStreamWriter writer = (XMLStreamWriter) message.getContent(XMLStreamWriter.class);
if (writer != null && !(writer instanceof CDataContentWriter)) {
message.setContent(XMLStreamWriter.class, new CDataContentWriter(writer));
}
}
public void handleFault(Message messageParam) {
System.out.println(messageParam);
}
}
CDataContentWriter
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamWriter;
import org.apache.cxf.staxutils.DelegatingXMLStreamWriter;
public class CDataContentWriter extends DelegatingXMLStreamWriter {
public CDataContentWriter(XMLStreamWriter writer) {
super(writer);
}
public void writeCharacters(String text) throws XMLStreamException {
boolean useCData = text.contains("RequestGeneric");
if (useCData) {
super.writeCData(text);
} else {
super.writeCharacters(text);
}
}
// optional
public void writeStartElement(String prefix, String local, String uri) throws XMLStreamException {
super.writeStartElement(prefix, local, uri);
}
}
Using the Writer and the Interceptor:
MyService wcf = new MyService(url, qName);
IMyService a = wcf.getBasicHttpBinding();
Client cxfClient = ClientProxy.getClient(a);
CdataInterceptor myInterceptor = new CdataInterceptor();
cxfClient.getInInterceptors().add(myInterceptor);
cxfClient.getOutInterceptors().add(myInterceptor);
And it works perfect!
I created a JacksonList class and JacksonListSerializer (extending JSonSerializer) for resolve a issue with collections and inheritance classes. Works fine.
The problem is that serializer ignore JsonIdentityInfo "rules". I have mapping exceptions like "Already had POJO for id (java.lang.Integer) .."
I put example below:
JacksonList.java
#JsonSerialize(using = JacksonListSerializer.class)
class JacksonList<E> extends ArrayList<E> {
private static final long serialVersionUID = 1L;
}
JacksonListSerializer.java
public class JacksonListSerializer extends JsonSerializer<JacksonList<?>> {
#Override
public void serialize(JacksonList<?> list, JsonGenerator generator, SerializerProvider provider)
throws IOException, JsonProcessingException {
generator.writeStartArray();
if (list != null) {
for (Object item : list) {
generator.writeObject(item);
}
}
generator.writeEndArray();
}
}
FirstItem.java
public class FirstItem {
private Son son;
public Son getSon() {
return son;
}
public void setSon(Son son) {
this.son = son;
}
}
Son.java
#JsonIdentityInfo(generator=ObjectIdGenerators.IntSequenceGenerator.class, property="#id", scope=Son.class)
public class Son {
public Son(String text) {
this.text = text;
}
private String text;
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
}
Main.java
ObjectMapper mapper = new ObjectMapper();
FirstItem f1 = new FirstItem();
FirstItem f2 = new FirstItem();
JacksonList<FirstItem> jacksonList = new JacksonList<FirstItem>();
List<FirstItem> list = new ArrayList<FirstItem>();
Son son = new Son();
f1.setSon(son);
f2.setSon(son);
list.add(f1);
list.add(f2);
jacksonList.add(f1);
jacksonList.add(f2);
System.out.println(mapper.writeValueAsString(jacksonList));
System.out.println(mapper.writeValueAsString(list));
Output:
{"JacksonList":[{"FirstItem":{"id":1,"son":{"type":"com.Son","#id":1}}},{"FirstItem":{"id":2,"son":{"type":"com.Son","#id":1}}}]}
{"ArrayList":[{"id":1,"son":{"type":"com.Son","#id":1}},{"id":2,"son":1}]}
As you can see in the second case (print of ArrayList) the information of the Son class are not duplicated. But in the first case the object son is put two times.
I have two classes like this:
public class A {
String aProp = "aProp";
public String getAProp() {
return aProp;
}
}
public class B {
String bProp = "bProp";
A a = new A();
#JsonProperty("bProp")
public String getBProp() {
return bProp;
}
#JsonSerialize(using = CustomSerializer.class)
public A getA() {
return a;
}
}
I'm expecting to get JSON like this:
{
"bProp": "bProp", // just serizlised bProp
"sProp1": "sProp1_aProp", // computed using aProp
"sProp2": "sProp2_aProp" // computed another way
}
So I wrote custom JsonSerializer like this:
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
public class CustomSerializer extends JsonSerializer<A> {
#Override
public void serialize(A a, JsonGenerator json, SerializerProvider provider) throws IOException {
json.writeStringField("sProp1", "sProp1_" + a.getAProp());
json.writeStringField("sProp2", "sProp2_" + a.getAProp());
}
}
But I keep getting an error:
com.fasterxml.jackson.core.JsonGenerationException: Can not write a field name, expecting a value
Unless I put json.writeStartObject(); and json.writeEndObject(); in serialize method (so it produces wrong JSON).
So I'm looking for a solution like #JsonUnwrapped to use with custom JsonSerializer.
I understand your problem and the thing that you need is UnwrappingBeanSerializer. You can see another related SO post:
Different JSON output when using custom json serializer in Spring Data Rest
The problem is that you cannot have both annotations #JacksonUnwrapped and #JsonSerialize in one field because when you have #JsonSerializer Jackson will always write field name.
Here is the complete solution:
public class CustomSerializer extends UnwrappingBeanSerializer {
public CustomSerializer(BeanSerializerBase src, NameTransformer transformer) {
super(src, transformer);
}
#Override
public JsonSerializer<Object> unwrappingSerializer(NameTransformer transformer) {
return new CustomSerializer(this, transformer);
}
#Override
protected void serializeFields(Object bean, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonGenerationException {
A a = (A) bean;
jgen.writeStringField("custom", a.getAProp());
jgen.writeStringField("custom3", a.getAProp());
}
#Override
public boolean isUnwrappingSerializer() {
return true;
}
}
Test case, you should redefine your object mapper with custom configuration or research for other method .
#RunWith(SpringJUnit4ClassRunner.class)
#WebAppConfiguration
#SpringApplicationConfiguration(classes = Application.class)
public class ColorsTest {
ObjectMapper mapper = new ObjectMapper();
#Before
public void setUp(){
mapper.registerModule(new Module() {
#Override
public String getModuleName() {
return "my.module";
}
#Override
public Version version() {
return Version.unknownVersion();
}
#Override
public void setupModule(SetupContext context) {
context.addBeanSerializerModifier(new BeanSerializerModifier() {
#Override
public JsonSerializer<?> modifySerializer(SerializationConfig config, BeanDescription beanDesc, JsonSerializer<?> serializer) {
if(beanDesc.getBeanClass().equals(A.class)) {
return new CustomSerializer((BeanSerializerBase) serializer, NameTransformer.NOP);
}
return serializer;
}
});
}
});
}
#Test
public void testSerializer() throws JsonProcessingException {
System.out.println(mapper.writeValueAsString(new B()));
}
}
Class B:
public class B {
#JsonProperty("bProp")
public String getBProp() {
return "bProp";
}
#JsonUnwrapped
public A getA() {
return new A();
}
}
I like to add this post and solution to the question asked here: Using custom Serializers with JsonUnwrapperd as the original poster is using JsonSerializer as I am. The suggest approach with the UnwrappingBeanSerializer won't work in this case. My post has a slightly different goal, but the idea from the post should be applicable to your use case easily, as it is just overwriting one more method and not having to add bunch of stuff apart from JsonUnwrapped on the property.
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonUnwrapped;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import java.io.IOException;
public class Test {
static class A {
String aProp = "aProp";
public String getAProp() {
return aProp;
}
}
static class B {
String bProp = "bProp";
A a = new A();
#JsonProperty("bProp")
public String getBProp() {
return bProp;
}
#JsonSerialize(using = CustomSerializer.class)
#JsonUnwrapped
public A getA() {
return a;
}
}
static class CustomSerializer extends JsonSerializer<A> {
#Override
public boolean isUnwrappingSerializer() {
return true;
}
#Override
public void serialize(A a, JsonGenerator json, SerializerProvider provider) throws IOException {
json.writeStringField("sProp1", "sProp1_" + a.getAProp());
json.writeStringField("sProp2", "sProp2_" + a.getAProp());
}
}
public static void main(String... a) throws Exception {
final ObjectMapper o = new ObjectMapper();
o.enable(SerializationFeature.INDENT_OUTPUT);
System.out.println(o.writeValueAsString(new B()));
}
}