6

我正在改编这个杰克逊代码:

@JsonDeserialize(as = EntityImpl.class)
public interface Entity { ... }

原始代码运行良好,即使对于嵌套的 Entity 对象也是如此。

如何对新的 json-b 规范做同样的事情?我尝试使用 @JsonbTypeDeserializer 但是

  1. 这真的是要走的路吗?它似乎缺乏仅指定一个类的简单性。
  2. 它似乎不适用于嵌套实体,这是我最大的问题:

    javax.json.bind.JsonbException:无法推断解组为的类型:实体

  3. 实体上未拾取注释。我必须使用 JsonbConfig::withDeserializers 手动添加。

这是我的反序列化代码:

public class EntityDeserializer implements JsonbDeserializer<Entity> {

    @Override
    public Entity deserialize(JsonParser parser, DeserializationContextdeserializationContext, Type runtimeType) {
        Class<? extends Entity> entityClass = EntityImpl.class.asSubclass(Entity.class);
        return deserializationContext.deserialize(entityClass, parser);
    }
}

非常感谢任何提示或帮助:-)

4

2 回答 2

11

JSON-B 没有声明序列化多态类型的标准方法。但是您可以使用自定义序列化器和反序列化器手动实现它。我将通过一个简单的示例对其进行解释。

想象一下,你有Shape接口和两个类SquareCircle实现它。

public interface Shape {
    double surface();
    double perimeter();
}

public static class Square implements Shape {
    private double side;

    public Square() {
    }

    public Square(double side) {
        this.side = side;
    }

    public double getSide() {
        return side;
    }

    public void setSide(double side) {
        this.side = side;
    }

    @Override
    public String toString() {
        return String.format("Square[side=%s]", side);
    }

    @Override
    public double surface() {
        return side * side;
    }

    @Override
    public double perimeter() {
        return 4 * side;
    }
}

public static class Circle implements Shape {
    private double radius;

    public Circle() {
    }

    public Circle(double radius) {
        this.radius = radius;
    }

    public double getRadius() {
        return radius;
    }

    public void setRadius(double radius) {
        this.radius = radius;
    }

    @Override
    public String toString() {
        return String.format("Circle[radius=%s]", radius);
    }

    @Override
    public double surface() {
        return Math.PI * radius * radius;
    }

    @Override
    public double perimeter() {
        return 2 * Math.PI * radius;
    }
}

您需要对可以包含任何Shape实现的 List 进行序列化和反序列化。

序列化开箱即用:

JsonbConfig config = new JsonbConfig().withFormatting(true);
Jsonb jsonb = JsonbBuilder.create(config);

// Create a sample list
List<SerializerSample.Shape> shapes = Arrays.asList(
            new SerializerSample.Square(2),
            new SerializerSample.Circle(5));

// Serialize
String json = jsonb.toJson(shapes);
System.out.println(json);

结果将是:

[
    {
        "side": 2.0
    },
    {
        "radius": 5.0
    }
]

没关系,但是如果您尝试反序列化它,它将无法正常工作。在反序列化时,JSON-B 需要创建Squareor的实例,Circle并且 JSON 文档中没有关于对象类型的信息。

为了修复它,我们需要在此处手动添加此信息。在这里,序列化器和反序列化器会有所帮助。我们可以创建一个序列化器,将一种类型的序列化对象放入 JSON 文档和反序列化器,读取它并创建一个适当的实例。可以这样做:

public static class ShapeSerializer implements JsonbSerializer<SerializerSample.Shape> {
    @Override
    public void serialize(SerializerSample.Shape shape, JsonGenerator generator, SerializationContext ctx) {
        generator.writeStartObject();
        ctx.serialize(shape.getClass().getName(), shape, generator);
        generator.writeEnd();
    }
}

public static class ShapeDeserializer implements JsonbDeserializer<SerializerSample.Shape> {
    @Override
    public SerializerSample.Shape deserialize(JsonParser parser, DeserializationContext ctx, Type rtType) {
        parser.next();

        String className = parser.getString();
        parser.next();

        try {
            return ctx.deserialize(Class.forName(className).asSubclass(Shape.class), parser);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
            throw new JsonbException("Cannot deserialize object.");
        }
    }
}

现在我们需要将它插入 JSON-B 引擎并尝试序列化。您不应该忘记在序列化/反序列化期间将泛型类型传递给 JSON-B 引擎。否则将无法正常工作。

// Create JSONB engine with pretty output and custom serializer/deserializer
JsonbConfig config = new JsonbConfig()
        .withFormatting(true)
        .withSerializers(new SerializerSample.ShapeSerializer())
        .withDeserializers(new SerializerSample.ShapeDeserializer());
Jsonb jsonb = JsonbBuilder.create(config);

// Create a sample list
List<SerializerSample.Shape> shapes = Arrays.asList(
        new SerializerSample.Square(2),
        new SerializerSample.Circle(5));

// Type of our list
Type type = new ArrayList<SerializerSample.Shape>() {}.getClass().getGenericSuperclass();

// Serialize
System.out.println("Serialization:");
String json = jsonb.toJson(shapes);
System.out.println(json);

序列化的结果将是:

[
    {
        "jsonb.sample.SerializerSample$Square": {
            "side": 2.0
        }
    },
    {
        "jsonb.sample.SerializerSample$Circle": {
            "radius": 5.0
        }
    }

]

您会看到对象类型是由添加的ShapeSerializer。现在让我们尝试反序列化它并打印结果:

// Deserialize
List<SerializerSample.Shape> deserializedShapes = jsonb.fromJson(json, type);

// Print results
System.out.println("Deserialization:");
for (SerializerSample.Shape shape : deserializedShapes) {
    System.out.println(shape);
}

结果是:

Square[side=2.0]
Circle[radius=5.0]

所以,它完美地工作。希望能帮助到你。:)

于 2017-09-07T10:52:01.230 回答
3

@Dmitry 给出的答案对我帮助很大,但它有两个缺陷:

1:使用 JSON 中的完整类名是一个严重的安全问题。攻击者可以让您反序列化任意类,并且某些类可以导致远程代码执行。您必须使用映射(或将允许的子类列入白名单)。例如:

[
    {
        "square": {
            "side": 2.0
        }
    },
    {
        "circle": {
            "radius": 5.0
        }
    }
]

2:在类型中包装实际对象可能不是我们希望 JSON 的样子。或者当我们从不同的系统接收 JSON 时,我们通常会得到不同的结构,例如带有@type字段。并且字段顺序在 JSON 中没有定义;生产者有时可能会发送@type最后一个。例如

[
    {
        "@type":"square",
        "side": 2.0
    },
    {
        "radius": 5.0,
        "@type":"circle"
    }
]

我找到的解决方案是这样的:

public class ShapeDeserializer implements JsonbDeserializer<Shape> {
    @Override public Shape deserialize(JsonParser parser, DeserializationContext ctx, Type rtType) {
        JsonObject value = parser.getObject();
        String type = value.getString("@type", "null");
        return JSONB.fromJson(value.toString(), classFor(type));
    }

    private Class<? extends Shape> classFor(String type) {
        switch (type) {
            case "circle":
                return Circle.class;
            case "square":
                return Square.class;
            default:
                throw new JsonbException("unknown shape type " + type);
        }
    }
}

请注意,从Parser光标前进;但我们需要重新读取完整的对象——记住:@type可能不是第一个字段。由于没有用于重置光标的 API,我通过调用生成一个新的 JSON 字符串toString并使用它来启动一个新的解析器。这并不完美,但性能影响应该是可以接受的。YMMV。

而且我很想看到这里讨论的 JSON-B 直接支持的多态类型。

于 2020-03-06T05:09:09.977 回答