为了账号安全,请及时绑定邮箱和手机立即绑定

Jackson反序列化SNS消息错误MismatchedInputException

Jackson反序列化SNS消息错误MismatchedInputException

叮当猫咪 2023-09-20 15:21:37
我正在编写一个通过 SNS HTTP 请求处理来自 Amazon Simple Email Service 的回调的功能。我想将亚马逊提供的消息解析为本地对象结构。问题是 SNS 将 JSON 消息包装成字符串,并且 Jackson 无法解析它。我收到错误:com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot construct instance of `xxx.email.domain.aws.ses.Notification` (although at least one Creator exists): no String-argument constructor/factory method to deserialize from String value ('{"notificationType":"Delivery","mail":{"timestamp":"2019-10-02T14:43:14.570Z" ... next values of the message ... }}')来自 SNS 的整条消息如下所示: {  "Type" : "Notification",  "MessageId" : "4944xxxx-711d-57d4-91b8-8215cxxxxx",  "TopicArn" : "arn:aws:sns:eu-west-1:...",  "Message" : "{\"notificationType\":\"Delivery\",\"mail\":{\"timestamp\":\"2019-10-02T14:43:14.570Z\", ... next values of the message ... },\"delivery\":{\"timestamp\":\"2019-10-02T14:43:16.030Z\", ... next values of the message ... }}",  "Timestamp" : "2019-10-02T14:43:16.062Z",  "SignatureVersion" : "1",  "Signature" : "signature base64",  "SigningCertURL" : "cert url",  "UnsubscribeURL" : "unsubscribe url"}我的实际本地结构如下所示:@Data@JsonNaming(PropertyNamingStrategy.UpperCamelCaseStrategy.class)public class MessageWrapper {    private String type;    private String messageId;    private String topicArn;    private Notification message;    private Date timestamp;    private String signatureVersion;    private String signature;    private String signingCertURL;    private String unsubscribeURL;}@Datapublic class Notification {    private String notificationType;    private Mail mail;}@Datapublic class Mail {    private String messageId;    private String source;    private String sourceArn;    private String sourceIp;    private String sendingAccountId;    private String[] destination;}我正在寻找某种方法来告诉 JacksonMessage应该从字符串中提取并视为普通的 JSON。编辑反序列化private MessageWrapper deserializeMessage(String message) throws IOException {    return new ObjectMapper().readValue(message, MessageWrapper.class);}
查看完整描述

2 回答

?
天涯尽头无女友

TA贡献1831条经验 获得超9个赞

我认为要解决这个问题,您需要一个用于类Notification中字段的自定义反序列化器MessageWrapper以及一个用于类Mail中字段的自定义反序列化器Notification,如下所示:


public class NotificationDeserializer extends JsonDeserializer<Notification> {

    @Override

    public Notification deserialize(JsonParser p, DeserializationContext ctxt)

            throws IOException, JsonProcessingException {

        String text = p.getText();


        return new ObjectMapper().readValue(text, Notification.class);

    }

}


public class MailDeserializer extends JsonDeserializer<Mail> {

    @Override

    public Mail deserialize(JsonParser p, DeserializationContext ctxt)

            throws IOException, JsonProcessingException {

        String text = p.getText();


        return new ObjectMapper().readValue(text, Mail.class); 

    }

}

在您的类上添加一些注释,如下所示:


@Data

@JsonNaming(PropertyNamingStrategy.UpperCamelCaseStrategy.class)

public class MessageWrapper {

    private String type;

    private String messageId;

    private String topicArn;

    @JsonDeserialize(using = NotificationDeserializer.class)

    private Notification message;

    private Date timestamp;

    private String signatureVersion;

    private String signature;

    private String signingCertURL;

    private String unsubscribeURL;

}


@Data

public class Notification {

    private String notificationType;

    @JsonDeserialize(using = MailDeserializer.class)

    private Mail mail;

}


@Data

public class Mail {

    private String messageId;

    private String source;

    private String sourceArn;

    private String sourceIp;

    private String sendingAccountId;

    private String[] destination;

}

编辑1


实际上并不MailDeserializer需要。独自NotificationDeserializer解决这个问题。


编辑2


在自定义解串器中使用新的ObjectMapper是必须的。


查看完整回答
反对 回复 2023-09-20
?
慕雪6442864

TA贡献1812条经验 获得超5个赞

messageproperty 的类型为 type Notification,但Jackson预期JSON Object不是string value。在这种情况下,您可以创建自定义反序列化器或通过某种环回实现来实现通用解决方案。如果给定的有效负载不是 aJSON Object将其读取为 aString并使用 this 再次调用反序列化String。


为了避免StackOverflowError您需要使用另一个实例ObjectMapper或使用BeanDeserializerModifier保留BeanDeserializer实例并在遇到的地方使用它JSON Object。简单的例子如下所示:


import com.fasterxml.jackson.core.JsonParser;

import com.fasterxml.jackson.core.JsonToken;

import com.fasterxml.jackson.databind.BeanDescription;

import com.fasterxml.jackson.databind.DeserializationConfig;

import com.fasterxml.jackson.databind.DeserializationContext;

import com.fasterxml.jackson.databind.DeserializationFeature;

import com.fasterxml.jackson.databind.JsonDeserializer;

import com.fasterxml.jackson.databind.ObjectMapper;

import com.fasterxml.jackson.databind.PropertyNamingStrategy;

import com.fasterxml.jackson.databind.annotation.JsonNaming;

import com.fasterxml.jackson.databind.deser.BeanDeserializer;

import com.fasterxml.jackson.databind.deser.BeanDeserializerBase;

import com.fasterxml.jackson.databind.deser.BeanDeserializerModifier;

import com.fasterxml.jackson.databind.module.SimpleModule;

import com.fasterxml.jackson.databind.node.TextNode;

import lombok.Data;

import lombok.ToString;


import java.io.File;

import java.io.IOException;

import java.util.Collections;

import java.util.Date;

import java.util.Objects;

import java.util.Set;


public class JsonApp {


    public static void main(String[] args) throws Exception {

        File jsonFile = new File("./resource/test.json").getAbsoluteFile();


        SimpleModule loopBackModule = new SimpleModule();

        loopBackModule.setDeserializerModifier(new LoopBackBeanDeserializerModifier(Collections.singleton(Notification.class)));


        ObjectMapper mapper = new ObjectMapper();

        mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);

        mapper.registerModule(loopBackModule);


        MessageWrapper wrapper = mapper.readValue(jsonFile, MessageWrapper.class);

        System.out.println(wrapper.getMessage());

    }

}


class LoopBackBeanDeserializerModifier extends BeanDeserializerModifier {


    private final Set<Class> allowedClasses;


    LoopBackBeanDeserializerModifier(Set<Class> allowedClasses) {

        this.allowedClasses = Objects.requireNonNull(allowedClasses);

    }


    @Override

    public JsonDeserializer<?> modifyDeserializer(DeserializationConfig config, BeanDescription beanDesc, JsonDeserializer<?> deserializer) {

        if (allowedClasses.contains(beanDesc.getBeanClass())) {

            return new LoopBackBeanDeserializer<>((BeanDeserializerBase) deserializer);

        }

        return deserializer;

    }

}


class LoopBackBeanDeserializer<T> extends BeanDeserializer {


    private final BeanDeserializerBase baseDeserializer;


    protected LoopBackBeanDeserializer(BeanDeserializerBase src) {

        super(src);

        this.baseDeserializer = src;

    }


    @Override

    public T deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {

        // if first token is VALUE_STRING we should read it as String and

        // run deserialization process again based on this String.

        if (p.currentToken() == JsonToken.VALUE_STRING) {

            return (T) ((ObjectMapper) p.getCodec()).readValue(p.getText(), _valueClass);

        }


        // vanilla bean deserialization

        return (T) baseDeserializer.deserialize(p, ctxt);

    }

POJO型号是一样的。您只需要列出您期望某些问题和loop-back机制适用于它们的类。


查看完整回答
反对 回复 2023-09-20
  • 2 回答
  • 0 关注
  • 72 浏览

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信