给出如此的模型层次结构:
// WARNING: This is pseudo-code for giving an example!
public abstract class BaseVehicle {
private String make;
private String model;
// Constructors, getters & setters down here
}
public class Motorcycle extends BaseVehicle {
private int numCylinders;
// Constructors, getters & setters down here
}
public class Car extends BaseVehicle {
// ...etc.
}
并给出以下有效负载类(将被发送到Spring控制器):
public class Payload {
@JsonIgnore
@JsonProperty(value = "orgId")
private String orgId;
@JsonIgnore
@JsonProperty(value = "isInitialized")
private Boolean isInitialized;
@JsonIgnore
@JsonProperty(value = "vehicle")
private BaseVehicle vehicle;
// Constructors, getters & setters down here
}
我想知道是否有可能让Spring控制器(使用Jackson进行JSON序列化)配置为只接收它收到的BaseVehicle
中的Payload
个实例,但要动态推断实际发送了BaseVehicle
个子类:
@RequestMapping(value='/payload', method=RequestMethod.POST)
@ResponseStatus(value = HttpStatus.OK)
@ResponseBody MyAppResponse onPayload(@RequestBody Payload payload) {
logger.info("Received a payload with a vehicle of type: " + payload.getVehicle().getClass().getName());
}
因此,如果我碰巧发送包含Payload
作为Motorcycle
字段的vehicle
JSON,那么当logger.info(...)
语句触发时,代码会看到{ {1}}是vehicle
(和其他任何Motorcycle
子类一样)?
这是可能的,如果是这样,怎么办?
答案 0 :(得分:1)
但是我非常喜欢允许JSON保持原样的解决方案。
正如我在上面的评论中提到的,您可以分析有效载荷工具JSON对象树,以便进行一些分析,尝试检测有效载荷元素类型。
@JsonDeserialize(using = BaseVehicleJsonDeserializer.class)
abstract class BaseVehicle {
@JsonProperty
private String make;
@JsonProperty
private String model;
}
@JsonDeserialize(as = Car.class)
final class Car
extends BaseVehicle {
}
@JsonDeserialize(as = Motorcycle.class)
final class Motorcycle
extends BaseVehicle {
@JsonProperty
private int numCylinders;
}
这里的诀窍是@JsonDeserialize
注释。 BaseVehicleJsonDeserializer
可以按如下方式实施:
final class BaseVehicleJsonDeserializer
extends JsonDeserializer<BaseVehicle> {
@Override
public BaseVehicle deserialize(final JsonParser parser, final DeserializationContext context)
throws IOException {
final TreeNode treeNode = parser.readValueAsTree();
final Class<? extends BaseVehicle> baseVehicleClass = Stream.of(treeNode)
// Check if the tree node is ObjectNode
.filter(tn -> tn instanceof ObjectNode)
// And cast
.map(tn -> (ObjectNode) tn)
// Now "bind" the object node with if the object node can be supported by the resolver
.flatMap(objectNode -> Stream.of(BaseVehicleTypeResolver.cachedBaseVehicleTypeResolvers).filter(resolver -> resolver.matches(objectNode)))
// If found, just get the detected vehicle class
.map(BaseVehicleTypeResolver::getBaseVehicleClass)
// Take the first resolver only
.findFirst()
// Or throw a JSON parsing exception
.orElseThrow(() -> new JsonParseException(parser, "Cannot parse: " + treeNode));
// Convert the JSON tree to the resolved class instance
final ObjectMapper objectMapper = (ObjectMapper) parser.getCodec();
return objectMapper.treeToValue(treeNode, baseVehicleClass);
}
// Known strategies here
private enum BaseVehicleTypeResolver {
CAR_RESOLVER {
@Override
protected Class<? extends BaseVehicle> getBaseVehicleClass() {
return Car.class;
}
@Override
protected boolean matches(final ObjectNode objectNode) {
return !objectNode.has("numCylinders");
}
},
MOTORCYCLE_RESOLVER {
@Override
protected Class<? extends BaseVehicle> getBaseVehicleClass() {
return Motorcycle.class;
}
@Override
protected boolean matches(final ObjectNode objectNode) {
return objectNode.has("numCylinders");
}
};
// Enum.values() returns array clones every time it's invoked
private static final BaseVehicleTypeResolver[] cachedBaseVehicleTypeResolvers = BaseVehicleTypeResolver.values();
protected abstract Class<? extends BaseVehicle> getBaseVehicleClass();
protected abstract boolean matches(ObjectNode objectNode);
}
}
正如您所看到的,这种方法或多或少是脆弱和复杂的,但它试图进行一些分析。现在,如何使用它:
final ObjectMapper mapper = new ObjectMapper();
Stream.of(
"{\"orgId\":\"foo\",\"isInitialized\":true,\"vehicle\":{\"make\":\"foo\",\"model\":\"foo\"}}",
"{\"orgId\":\"bar\",\"isInitialized\":true,\"vehicle\":{\"make\":\"bar\",\"model\":\"bar\",\"numCylinders\":4}}"
)
.map(json -> {
try {
return mapper.readValue(json, Payload.class);
} catch ( final IOException ex ) {
throw new RuntimeException(ex);
}
})
.map(Payload::getVehicle)
.map(BaseVehicle::getClass)
.forEach(System.out::println);
输出:
class q43138817.Car
class q43138817.Motorcycle