Jackson反序列化SNS消息报错MismatchedInputException

Jackson deserialization SNS message error MismatchedInputException

我正在编写一项功能,通过 SNS HTTP 请求处理来自 Amazon Simple Email Service 的回调。我想将亚马逊提供的消息解析为本地对象结构。问题是 SNS 将 JSON 消息包装成 String 并且 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;
}

@Data
public class Notification {
    private String notificationType;
    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;
}

我正在寻找一些方法来告诉 Jackson Message 应该从一个字符串中提取出来并被当作一个普通的 JSON。

编辑

反序列化

private MessageWrapper deserializeMessage(String message) throws IOException {
    return new ObjectMapper().readValue(message, MessageWrapper.class);
}

我认为要解决这个问题,您需要为 MessageWrapper class 中的 Notification 字段以及 Mail 字段中的 Mail 字段自定义反序列化器=12=] class 像下面这样:

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); 
    }
}

在您的 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

实际上不需要MailDeserializerNotificationDeserializer 单独解决了这个问题。

编辑 2

必须在自定义反序列化器中使用新的 ObjectMapper

message 属性 是 Notification 类型,Jackson 期望 JSON Object 而不是 string value。在这种情况下,您可以创建自定义反序列化器或使用某种环回实现来实现通用解决方案。如果给定的有效载荷不是 JSON Object 将其读取为 String 并使用此 String.

再次调用反序列化

要避免 WhosebugError,您需要使用 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 机制将适用于它们。