javaxmljackson

Jackson XML deserialization of java.awt.Color from "r", "g", "b", "a" attributes?


I have a legacy system with the following (simplified) approach

public class Gadget {

    @JacksonXmlProperty
    private Color color;
:
:
}

and the XML representation of that is

<gadget r="10" g="20" b="30" a="40" />

Serializing that with a ColorSerializer;

public class ColorSerializer extends StdSerializer<Color> {

    public ColorSerializer() {
        super(Color.class);
    }

    @Override
    public void serialize(Color color, JsonGenerator gen, SerializerProvider provider) throws IOException {
        if (!(gen instanceof ToXmlGenerator)) {
            throw new UnsupportedOperationException("ColorSerializer can only be used with XML serialization");
        }

        ToXmlGenerator xmlGen = (ToXmlGenerator) gen;
        xmlGen.setNextName(new QName(null, "r"));

        xmlGen.setNextIsAttribute(true);
        xmlGen.writeNumber(color.getRed());

        xmlGen.setNextIsAttribute(true);
        xmlGen.writeNumberField("g", color.getGreen());

        xmlGen.setNextIsAttribute(true);
        xmlGen.writeNumberField("b", color.getBlue());

        xmlGen.setNextIsAttribute(true);
        xmlGen.writeNumberField("a", color.getAlpha());
    }
}

Note the trick to "rename" the "color" field name to "r" and then creating an extra 3 attributes in succession.

But I can NOT figure out how to deserialize that back to a java.awt.Color instance. I am getting an exception saying that attribute "r" is unknown. It seems that the XML drives the process, whereas I think I would need to get the parser to look at the Java code and have that drive lookup in the parsed XML.

Any ideas on how to go about this?

EDIT: Deserializer is expected to be something like;

public class ColorDeserializer extends StdDeserializer<Color> {

    ColorDeserializer() {
        super(Color.class);
    }

    @Override
    public Color deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JacksonException {
        TreeNode node = p.getCodec().readTree(p);
        int red = colorValueOf(node, "r");
        int green = colorValueOf(node, "g");
        int blue = colorValueOf(node, "b");
        int alpha = colorValueOf(node, "a");

        return new Color(red, green, blue, alpha);
    }

    private int colorValueOf(TreeNode node, String value) {
        JsonNode jsonNode = (JsonNode) node.get(value);
        if( jsonNode != null ) {
            return Integer.parseInt(jsonNode.asText());
        }
        return 0;
    }
}

Solution

  • The work around that I came up with looks like this...

    First, use JsonAlias to trigger the object mapper to pass control to the deserializer.

    public class Gadget {
    
        @JacksonXmlProperty
        @JsonAlias({"r","g","b","a"})
        private Color color;
    :
    :
    }
    

    Then use the XMLReader from the parser to obtain the attributes.

    public class ColorDeserializer extends StdDeserializer<Color> {
    
        ColorDeserializer() {
            super(Color.class);
        }
    
        @Override
        public Color deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JacksonException {
    
            XMLStreamReader staxReader = ((FromXmlParser) p).getStaxReader();
    
            int red = colorValueOf(staxReader, "r");
            int green = colorValueOf(staxReader, "g");
            int blue = colorValueOf(staxReader, "b");
            int alpha = colorValueOf(staxReader, "a");
            return new Color(red, green, blue, alpha);
        }
    
        private int colorValueOf(XMLStreamReader reader, String value) {
            String attributeValue = reader.getAttributeValue(null, value);
            if( attributeValue != null ) {
                return Integer.parseInt(attributeValue);
            }
            return 0;
        }
    }
    

    NOTE: This will trigger the creation and setting of 4 Color instances. Not optimal, but the only work-around I could think about.