1

I have a simple enum I'd like to serialize and deserialize. The class looks like this:

    public enum TipusViatge {
    OCI,
    NEGOCIS,
    FAMILIA;

    @Override
    public String toString() {
        return name().toUpperCase();
    }
}

The thing is, I send it via a restful call and the receiving side may receive any type (so it only knows it will receive Object). So Jackson should be able to figure out the type of the argument to deserialize it.

Is it possible to do so? I was thinking that including the class name in the resulting json should allow Jackson to figure out the type, but I've been unable to do so.

1 Answers1

1

I have worked over this problem for a while.

1st you could deserialize your json with Map<String, Object>. It alway works; you get standard types (your enumeration will be readed as plain string).

2nd in general case you alway know what kind of object you read. This is top-level object and you can set it to Jackson mapper: mapper.readerFor(cls).readValue(json). In case of your enumeration is a part of this cls object, then Jackson knows the type and just read value and parse to it.

3rd you actually could have multiple objects for one json string. I am talking about inheritance. And you could look at @JsonTypeInfo in Jackson documentation.

4th imagin that you read a json source and do not know what you read. In this case, you could ask Jackson to write marker at the beginning of the object. Just like you asking about class name. I think it relates to @JsonRootName. You can look on it here: Jackson JSON Deserialization with Root Element

I think that it is clean now how to work with objects in Jackson. I mean that we know how to tell Jackson what element we want to deserialize. Now we have one problem: how to serialize json -> our enumeration.

5th this is not a problem and works out of the box. Jackson uses name() method to serialize enumeration, and valueOf() to deserialize. You can look at it closer in EnumDeserializer in Jackson.

6th I do not like this behaviour, becuase it is case-sencitive. I faced with situation that when people write json string manually, the use lower-case and cannot deserialize it. Moreover, I belive, that writing enumeration constants directly to the json file is a bad practise, because If I want to refactor names of the enumeration, all existed json string should be modified as well (brrr). To solve thiese issues, I do following trick: 1. Implement EnumId interface with default implementation of parseId(String id) with using getId() to identify enumeration constants and using ignore case for compare. 1. I add id field to the enumeration 2. Add getId() - for serialization 3. Add parseId(String id) - for deserialization 4. Add new module in Jackson ObjectMapper with my customer serializer (it

should use `getId()` instead of `name()`).
if (enumId != null) {
    generator.writeString(enumId.getId());
}
  1. And tell Jackson how to deserialize this enum. Here this is dificult situation, becuase in different sources, Jackson use different deseriaization hierarchy and just adding another module to ObjectMapper with custom deserialize (just like in 4.) will not be working with all situations. To solve this problem, I found out that we could add @JsonCreator to parseId(String id) method in enumeration and Jackson will be using it in all situation.

I think that is all about this topic. I give you a code example to make it more clearly (it is better to write once, then explain twice):

public interface EnumId {

    String name();

    default String getId() {
        return name().toLowerCase();
    }

    static <T extends Enum<?> & EnumId> T parseId(Class<T> cls, String id) {
        T res = parseId(cls.getEnumConstants(), id, null);

        if (res != null) {
            return res;
        }

        throw new EnumConstantNotPresentException(cls, id);
    }

    static <T extends EnumId> T parseId(T[] values, String id, T def) {
        for (T value : values) {
            if (id != null ? id.equalsIgnoreCase(value.getId()) : value.getId() == null) {
                return value;
            }
        }

        return def;
    }

    static <T extends EnumId> T get(T value, T def) {
        return value != null ? value : def;
    }
}

public enum TipusViatge implements EnumId {
    OCI,
    NEGOCIS,
    FAMILIA;

    @JsonCreator
    public static TipusViatge parseId(String id) {
        return EnumId.parseId(TipusViatge.class, id);
    }
}
Oleg Cherednik
  • 17,377
  • 4
  • 21
  • 35
  • Thanks for your comments @oleg.cherednik! In my case, I don't know the types I'll be receiving. Using the Map and deserializing as Strings works, but as I don't know the types of the enums, I need to tag them somehow so Jackon can deserialize them. I'm trying your #JsonRootName suggestion, I'd expect the resulting jsonized string to include the enum class, but it doesn't. – Gustau Pérez Sep 10 '17 at 09:15
  • Then I think about custom serializer. For your enumeration, you can write it and manually add two parameters insted of one. For deserialzier, you could read class name and then use it to deserialzie second parameter which is enumeration constant. Smth. like that. I have in my mind, that I saw default implementation of this useing some ObjectMapper settings, but I don't remember right now. I look and write her if find it. – Oleg Cherednik Sep 10 '17 at 09:28
  • Here it is: new ObjectMapper().configure(SerializationFeature.WRAP_ROOT_VALUE, true).writeValueAsString(TipusViatge.FAMILIA) – Oleg Cherednik Sep 10 '17 at 09:31