Issue with unmarshalling json with inheritance, List and subclass

49 Views Asked by At

I have a base class:

@Data
@NoArgsConstructor
@AllArgsConstructor
@JsonTypeInfo(
        use = JsonTypeInfo.Id.NAME, property = "fieldType", include=JsonTypeInfo.As.EXISTING_PROPERTY, visible=true)
@JsonSubTypes({
        @JsonSubTypes.Type(value = JobCurrencyField.class, name = "CURRENCY")
})
public abstract class BaseJobField {
    private String name;
    private Object value;
    private String fieldType;

    public static void main(String[] args) {
        ObjectMapper objectMapper = new ObjectMapper();
        List<BaseJobField> jobFields = new ArrayList<>();
        jobFields.add(JobCurrencyField.create("currencyUS", "USD", 10.4));
        try {
            String fields = objectMapper.writeValueAsString(jobFields);
            List<BaseJobField> ret = objectMapper.readValue(fields, new TypeReference<List<BaseJobField>>(){});
            JobCurrencyField jobCurrencyField = (JobCurrencyField) ret.get(0);
            JobCurrencyField.Currency currency = (JobCurrencyField.Currency)jobCurrencyField.getValue();
        } catch (Exception x) {
            System.out.println(x.getMessage());
        }
    }
}

And a subclass:

@Data
@NoArgsConstructor
@JsonTypeName("CURRENCY")
public class JobCurrencyField extends BaseJobField {
    @Data
    @NoArgsConstructor
    public static class Currency {
        private String currencyName;
        private double amount;
    }

    public static JobCurrencyField create(String name, String currencyName, Double amount) {
        Currency currency = new Currency();
        currency.setCurrencyName(currencyName);
        currency.setAmount(amount);
        JobCurrencyField jobCurrencyField = new JobCurrencyField();
        jobCurrencyField.setName(name);
        jobCurrencyField.setValue(currency);
        jobCurrencyField.setFieldType("CURRENCY");
        return jobCurrencyField;
    }
}

The unmarshalling of the object, objectMapper.readValue() does not unmarshal the inner class to JobCurrencyField.CURRENCY but to a LinkedHashMap, so:
JobCurrencyField.Currency currency = (JobCurrencyField.Currency)jobCurrencyField.getValue();
throws an exception:
java.util.LinkedHashMap cannot be cast to JobCurrencyField$Currency
How do I tell objectMapper to unmarshal the value to the Currency field instead of a LinkedHashMap?

1

There are 1 best solutions below

0
devatherock On

One way to achieve what you want would be to parameterize the BaseJobField class as BaseJobField<T>, move Currency class into its own file and then make JobCurrencyField class extend BaseJobField<Currency>. The classes ended up looking like below:

BaseJobField.java:

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;

@Data
@NoArgsConstructor
@AllArgsConstructor
@JsonTypeInfo(
        use = JsonTypeInfo.Id.NAME, property = "fieldType", include=JsonTypeInfo.As.EXISTING_PROPERTY, visible=true)
@JsonSubTypes({
        @JsonSubTypes.Type(value = JobCurrencyField.class, name = "CURRENCY")
})
public abstract class BaseJobField<T> {
    private String name;
    private T value;
    private String fieldType;
}

Currency.java:

import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
public class Currency {
    private String currencyName;
    private double amount;
}

JobCurrencyField.java:

import com.fasterxml.jackson.annotation.JsonTypeName;

import lombok.NoArgsConstructor;

@NoArgsConstructor
@JsonTypeName("CURRENCY")
public class JobCurrencyField extends BaseJobField<Currency> {

    public static JobCurrencyField create(String name, String currencyName, Double amount) {
        Currency currency = new Currency();
        currency.setCurrencyName(currencyName);
        currency.setAmount(amount);
        JobCurrencyField jobCurrencyField = new JobCurrencyField();
        jobCurrencyField.setName(name);
        jobCurrencyField.setValue(currency);
        jobCurrencyField.setFieldType("CURRENCY");
        return jobCurrencyField;
    }
}

Test to verify the deserialization:

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.core.type.TypeReference;
import java.util.List;
import java.util.ArrayList;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

public class JacksonTest {

    @Test
    public void testDeserialize() throws Exception {
        ObjectMapper objectMapper = new ObjectMapper();
        List<BaseJobField<?>> jobFields = new ArrayList<>();
        jobFields.add(JobCurrencyField.create("currencyUS", "USD", 10.4));
        String fields = objectMapper.writeValueAsString(jobFields);

        List<BaseJobField<?>> ret = objectMapper.readValue(fields, new TypeReference<List<BaseJobField<?>>>(){});
        JobCurrencyField jobCurrencyField = (JobCurrencyField) ret.get(0);
        Currency currency = (Currency)jobCurrencyField.getValue();

        Assertions.assertTrue(currency instanceof Currency);
        Assertions.assertEquals("USD", currency.getCurrencyName());
        Assertions.assertEquals(10.4d, currency.getAmount());
    }
}