从 json 动态选择要创建对象的类

b15*_*b15 5 java json design-patterns

我有一个有趣的问题,我在想出一个干净的解决方案时遇到了麻烦。我的应用程序读取 json 对象的集合,它需要根据 json 本身中的字段将其反序列化为这个或那个类类型。我无法控制 json 结构或它如何到达我的应用程序。

我已经为可能进入应用程序的每种类型的对象创建了模型,并且我已经到了尝试构建一个服务的地步,该服务拉出“类型”字段,然后使用 ObjectMapper 将 json 反序列化为合适的型号。

JSON 示例:

{
    "message_type" : "model1"
    "other data" : "other value"
    ...
}
Run Code Online (Sandbox Code Playgroud)

楷模:

public class Model1 {
    ...
}

public class Model2 {
    ...
}
Run Code Online (Sandbox Code Playgroud)

服务?:

public class DynamicMappingService {

    public ???? mapJsonToObject(String json) {
        String type = pullTypeFromJson();

        ???
    }

    private String pullTypeFromJson() {...}
}
Run Code Online (Sandbox Code Playgroud)

我不想要一个大量的 switch 语句,它说“如果类型值是这个,那么反序列化到那个”,但我正在努力想出一些干净的东西来做到这一点。我想可能是一个通用模型类,其中通用参数是模型类型,唯一的字段是该模型类型的实例,但这似乎也不对,我不确定这对我有什么好处。我也可以有一些所有模型都扩展的空抽象类,但这看起来也很糟糕。我该如何处理?举例加分。

Con*_*ion 4

我使用父接口Vehicle的概念,其中包含 2 个类CarTruck。在您的情况下,这意味着Model1Model2应该实现一个通用接口。

我的测试课:

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());
    }
}
Run Code Online (Sandbox Code Playgroud)

您需要在某个地方存储类型字段的值和相应类之间的映射。根据您想要的位置,实施方式有所不同。

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 {
}
Run Code Online (Sandbox Code Playgroud)

汽车卡车的模型是简单的 POJO,没有任何注释:

public class Car implements Vehicle {
    private String model;

    public String getModel() {
        return model;
    }

    public void setModel(String model) {
        this.model = model;
    }
}
Run Code Online (Sandbox Code Playgroud)

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 {
}
Run Code Online (Sandbox Code Playgroud)

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));
    }
}
Run Code Online (Sandbox Code Playgroud)

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 {
}
Run Code Online (Sandbox Code Playgroud)

解决方案 3是最容易实现的,但我个人不喜欢在数据中包含完整的 java 类名。如果您开始重构 java 包,这可能是一个潜在的风险。