【问题标题】:Jackson deserialization SNS message error MismatchedInputExceptionJackson 反序列化 SNS 消息错误 MismatchedInputException
【发布时间】:2019-10-02 15:06:37
【问题描述】:

我正在编写一个通过 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);
}

【问题讨论】:

  • 您不需要为您的所有 3 个类添加相同的注释 @JsonNaming(PropertyNamingStrategy.UpperCamelCaseStrategy.class) 吗?
  • @Ivan 即使我将这个注释添加到所有这些,我仍然得到 MismatchedInputException
  • 能否添加通过 Jackson 解析的代码 sn-p?
  • @mcarlin 添加在末尾​​span>

标签: java amazon-web-services jackson aws-sdk amazon-sns


【解决方案1】:

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

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

【讨论】:

  • 感谢您的帮助:) NotificationDeserializer 足以解决我的问题,不需要MailDeserializer 因为Message 的值是正确的JSON(添加它甚至会导致异常) .
  • @mcarlin,要使用root ObjectMapper,您可以使用((ObjectMapper) p.getCodec()),但在此示例中它不起作用。它将抛出StackOverflowError,因为ObjectMapper 一直想使用自定义实现。因此,必须创建一个新的ObjectMapper
【解决方案2】:

message 属性是 Notification 类型,Jackson 期望 JSON Object 而不是 string value。在这种情况下,您可以创建自定义反序列化器或使用某种环回实现来实现通用解决方案。如果给定的有效负载不是JSON Object,则将其读取为String,并使用此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 机制就会为它们工作。

【讨论】:

    猜你喜欢
    • 2019-09-17
    • 2019-08-26
    • 2011-09-09
    • 2012-02-23
    • 1970-01-01
    • 1970-01-01
    • 2015-06-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多