How to deserialize based on information available in the parent class

I use Jackson to deserialize a number of different implementations of the Product interface. These product implementations have different fields, but everyone has an InsuredAmount field. This InsuredAmount class has a value field and an IAType field. IAType is a token interface with various enumerations as an implementation.

Now here's the problem: the enum implementation of the IAType interface corresponds to a specific implementation of the Product interface. How can I make a general implementation and tell Jackson to find the correct implementation of your IAType? Should I use a generic parameter for the product and the IAType interface that identifies the product implementation? Should I use the Productable functional interface for classes that identify the product implementation? How can I say that Jackson used this implementation?

I hope the code below explains the problem, I decided to implement the Productable interface here, but it would be better to also create a more efficient structure to solve this problem.

 @JsonPropertyOrder({"type", "someInfo"}) public class InsuredAmount implements Productable, Serializable { private static final long serialVersionUID = 1L; private IAType type; private String someInfo; public InsuredAmount() { } public InsuredAmount(IAType typeA, String someInfo) { this.type = typeA; this.someInfo = someInfo; } /* This should be on the product level, but if I can solve this problem, the next level will just be more of the same. */ @JsonIgnore @Override public Product getProduct() { return Product.PROD_A; } // Getters, setters, equals, etc. omitted. } 

-

 public interface Productable { public Product getProduct(); } 

-

 public enum Product { PROD_A, PROD_B; } 

-

 @JsonDeserialize(using = IATypeDeserializer.class) public interface IAType extends Productable { } 

-

 public enum IATypeA implements IAType { FOO, BAR; @Override public Product getProduct() { return Product.PROD_A; } } 

-

 public class IATypeDeserializer extends StdDeserializer<IAType> { private static final long serialVersionUID = 1L; public IATypeDeserializer() { this(null); } public IATypeDeserializer(Class<?> vc) { super(vc); } @Override public IAType deserialize(JsonParser parser, DeserializationContext context) throws IOException, JsonProcessingException { JsonNode node = parser.getCodec().readTree(parser); /* How to find out that the class calling the deserialization is InsuredAmountA, which has getProduct() method that returns PROD_A, and matches the IATypeA that also returns PROD_A, so I know to deserialize IATypeA, instead of other implementations of the IAType interface? */ return IATypeA.valueOf(node.asText()); } } 

-

 public class InsuredAmountTest { private final ObjectMapper mapper = new ObjectMapper(); @Test public void test01() throws IOException { InsuredAmount iaA = new InsuredAmount(IATypeA.FOO, "test it"); String json = mapper.writeValueAsString(iaA); assertThat(json, is("{\"type\":\"FOO\",\"someInfo\":\"test it\"}")); InsuredAmount iaA2 = mapper.readValue(json, InsuredAmount.class); IAType type = iaA2.getType(); assertThat(type, is(IATypeA.FOO)); assertThat(type.getProduct(), is(Product.PROD_A)); assertThat(iaA, is(iaA2)); } @Test public void test02() throws IOException { InsuredAmount iaA = new InsuredAmount(IATypeA.BAR, "test it"); String json = mapper.writeValueAsString(iaA); assertThat(json, is("{\"type\":\"BAR\",\"someInfo\":\"test it\"}")); InsuredAmount iaA2 = mapper.readValue(json, InsuredAmount.class); assertThat(iaA, is(iaA2)); } } 
+5
source share
3 answers

I ended up using the JsonCreator annotation for a custom constructor.

  @JsonCreator public InsuredAmountA( @JsonProperty("type") String type, @JsonProperty("someInfo") String someInfo) throws IOException { switch (getProduct()) { case PROD_A: try { this.type = IATypeA.valueOf(type); break; } catch (IllegalArgumentException ex) { // Throw IOException in the default. } // case PROD_B: // this.type = (IATypeB) typeA; // break; default: throw new IOException(String.format("Cannot parse value %s as type.", type)); } this.someInfo = someInfo; } 
+2
source

Jackson handles serialization of enumerations with minimal clutter, so all you have to do is annotate the IAType field using @JsonTypeInfo :

 @JsonTypeInfo(use = JsonTypeInfo.Id.MINIMAL_CLASS) private IAType type; 

Then test:

 public static void main(String[] args) throws IOException { ObjectMapper mapper = new ObjectMapper(); String json = mapper.writeValueAsString(new InsuredAmount(IATypeA.FOO, "info")); System.out.println(json); InsuredAmount ia = mapper.readValue(json, InsuredAmount.class); System.out.println("Type is: " + ia.getType()); } 

outputs the result:

 {"type":[".IATypeA","FOO"],"someInfo":"info"} Type is: FOO 

To get a more compact view, you have to use custom serialization. Assuming there are no matches in your enum namespace, you can serialize the type field as an enumeration name.

The deserializer should know what types are available for construction, either by detecting the class path, or, as in the following example, just hard-code the links:

 public class IATest { public static class IATypeSerializer extends JsonSerializer<IAType> { @Override public void serialize(IAType value, JsonGenerator gen, SerializerProvider serializers) throws IOException { gen.writeString(((Enum) value).name()); } } public static class IATypeDeserializer extends JsonDeserializer<IAType> { @Override public IAType deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { String value = p.readValueAs(String.class); try { return IATypeA.valueOf(value); } catch (IllegalArgumentException e) { // fall through } try { return IATypeB.valueOf(value); } catch (IllegalArgumentException e) { // fall through } throw new JsonMappingException(p, "Unknown type '" + value + "'"); } } public static void main(String[] args) throws IOException { ObjectMapper mapper = new ObjectMapper(); // Register a module to handle serialization of IAType implementations SimpleModule module = new SimpleModule(); module.addSerializer(IAType.class, new IATypeSerializer()); module.addDeserializer(IAType.class, new IATypeDeserializer()); mapper.registerModule(module); // Test String json = mapper.writeValueAsString(new InsuredAmount(IATypeA.FOO, "info")); System.out.println(json); InsuredAmount ia = mapper.readValue(json, InsuredAmount.class); System.out.println("Type is: " + ia.getType()); } } 

What outputs:

 {"type":"FOO","someInfo":"info"} Type is: FOO 
+3
source

You can study the direction of polymorphic deserialization:

http://wiki.fasterxml.com/JacksonPolymorphicDeserialization

non-standard type of recognizer

0
source

Source: https://habr.com/ru/post/1263778/


All Articles