简体   繁体   中英

How do I convert request body in a @RestController to a List of abstract values?

Let's say we have the following classes:

public abstract class Investment {

   private String investmentType;

   // getters & setters
}

public class Equity extends Investment {
}

public class Bond extends Investment {
}

public class InvestmentFactory {

    public static Investment getTypeFromString(String investmentType) {
        Investment investment = null;
        if ("Bond".equals(investmentType)) {
            investment = new Bond();
        } else if ("Equity".equals(investmentType)) {
            investment = new Equity();
        } else {
            // throw exception
        }
        return investment;
    }
}

And the following @RestController :

@RestController
public class InvestmentsRestController {

    private InvestmentRepository investmentRepository;

    @Autowired
    public InvestmentsRestController(InvestmentRepository investmentRepository) {
        this.investmentRepository = investmentRepository;
    }

    @RequestMapping(RequestMethod.POST)
    public List<Investment> update(@RequestBody List<Investment> investments) {
       return investmentRepository.update(investments);
    }

}

And the following json in the request body:

[
  {"investmentType":"Bond"},
  {"investmentType":"Equity"}
]

How can I bind or convert the json to a request body of List<Investment> without using Jackson's @JsonSubTypes on abstract class Investment , and instead use the InvestmentFactory ?

@JsonDeserialize works great, but if you have more fields than just the type then you will have to set them all manually. If you were to go back to Jackson, you can use:

Investment.class

    @JsonTypeInfo(
            use = JsonTypeInfo.Id.NAME,
            include = JsonTypeInfo.As.PROPERTY,
            property = "investmentType")
    @JsonTypeIdResolver(InvestmentResolver.class)
    public abstract class Investment {
    } 

InvestmentResolver.class

public class InvestmentResolver extends TypeIdResolverBase {

    @Override
    public JavaType typeFromId(DatabindContext context, String id) throws IOException {
        Investment investment = InvestmentFactory.getTypeFromString(type);
        return context.constructType(investment.getClass());
    }

The beauty of this is that if you start adding fields to Investment you won't have to add them in the Desrializer (at least, this happened to me in my case), but instead Jackson will take care of it for you. So tomorrow you can have the test case:

'[{"investmentType":"Bond","investmentName":"ABC"},{"investmentType":"Equity","investmentName":"APPL"}]'

You should be good to go!

If you can use @JsonDeserialize :

@JsonDeserialize(using = InvestmentDeserializer.class)
public abstract class Investment {
}



public class InvestmentDeserializer extends JsonDeserializer<Investment> {
    @Override
    public Investment deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
        ObjectMapper objectMapper = (ObjectMapper) p.getCodec();
        TreeNode node = objectMapper.readTree(p);

        TreeNode investmentNode = node.get("investmentType");
        String type = objectMapper.readValue(investmentNode.traverse(objectMapper), String.class);
        return InvestmentFactory.getTypeFromString(type);
    }
}

Example controller:

@RestController
public class MyController {
    @RequestMapping("/")
    public List<Class<?>> update(@RequestBody List<Investment> investments) {
        return investments.stream().map(Object::getClass).collect(Collectors.toList());
    }
}

Testing:

$ curl localhost:8080 -H "Content-Type: application/json" -d '[{"investmentType":"Bond"},{"investmentType":"Equity"}]'

Output:

["com.example.demo.Bond","com.example.demo.Equity"]

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM