如何将 @RestController 中的请求主体转换为抽象值列表?
How do I convert request body in a @RestController to a List of abstract values?
假设我们有以下 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;
}
}
以及以下@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);
}
}
以及请求正文中的以下 json:
[
{"investmentType":"Bond"},
{"investmentType":"Equity"}
]
如何在不使用 Jackson 的 @JsonSubTypes
摘要 class Investment
的情况下将 json 绑定或转换为 List<Investment>
的请求主体,而是使用 InvestmentFactory
?
如果可以使用@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);
}
}
示例控制器:
@RestController
public class MyController {
@RequestMapping("/")
public List<Class<?>> update(@RequestBody List<Investment> investments) {
return investments.stream().map(Object::getClass).collect(Collectors.toList());
}
}
测试:
$ curl localhost:8080 -H "Content-Type: application/json" -d '[{"investmentType":"Bond"},{"investmentType":"Equity"}]'
输出:
["com.example.demo.Bond","com.example.demo.Equity"]
@JsonDeserialize 效果很好,但如果您的字段不仅仅是类型,那么您将不得不手动设置它们。如果你要回到杰克逊,你可以使用:
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());
}
这样做的美妙之处在于,如果您开始向 Investment 添加字段,则不必将它们添加到 Desrializer 中(至少,在我的情况下,这发生在我身上),而是 Jackson 会处理它为你。所以明天你可以有测试用例:
'[{"investmentType":"Bond","investmentName":"ABC"},{"investmentType":"Equity","investmentName":"APPL"}]'
你应该可以开始了!
假设我们有以下 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;
}
}
以及以下@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);
}
}
以及请求正文中的以下 json:
[
{"investmentType":"Bond"},
{"investmentType":"Equity"}
]
如何在不使用 Jackson 的 @JsonSubTypes
摘要 class Investment
的情况下将 json 绑定或转换为 List<Investment>
的请求主体,而是使用 InvestmentFactory
?
如果可以使用@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);
}
}
示例控制器:
@RestController
public class MyController {
@RequestMapping("/")
public List<Class<?>> update(@RequestBody List<Investment> investments) {
return investments.stream().map(Object::getClass).collect(Collectors.toList());
}
}
测试:
$ curl localhost:8080 -H "Content-Type: application/json" -d '[{"investmentType":"Bond"},{"investmentType":"Equity"}]'
输出:
["com.example.demo.Bond","com.example.demo.Equity"]
@JsonDeserialize 效果很好,但如果您的字段不仅仅是类型,那么您将不得不手动设置它们。如果你要回到杰克逊,你可以使用:
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());
}
这样做的美妙之处在于,如果您开始向 Investment 添加字段,则不必将它们添加到 Desrializer 中(至少,在我的情况下,这发生在我身上),而是 Jackson 会处理它为你。所以明天你可以有测试用例:
'[{"investmentType":"Bond","investmentName":"ABC"},{"investmentType":"Equity","investmentName":"APPL"}]'
你应该可以开始了!