java - 让 Spring/Jackson 智能地反序列化模型子类

标签 java json spring jackson

给定一个像这样的模型层次结构:

// 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 Controller ):

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 Controller (使用 Jackson 进行 JSON 序列化)配置为仅期望它接收的 Payload 中的 BaseVehicle 实例,但是动态推断实际发送的是哪个 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());
}

这样,如果我碰巧发送一个包含 Motorcycle 作为其 vehicle 字段的 Payload JSON,那么当该 logger .info(...) 语句触发,代码看到 vehicleMotorcycle(对于任何其他 BaseVehicle 也是如此)子类)?

这可能吗?如果可能的话,如何实现?

最佳答案

However I'd greatly prefer a solution that allows the JSON to remain as-is.

正如我在上面的评论中提到的,您可以分析有效负载车辆 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

关于java - 让 Spring/Jackson 智能地反序列化模型子类,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/43138817/

相关文章:

spring - org.hibernate.PersistentObjectException : detached entity passed to persist with H2 in memory database

java - Grails java.lang.NoClassDefFoundError : org/springframework/mock/web/MockHttpServletRequest

javascript - 创建多维数组和JSON解析

c++ - 如何在 C++ 中使用 casablanca 获取 JSON 对象值

java - 当 Intent 返回我的 Activity 时,在回收 View 中显示两倍的相同数据

java - 如果socketaccept导致异常,如何找出原因?

javascript - 如何在 html 中将 JSON 显示为格式化 View

java - JPA:在单个持久性单元上映射多个 Oracle 用户

java - 如何确认不同的结果是否是由于 float 处理的差异?

java - 更好地理解 Java 概念 : File , 异常处理