使用带有字段依赖的 Jackson 反序列化 JSON

Posted

技术标签:

【中文标题】使用带有字段依赖的 Jackson 反序列化 JSON【英文标题】:Deserialize JSON with Jackson with field dependency 【发布时间】:2017-04-04 10:14:31 【问题描述】:

在我们的项目中,我们使用 Jackson 解析 JSON。我们通过字段channelId 设置字段saved。问题是channelId 字段的解析晚于saved。所以当时我们要设置字段saved 字段channelId 为null。 JSON反序列化中如何设置字段依赖,使saved字段设置在channelId之后?

这是我们 JSON 数据的一部分:

"message":  
            "data":  
               "text":"Some text"
            ,
            "saved_by":[  
               2715,
               1234
            ],
            "some_boolean_field":false,
            "channel_id":8162
           

这是我们的实体类:

@JsonIgnoreProperties(ignoreUnknown = true)
@org.parceler.Parcel(org.parceler.Parcel.Serialization.BEAN)

public class Message 

    @JsonProperty("channel_id")
    protected long channelId;

    protected boolean saved;

    @JsonSetter("saved_by")
    public void setSavedBy(Set<Long> savedBy) 
        saved = savedBy.contains(getUserIdByChannelId(channelId));
    

    public long getChannelId() 
        return channelId;
    

    public void setChannelId(long channelId) 
        this.channelId = channelId;
    

    public boolean isSaved() 
        return saved;
    

    public void setSaved(boolean saved) 
        this.saved = saved;
    

    public void setData(JsonNode data) throws JsonProcessingException 
        JsonNode textNode = data.get("text");
        text = textNode != null ? textNode.asText() : "";

        components = new ArrayList<>();
        JsonNode mediaNode = data.get("media");
        if (mediaNode != null) 
            MessageComponent[] parsedComponents = AppSession.getInstance().getObjectMapper().treeToValue(mediaNode, MessageComponent[].class);
            List<MessageComponent> components = Arrays.asList(parsedComponents).subList(0, parsedComponents.length < 4 ? parsedComponents.length : 4);

            this.components.addAll(components);
        

        mediaCount = components.size();
    

    

完整的 JSON:

  
   "data":  
      "serial":66,
      "updated_entity":"bookmark",
      "bookmark":  
         "message":  
            "data":  
               "text":"hello"
            ,
            "counted_serial":748,
            "saved_by":[  
               26526,
               27758
            ],
            "type":"UserMessage",
            "is_reviewed":false,
            "channel_id":8128,
            "id":2841531,
            "replied_message_data":null,
            "is_blocked":false,
            "is_deleted":false,
            "updated_at":"2016-11-21T05:59:52.471Z",
            "spam_reported_by":[  

            ],
            "created_at":"2016-11-19T15:40:17.027Z",
            "uuid":"0b6ba58e-f5e1-4ee5-a9da-041dfc2c85cd",
            "liked_by":[  

            ],
            "user":  
               "last_name":"M",
               "id":4537,
               "first_name":"John",
               "is_deleted":false,
               "avatar_thumb":"https:\/\/cdn.site.org\/uploads\/99ef4d68-6eaf-4ba6-aafa-74d1cf895d71\/thumb.jpg"
            ,
            "serial":934
         ,
         "id":6931,
         "created_at":"2016-11-21T05:59:52.459Z",
         "is_deleted":false,
         "updated_at":"2016-11-21T05:59:52.459Z"
      
   ,
   "type":"action_performed"

【问题讨论】:

是否可以从构造函数而不是通过设置字段来重建它? 如果saved 字段总是依赖于channelId,那么为什么不在setChannelId() 中设置saved 字段呢? 不保证 JSON 顺序。例如,在某些手机中我得到了正确的字段顺序,在其他手机中顺序不同。 setChannelId 我们可能还没有saved_by 值。 显示完整的 json 文件... 【参考方案1】:

这有点老套,但是通过将 Message 类设为自己的反序列化构建器,您将获得一种“准备创建 bean”的事件,您可以在其中访问所有属性。

我的建议是您尝试以下方法:

@JsonDeserialize(builder = Message.class)
public class Message 

    ...

    @JsonSetter("saved_by")
    public void setSavedBy(Set<Long> savedBy) 
        // Merely store the value for later use.
        this.savedBy = savedBy;
    

    ...

    public Message build() 
        // Calculate value of "saved" field.
        this.saved = this.savedBy.contains(getUserIdByChannelId(this.channelId));
        return this;
    

    // Handling the added challenge.
    @JsonProperty("data") 
    public void setData(JsonNode data) throws JsonProcessingException 
       ...
    

上面利用了JsonPOJOBuilder注解的默认设置,即buildMethodName的默认值为build

【讨论】:

我试过你的代码,但对象没有设置所有字段。我需要在build()方法中设置所有字段吗? 不,只要有setter,就应该使用。您是否尝试设置几个断点以查看它们是否被命中? 我刚刚重新运行了我的沙盒测试,它们确实有效。您是否看到任何错误?反序列化使用如下:Message message = om.readValue(json, Message.class); ? 是的,我们像这样反序列化。我添加了我们在此类中的setData() 方法,当我添加您的方法build(). 后,它会导致问题。我们使用它从 JSON 中的data 字段设置多个参数 在方法中添加@JsonProperty("data") 似乎可以解决您添加的问题。请尝试一下。

以上是关于使用带有字段依赖的 Jackson 反序列化 JSON的主要内容,如果未能解决你的问题,请参考以下文章

Java Jackson将空字段反序列化为POJO中的默认空列表

Jackson 将日期字符串反序列化为 Long

spring/jackson:实现对保存JSON字符串的字段自动序列化和反序列化

使用 Jackson 反序列化:获取 Json 对象设置的字段列表

如何使用 Jackson json 注释枚举字段以进行反序列化

带有 Jackson 的不可变 Lombok 注释类