我正在try 设计一个Java类来保存反序列化JSON格式的消息的结果.这条消息的 struct 很简单:

{
    "appId": "MyApp",
    "action": "Upsert",
    "entityType": "Property",
    "entity": {
        "marketId":   "abc123",
        "propertyId": "def456",
        "name":       "Grand Central Station",
        "hasFoo":     true,
        "hasBar":     false,
        "areaInSf":   30,000
        ... more key-value pairs...
    }
}

entity中,ID和名称字段是必需的,并且它们的名称是已知的.我可能还想反序列化任意数量的其他属性--它们是可选的,而且我事先不知道所有的名称.

如果我只处理已知的字段,我可以创建一个Java模型,如下所示

public class Message {
    public String appId;
    public String action;
    public String entityType;
    public Entity entity;
}

public class Entity {
    public String marketId;
    public String propertyId;
    public String name;
}

并使用Jackson反序列化JSON,如下所示:

ObjectMapper om = new ObjectMapper();
Message myMessage = om.readValue(myJsonString, Message.class);

但是我该如何处理消息中的可选字段呢?它们是ID和NAME字段的平面sibling ,而不是嵌套映射,因此我不能这样做

public class Entity {
    public String marketId;
    public String propertyId;
    public String name;
    public Map<String, Object> otherFields;  // hasFoo, hasBar, areaInSf ...
}

我更喜欢强类型的Entity,这样我就可以验证所需字段的各个方面--ID的最小长度等.否则,Entity可能只是Map<String, Object>.要获取这些字段,我只需循环遍历Map的属性.但我希望能够按名称提取所需的字段,并在可能的情况下只循环剩余的字段.

如何设计实体POJO,以便可以反序列化类定义中声明的两个字段,以及输入JSON中的一些额外字段?

推荐答案

您的设计已经准备好了.您需要的是它的定制反序列化程序.这里的 comments 链接是一篇很好的文章Custom Deserialization

将这篇文章翻译成一段代码应该是这样的(note这可能是不可行的,用作参考,因为我没有测试它):

public class MessageDeserializer extends StdDeserializer<Message> {

    public MessageDeserializer() {
        super(Message.class);
    }

    @Override
    public Message deserialize(JsonParser jp, DeserializationContext ctxt) {
        try {
            JsonNode node = jp.getCodec().readTree(jp);
            Message message = new Message();

            // your known fields
            message.setAppId(node.get("appId").asText());
            message.setAction(node.get("action").asText());
            // .... other fields

            Entity entity = new Entity();
            entity.setMarketId(node.get("entity").get("marketId").asText());
            entity.setPropertyId(node.get("entity").get("propertyId").asText());
            // .... other fields

            Map<String, Object> otherFields = new HashMap<>();
            JsonNode entityNode = node.get("entity");
            final List<String> knownEntityFields = List.of("marketId", "propertyId" /*, others here*/);
            entityNode.fields().forEachRemaining(entry -> {
                String fieldName = entry.getKey();
                if ( knownEntityFields.stream().noneMatch(fieldName) ) {
                    otherFields.put(fieldName, entry.getValue());
                }
            });
            entity.setOtherFields(otherFields);

            message.setEntity(entity);
            return message;
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

您甚至可以通过使用反射来改进它(有些人可能不同意:)

Java相关问答推荐

如何让HikariCP指标在NewRelic中正确显示?

如何为具有多对多关系的实体的给定SQL查询构建JPA规范?

在Java中将Charsequence数组更改为String数组或List String<>

Spring Batch 5-不要让它在数据库中自动创建表

XPages-在第二次点击按钮之前延迟

如何找到MongoDB文档并进行本地化?

无法使用Java&;TestContainers获取AWS SQS队列的属性

第三方Jar pom.xml

OpenGL ES 3.0-纹理黑色

如何集成语义发布和BitBucket(Java项目)

如何将Pane的图像快照保存为BMP?

如果List是一个抽象接口,那么Collectors.toList()如何处理流呢?

使IntelliJ在导入时优先 Select 一个类或将另一个标记为错误

我的代码是线程安全的吗?[Java、CAS、转账]

为什么mvn编译生命周期阶段不只是编译已更改的java文件?

为什么没有加载java.se模块?

Java类型推断:为什么要编译它?

rest api服务 spring 启动中出现IllegalFormatConversionException

如何在特定关键字后提取与模式匹配的多个值?

SonarQube在合并升级到java17后对旧代码提出错误