Search code examples
javaspring-bootjackson-databind

Use abstract class with x-www-form-urlencoded content-type


I have an abstract class

public abstract class AbstractClazz {

    private String discriminator;

    // getter, setter
}

and I have multiple implementations of this class, one of them could look like this

public class ConcreteClazz {

    private String value;

    // getter, setter
}

also I have an endpoint that consumes x-www-form-urlencoded content type only.

@PostMapping(consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
public ResponseEntity<Void> test(AbstractClazz abstractClazz) {
    System.out.println(abstractClazz);
    return ResponseEntity.ok().build();
}

What I want to achieve is to deserialize to a concrete class based on the discriminator parameter in AbstractClazz. I've tried using jackson @JsonSybTypes and @JsonTypeInfo annotations on the abstract class but it only works if the payload comes as json in request body and not as x-www-form-urlencoded content.

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "discriminator")
@JsonSubTypes(@JsonSubTypes.Type(value = ConcreteClazz.class, name = "concrete"))
public abstract class AbstractClazz {

    private String discriminator;

    // getter, setter
}

Is there a way how to achieve the same behavior with x-www-form-urlencoded content type?
The error I get is

Request processing failed: org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.example.AbstractClazz]: Is it an abstract class?] with root cause

java.lang.InstantiationException: null

Solution

  • You can use the RequestParam annotation that combines query parameters and form data into a single map called "parameters", and that includes automatic parsing of the request body like below:

    public ResponseEntity<Void> test(@RequestParam Map<String, String> parameters) {}
    

    Then you can use an ObjectMapper instance to convert your Map<String, String> parameters to a subclass of your AbstractClazz depending from the value of the discriminator key obtaining the expected result:

    @PostMapping(consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
    public ResponseEntity<Void> test(@RequestParam Map<String, String> parameters) {
        ObjectMapper mapper = new ObjectMapper();
        AbstractClazz clazz = mapper.convertValue(parameters, AbstractClazz.class); 
        return ResponseEntity.ok().build();
    }