我有一个有趣的问题,我在想出一个干净的解决方案时遇到麻烦。我的应用程序根据json本身的字段读取需要反序列化为该或该类类型的json对象的集合。我无法控制json结构或它如何到达我的应用程序。
我已经为可能要进入应用程序的每种类型的对象创建了模型,并且达到了要构建一个提取“类型”字段然后使用ObjectMapper反序列化的服务的地步。 json转换为适当的模型。
Json示例:
{
"message_type" : "model1"
"other data" : "other value"
...
}
型号:
public class Model1 {
...
}
public class Model2 {
...
}
服务?:
public class DynamicMappingService {
public ???? mapJsonToObject(String json) {
String type = pullTypeFromJson();
???
}
private String pullTypeFromJson() {...}
}
我不希望有大量的switch语句显示“如果类型值是这个,然后反序列化为那个”,但是我正在努力提出一些干净的方法。我认为也许是通用模型类,其中通用参数是Model Type,唯一字段是该模型类型的实例,但这似乎也不对,我不确定买什么。我还可以拥有所有模型都可以扩展的某种空抽象类,但这似乎也很可怕。我该如何处理?加分为例。
答案 0 :(得分:1)
您可以在此处使用访客模式:
class ScratchStackOverflowQuestion57102092 {
interface Reserializer {
void accept(Model1 model1);
void accept(Model2 model2);
}
interface Reserializeable {
void visit(Reserializer reserializer);
}
class Model1 implements Reserializeable {
@Override
public void visit(Reserializer reserializer) {
reserializer.accept(this);
}
}
class Model2 implements Reserializeable {
@Override
public void visit(Reserializer reserializer) {
reserializer.accept(this);
}
}
public class ReserializerImpl implements Reserializer {
@Override
public void accept(Model1 model1) {
//TODO: reserialize and push the new object somewhere
}
@Override
public void accept(Model2 model2) {
//TODO: reserialize and push the new object somewhere
}
}
public class JsonConversion {
//TODO: instantiate etc
private Reserializer reserializer;
public void handleJson(String json) {
//TODO: use some framework like Jackson which can read type hints from JSON fields
Reserializeable serialized = mapFromJson(json);
serialized.visit(reserializer);
}
}
}
这是一个有关如何完成所需目标的简化示例,但目前缺少以下功能:
因此,您可能需要对给定的代码进行一些调整:)
编辑:由于流行的需求,一个完整的实现可以通过另一个访问者动态处理重新序列化的对象(它只缺少在JSON中考虑类型提示的Jackson用法)。我为将近十二堂课道歉,没有捷径可走。有关如何使用此方法/如何为不同的已转换对象定义处理程序的信息,请参见函数exampleUsage()
:
class ScratchStackOverflowQuestion57102092_V2 {
//////////////////////////////// INPUTS //////////////////////////////
interface Reserializer {
void accept(Model1 model1);
void accept(Model2 model2);
}
interface Reserializeable {
void visit(Reserializer reserializer);
}
class Model1 implements Reserializeable {
@Override
public void visit(Reserializer reserializer) {
reserializer.accept(this);
}
}
class Model2 implements Reserializeable {
@Override
public void visit(Reserializer reserializer) {
reserializer.accept(this);
}
}
//////////////////////////////// RECONVERSION /////////////////////////
interface ReconvertedVisitor {
void accept(ReconvertedModel1 reconverted);
void accept(ReconvertedModel2 reconverted);
}
interface ReconvertedModel {
void visit(ReconvertedVisitor visitor);
}
//Some dummy object as an example
class ReconvertedModel1 implements ReconvertedModel{
@Override
public void visit(ReconvertedVisitor visitor) {
visitor.accept(this);
}
}
//Some dummy object as an example
class ReconvertedModel2 implements ReconvertedModel{
@Override
public void visit(ReconvertedVisitor visitor) {
visitor.accept(this);
}
}
////////////////////////////// IMPLEMENTATIONS ///////////////////////////////
public class ReserializerImpl implements Reserializer {
private final ReconvertedVisitor visitor;
public ReserializerImpl(ReconvertedVisitor visitor) {
this.visitor = visitor;
}
@Override
public void accept(Model1 model1) {
//TODO: do some real conversion
ReconvertedModel1 reserializeResult = new ReconvertedModel1();
}
@Override
public void accept(Model2 model2) {
//TODO: do some real conversion
ReconvertedModel2 reserializeResult = new ReconvertedModel2();
}
}
public class JsonConversion {
public void handleJson(String json, ReconvertedVisitor handler) {
//TODO: use some framework like Jackson which can read type hints from JSON fields
Reserializeable serialized = mapFromJson(json);
ReserializerImpl reserializer = new ReserializerImpl(handler);
serialized.visit(reserializer);
}
}
public void exampleUsage() {
//Just some sample, you could delegate to different objects in each accept
class PrintingReconvertedVisitor implements ReconvertedVisitor {
@Override
public void accept(ReconvertedModel1 reconverted) {
System.out.println(reconverted);
}
@Override
public void accept(ReconvertedModel2 reconverted) {
System.out.println(reconverted);
}
}
JsonConversion conversion = new JsonConversion();
conversion.handleJson("TODO: SOME REAL JSON HERE", new PrintingReconvertedVisitor());
}
}
我对类的命名不太满意,也许将Reserializer
重命名为ModelVisitor
或其他合适的名称。
答案 1 :(得分:1)
您在这里有两个不同的问题:创建一个未知类型的对象,然后对其进行有意义的处理。如果您创建的类型确实取决于json中节点的值,那么您将无法创建从该String到它将创建的类的映射。一个巨大的if-then-else块将仅在两到三个类之外才有用。
创作
您可以创建用于维护此映射的注册表单例。
public enum ClassByNodeMapping {
INSTANCE;
final Map<String, Class<?>> mapping = new HashMap<>();
public void addMapping(String nodeValue, Class<?> clazz) {
mapping.put(nodeValue, clazz);
}
public Class<?> getMapping(String nodeValue) {
return mapping.get(nodeValue);
}
}
您可以按课程填写:
@Data
class Model1 {
static {
ClassByNodeMapping.INSTANCE.addMapping("model1", Model1.class);
}
private String model1Value;
}
但是即使如此,您仍需要实例化Model1
一次才能注册(以确保已加载该类。因此,您的客户端代码应如下所示:
class Scratch {
static {
// need to instantiate the models once to trigger registration
Model1 model = new Model1();
Model2 model2 = new Model2();
}
private static final ObjectMapper mapper = new ObjectMapper()
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
public static void main(String[] args) throws IOException {
String json1 = "{\"type\": \"model1\", \"model1Value\": \"m1\"}";
String json2 = "{\"type\": \"model2\", \"model2Value\": \"m2\"}";
System.out.println(deserialize(json1));
System.out.println(deserialize(json2));
}
private static Object deserialize(String json) throws IOException {
JsonNode jsonNode = mapper.readTree(json);
Class<?> type = ClassByNodeMapping.INSTANCE.getMapping(jsonNode.get("type").textValue());
return mapper.readValue(json, type);
}
}
正在处理
现在您已经创建了对象,但是由于它们的类型为Object
,因此您可以用它们做很多事情。在对问题的评论中,您说过“重新序列化”之类的内容,我不确定这意味着什么;这里的一般概念是消耗-创建对象,然后对其进行处理。您可以在此处使用其他答案中的访客概念,但我觉得有些困惑。
您还可以通过添加处理程序来扩展映射。通常,您希望将两者分开,但是在这种情况下,可能需要保留类型安全性。
enum ClassByNodeMapping {
INSTANCE;
final Map<String, Class<?>> mapping = new HashMap<>();
final Map<Class<?>, Consumer<?>> handlerMapping = new HashMap<>();
public <T>void addMapping(String nodeValue, Class<T> clazz, Consumer<T> handler) {
mapping.put(nodeValue, clazz);
handlerMapping.put(clazz, handler);
}
public Class<?> getMapping(String nodeValue) {
return mapping.get(nodeValue);
}
public Consumer<Object> getHandler(Class<?> clazz) {
return (Consumer<Object>) handlerMapping.get(clazz);
}
}
这会使您的注册码类似
@Data
class Model1 {
static {
// you'd probably not want to do the registration here,
// assuming your handler code is outside
ClassByNodeMapping.INSTANCE.addMapping("model1", Model1.class, Model1::handle);
}
private String model1Value;
private static void handle(Model1 m1) {
System.out.printf("handling as model1: %s%n", m1);
}
}
以及您的(测试)客户端代码
public static void main(String[] args) throws IOException {
String json1 = "{\"type\": \"model1\", \"model1Value\": \"m1\"}";
String json2 = "{\"type\": \"model2\", \"model2Value\": \"m2\"}";
handle(json1);
handle(json2);
}
private static void handle(String json) throws IOException {
JsonNode jsonNode = mapper.readTree(json);
Class<?> type = ClassByNodeMapping.INSTANCE.getMapping(jsonNode.get("type").textValue());
Consumer<Object> handler = ClassByNodeMapping.INSTANCE.getHandler(type);
Object o = mapper.readValue(json, type);
handler.accept(o);
}
注意事项
我认为整个概念都是代码味道。我绝对会争取只包含一种类型数据的传入通道-无论是单独的REST端点,Kafka主题还是其他类型。
答案 2 :(得分:1)
我使用具有两个类Car和Truck的父接口Vehicle的概念。 在您的情况下,这意味着Model1和Model2应该实现一个公共接口。
我的考试班:
import com.fasterxml.jackson.databind.ObjectMapper;
public class Tester {
static ObjectMapper mapper=new ObjectMapper();
public static void main(String[] args) throws IOException {
Car car = new Car();
car.setModel("sedan");
String jsonCar=mapper.writeValueAsString(car);
System.out.println(jsonCar);
Vehicle c=mapper.readValue(jsonCar, Vehicle.class);
System.out.println("Vehicle of type: "+c.getClass().getName());
Truck truck=new Truck();
truck.setPower(100);
String jsonTruck=mapper.writeValueAsString(truck);
System.out.println(jsonTruck);
Vehicle t=mapper.readValue(jsonTruck, Vehicle.class);
System.out.println("Vehicle of type: "+t.getClass().getName());
}
}
您将需要在某个地方存储类型字段的值和相应类之间的映射。根据您希望的位置,实现方式会有所不同。
1)父类型保存子类型列表:
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
@JsonSubTypes({
@JsonSubTypes.Type(value = Car.class, name = "car"),
@JsonSubTypes.Type(value = Truck.class, name = "truck") }
)
@JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.PROPERTY,
property = "type")
public interface Vehicle {
}
汽车和卡车的模型是简单的POJO,没有任何注释:
public class Car implements Vehicle {
private String model;
public String getModel() {
return model;
}
public void setModel(String model) {
this.model = model;
}
}
2)一个单独的解析器保存该映射:
车辆包含额外的注释@JsonTypeIdResolver
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.databind.annotation.JsonTypeIdResolver;
@JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.PROPERTY,
property = "type")
@JsonTypeIdResolver(JsonResolver.class)
public interface Vehicle {
}
JsonResolver类保存类型字段值和类之间的映射:
import java.util.HashMap;
import java.util.Map;
import com.fasterxml.jackson.annotation.JsonTypeInfo.Id;
import com.fasterxml.jackson.databind.DatabindContext;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.jsontype.impl.TypeIdResolverBase;
public class JsonResolver extends TypeIdResolverBase {
private static Map<String,Class<?>> ID_TO_TYPE=new HashMap<>();
static {
ID_TO_TYPE.put("car",Car.class);
ID_TO_TYPE.put("truck",Truck.class);
}
public JsonResolver() {
super();
}
@Override
public Id getMechanism() {
return null;
}
@Override
public String idFromValue(Object value) {
return value.getClass().getSimpleName();
}
@Override
public String idFromValueAndType(Object value, Class<?> arg1) {
return idFromValue(value);
}
@Override
public JavaType typeFromId(DatabindContext context, String id) {
return context.getTypeFactory().constructType(ID_TO_TYPE.get(id));
}
}
3)json包含完整的类名:
如果您接受序列化的json拥有完整的Java类名称,则不需要解析器,而是指定use = JsonTypeInfo.Id.CLASS
:
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.databind.annotation.JsonTypeIdResolver;
@JsonTypeInfo(
use = JsonTypeInfo.Id.CLASS,
include = JsonTypeInfo.As.PROPERTY,
property = "type")
public interface Vehicle {
}
解决方案3是最容易实现的方法,但就我个人而言,我不希望在数据中包含完整的Java类名称。如果您开始重构Java软件包,则可能存在潜在风险。
答案 3 :(得分:0)
为此,您需要Factory Method或/和Abstract Factory。