WTF-debugging: the case of the obscure configuration


Ever stared at a piece of code without understanding why in the world it does not work? Or why it actually works at all? I'd like to call this phenomenon WTF-debugging and I've been remarkably free of it since I've been doing framework-free backend java. But now I have a new job and we use all the popular frameworks, for better and for worse. Well, really only for worse, IMO, but I will write more on that when I understand my aversion better. So far, I have discovered that the tendency to want to write frameworks is very strong because it is the ultimate intellectual masturbation. The tendency to want to use frameworks is more puzzling, but I think we are all attracted to magic to some degree and there is a powerful illusion that frameworks provide a lot of value, automagically.

We are working with JSON in Java and using Jackson, and I had a little problem where one of the fields of the main object could be a different type depending on what the object represented, as indicated by a type name in another field. So I had to work out how to configure Jackson to handle it, which turned out to be a little challenging. After an hour or so I hit upon a fruitful phrasing of the search terms and found a solution.



@JsonDeserialize(builder = Attachment.AttachmentBuilder.class)
public class Attachment {

    private final long id;
    private final String attachmentType;
    ...

    public interface ExcelSheets extends List<ExcelSheet> {}

    private static class ExcelSheetsImpl extends ArrayList<ExcelSheet> implements ExcelSheets {}

    @JsonTypeInfo(use = Id.NAME, property = "attachmentType")
    @JsonSubTypes(value =  {
            @JsonSubTypes.Type(value = ExcelSheetsImpl.class, name ="excel")
    })
    private final Object typeSpecificData;

    Attachment(long id, String attachmentType, long reportId, String filename, Object typeSpecificData) {
        this.id = id;
        this.attachmentType = attachmentType;
        ...
        this.typeSpecificData = typeSpecificData;
    }

    public long getId() { return id; }

    public String getAttachmentType() { return attachmentType; }

    ...

    public Object getTypeSpecificData() { return typeSpecificData; }


    @Override
    public AttachmentBuilder builder() { return new AttachmentBuilder(this); }

    public static class AttachmentBuilder implements Builder {
        private long id;
        private String attachmentType;
        private Object typeSpecificData;
        ...

        public AttachmentBuilder() {}

        AttachmentBuilder(Attachment attachment) {
            this.id = attachment.id;
            this.attachmentType = attachment.attachmentType;
            ...
        }

        @Override
        public AttachmentBuilder withId(long id) {
            this.id = id;
            return this;
        }

        public AttachmentBuilder withAttachmentType(String attachmentType) {
            this.attachmentType = attachmentType;
            return this;
        }

        public AttachmentBuilder withTypeSpecificData(Object typeSpecificData) {
            this.typeSpecificData = typeSpecificData;
            return this;
        }

        ...

        @Override
        public Attachment build() {
            return new Attachment(id, attachmentType, reportId, filename, typeSpecificData);
        }
    }
}

Now I could be happy with that and sing the praises of Jackson and "look how elegantly it got configured". But should I?

Even when I have this solution before me, I still can't quite figure it out from the documentation (WTF?), so what will happen in six months time when I have to modify this code?

And here comes an even bigger WTF: change the type "Object" for typeSpecificData to "ExcelSheets" and deserialization no longer works! (What I really wanted to do was to introduce a marker interface, TypeSpecificData, but, as you can surmise, that didn't work either.)

Even though Jackson is (sadly) perhaps the easiest way to handle JSON in Java, I think there may be good reasons besides the above to avoid using it. I won't go into that in detail in this post, but I will leave with this thought: Jackson or any other automagic data-binding framework entices you to create Java objects that match the serialized JSON, but your serialization format is not your internal model, at least not forever, because the two have different reasons to change. So even after you get a deserialized object from Jackson, you should probably write lots of code to transfer the data into your internal representation. Then what did you gain?

Comments

Popular Posts