如何在Jackson中正确反序列化Map<String, Object>?

3
我有一个POJO,可以序列化为字节或json
public final class Message {

  private final Data data;
  private final Request request;
  private final Response response;

  public Message() {
    this.data = new Data();
    this.request = new Request();
    this.response = new Response();
  }

  public Data getData() {
    return data;
  }

  public Request getRequest() {
    return request;
  }

  public Response getResponse() {
    return response;
  }

  public Object query(String pointer) {
    return toJson().query(pointer);
  }

  public byte[] toBytes() {
    try {
      return new ObjectMapper(new MessagePackFactory()).writeValueAsBytes(this);
    } catch (JsonProcessingException ex) {
      throw new MessageException(ex);
    }
  }

  public JSONObject toJson() {
    try {
      return new JSONObject(new ObjectMapper().writeValueAsString(this));
    } catch (JsonProcessingException ex) {
      throw new MessageException(ex);
    }
  }

  @Override
  public String toString() {
    try {
      return toString(0);
    } catch (MessageException ex) {
      throw new MessageException(ex);
    }
  }

  public String toString(int indent) {
    try {
      return toJson().toString(indent);
    } catch (MessageException ex) {
      throw new MessageException(ex);
    }
  }
}

参考类:

public class Data {

  private final Map<String, Map<String, Object>> dataMap;

  public Data() {
    this.dataMap = new HashMap();
  }

  public Data addToSet(String name, String key, Object value) {
    Map<String, Object> map = dataMap.get(name);
    if (map == null) {
      map = new HashMap();
    }
    map.put(key, value);
    dataMap.put(name, map);
    return this;
  }

  public Map<String, Map<String, Object>> getSets() {
    return dataMap;
  }

  public Data updateSet(String name, String key, Object value) {
    return Data.this.addToSet(name, key, value);
  }

  public Data removeFromSet(String name, String key) {
    Map<String, Object> map = dataMap.get(name);
    if (map == null) {
      throw new MessageException("No such property '" + key + "' for set '" + name + "'");
    }
    map.remove(key);
    return this;
  }

  public Map<String, Object> getSet(String name) {
    return dataMap.get(name);
  }
}

public class Request {

  private String method;
  private String resource;
  private final Map<String, Object> body;
  private final Map<String, String> headers;
  private final Map<String, String[]> parameters;

  public Request() {
    this.body = new HashMap();
    this.headers = new HashMap();
    this.parameters = new HashMap();
  }

  public String getMethod() {
    return Objects.toString(method, "");
  }

  public String getResource() {
    return Objects.toString(resource, "");
  }

  public Map<String, Object> getBody() {
    return body;
  }

  public Map<String, String> getHeaders() {
    return headers;
  }

  public Map<String, String[]> getParameters() {
    return parameters;
  }

  public String getHeader(String name) {
    return headers.get(name);
  }

  public Request setBody(String payload) {
    try {
      this.body.putAll(new ObjectMapper().readValue(payload, new TypeReference<Map<String, Object>>() {
      }));
      return this;
    } catch (JsonProcessingException ex) {
      throw new MessageException(ex);
    }
  }

  public Request setMethod(String name) {
    this.method = name;
    return this;
  }

  public Request setResource(String name) {
    this.resource = name;
    return this;
  }

  public Request setHeaders(Map<String, String> headers) {
    this.headers.putAll(headers);
    return this;
  }

  public Request setParameters(Map<String, String[]> parameters) {
    this.parameters.putAll(parameters);
    return this;
  }
}

public class Response {

  private String code;
  private String data;
  private String messageId;
  private String timestamp;
  private String description;

  public Response() {
  }

  public String getCode() {
    return Objects.toString(code, "");
  }

  public String getData() {
    return Objects.toString(data, "");
  }

  public String getMessageId() {
    return Objects.toString(messageId, "");
  }

  public String getTimestamp() {
    return Objects.toString(timestamp, "");
  }

  public String getDescription() {
    return Objects.toString(description, "");
  }

  public Response setCode(String code) {
    this.code = code;
    return this;
  }

  public Response setData(String data) {
    this.data = data;
    return this;
  }

  public Response setMessageId(String messageId) {
    this.messageId = messageId;
    return this;
  }

  public Response setTimestamp(String timestamp) {
    this.timestamp = timestamp;
    return this;
  }

  public Response setDescription(String description) {
    this.description = description;
    return this;
  }
}

当将数据序列化为json格式时,我得到了一个有效的字符串。
{
    "request": {
        "headers": {},
        "method": "",
        "resource": "",
        "body": {
            "whatsapp": {
                "conversationId": "39f09c41-1bd3-4e81-b829-babed3747d4b",
                "name": "Dave",
                "source": "+123456789098"
            },
            "payment": {
                "product": "chocolate",
                "amount": 1,
                "method": "cashapp",
                "msisdn": "123456789098",
                "entity": "The Fudge Shop"
            }
        },
        "parameters": {}
    },
    "data": {
        "sets": {
            "whatsapp": {
                "provider": "clickatell",
                "name": "Dave",
                "destination": "123456789098",
                "source": "123456789098",
                "message": "Your payment of $1.00 received, your receipt.no is QWJ124XPA9."
            },
            "cashapp": {
                "amount": 1,
                "receiptNo": "QWJ124XPA9",
                "name": "Dave Chapelle",
                "msisdn": "123456789098"
            }
        }
    },
    "response": {
        "code": "202",
        "data": "",
        "messageId": "20210623160202a647d32ee9ae477f9c90d8b1fbfd763a",
        "description": "Processing Request",
        "timestamp": "2021-06-23 16:02:02.408"
    }
}

当我试图将json反序列化回一个pojo时
Message output = new ObjectMapper().readValue(json.toString(), Message.class);

我收到了以下错误:

com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize instance of `java.lang.String` out of START_OBJECT token

当尝试反序列化Map<String, Object>主体时,错误似乎是由Request类生成的:

我该如何正确地反序列化Map?


你还有其他的课程吗? - undefined
@oleg.cherednik 我已经添加了他们 - undefined
Request在哪里? - undefined
这是示例中的最后一个类。我会将它移到参考资料中。 - undefined
3个回答

2
我用的解决方案是使用自定义反序列化,@JsonDeserialize 注释和 JsonDeserializer 接口,以实现所需结果。
以下是解决方案:
public class Request {

  private String method;
  private String resource;
  @JsonDeserialize(using = BodyDeserializer.class)
  private final Map<String, Object> body;
  private final Map<String, String> headers;
  private final Map<String, String[]> parameters;

  public Request() {
    this.body = new HashMap();
    this.headers = new HashMap();
    this.parameters = new HashMap();
  }

  public String getMethod() {
    return method;
  }

  public String getResource() {
    return resource;
  }

  public Map<String, Object> getBody() {
    return body;
  }

  public Map<String, String> getHeaders() {
    return headers;
  }

  public Map<String, String[]> getParameters() {
    return parameters;
  }

  public String getHeader(String name) {
    return headers.get(name);
  }

  public Request setBody(Map<String, Object> body) {
    this.body.putAll(body);
    return this;
  }

  public Request setMethod(String name) {
    this.method = name;
    return this;
  }

  public Request setResource(String name) {
    this.resource = name;
    return this;
  }

  public Request setHeaders(Map<String, String> headers) {
    this.headers.putAll(headers);
    return this;
  }

  public Request setParameters(Map<String, String[]> parameters) {
    this.parameters.putAll(parameters);
    return this;
  }

  private static class BodyDeserializer extends JsonDeserializer<Map<String, Object>> {

    @Override
    public Map<String, Object> deserialize(JsonParser jp, DeserializationContext dc) throws IOException, JsonProcessingException {
      JsonDeserializer<Object> deserializer = dc.findRootValueDeserializer(dc.constructType(Map.class));
      Map<String, Object> map = (Map<String, Object>) deserializer.deserialize(jp, dc);
      return map;
    }
  }
}

1

对于字符串问题,以下资源可能有所帮助:

无法将java.lang.String的实例反序列化为START_OBJECT令牌

https://www.baeldung.com/jackson-map#1-mapltstring-stringgt-deserialization

为什么这段代码无法工作

Jackson 并不比你更强大。

如果 Jackson 获得一个要序列化的对象,它会尝试序列化所有值。只有值(这对于独立于类非常好)。这是一个 json 对象:

{
"type":"apple",
"quantity":3,
"imageID":17
}

现在,这个对象的类是什么?它可能是Fruit.class、Image.class或者RoundObject.class,json不知道,Jackson也不知道。
那么json怎么知道这个类是什么呢?通过查看对象引用的类型。在你的情况下,它是Object。在Object.class中,Jackson找不到需要保存对象变量的构造函数,所以它会崩溃。
解决方案
尝试序列化对象并不是一个好主意。如果你有非常不同的类想要放进去,比如Apple和Banana,那就创建一个名为Fruit的接口或抽象类,让它们都实现它。现在,在这个类的顶部使用这个注释:
@JsonTypeInfo(
        use = JsonTypeInfo.Id.NAME,
        property = "type") // name of the variable to save the kind of object you put in. NO VARIABLES in all classes that extend from Fruit are allowed to have this name (or at least @JsonProperty).
@JsonSubTypes({
        @JsonSubTypes.Type(value = Apple.class, name = "banana"),
        @JsonSubTypes.Type(value = Banana.class, name = "apple"),
})

使用Map应该可以工作。

谢谢你给我指明了正确的方向。 - undefined
嗨 @kimathie,现在可以工作了吗?还有什么需要修复的吗? - undefined
我最终使用自定义反序列化来实现所需的结果,请参考我的答案。https://dev59.com/fmQLtIcB2Jgan1znY6uQ#68133685 - undefined
1
@kimathie 哦,抱歉,我没有注意到这个回答是你写的。很高兴能帮到你! - undefined

0

试试这个 JacksonUtils

Message actual = createMessage();
String json = JsonUtils.prettyPrint().writeValue(actual);
System.out.println(json);
Message expected = JsonUtils.readValue(json, Message.class);

这是完整的代码片段:

public class MavenMain {
    public static void main(String... args) {
        Message actual = createMessage();
        String json = JsonUtils.prettyPrint().writeValue(actual);
        System.out.println(json);
        Message expected = JsonUtils.readValue(json, Message.class);
    }

    private static Message createMessage() {
        Message message = new Message();
        message.setData(createData());
        message.setRequest(createRequest());
        message.setResponse(createResponse());
        return message;
    }

    private static Data createData() {
        Map<String, Object> whatsapp = new LinkedHashMap<>();
        whatsapp.put("provider", "clickatell");
        whatsapp.put("name", "Dave");
        whatsapp.put("destination", "123456789098");
        whatsapp.put("source", "123456789098");
        whatsapp.put("message", "Your payment of $1.00 received, your receipt.no is QWJ124XPA9.");

        Map<String, Object> cashapp = new LinkedHashMap<>();
        cashapp.put("receiptNo", "QWJ124XPA9");
        cashapp.put("name", "Dave Chapelle");
        cashapp.put("msisdn", "123456789098");

        Map<String, Map<String, Object>> dataMap = new LinkedHashMap<>();
        dataMap.put("whatsapp", whatsapp);
        dataMap.put("cashapp", cashapp);

        Data data = new Data();
        data.setDataMap(dataMap);

        return data;
    }

    private static Request createRequest() {
        Map<String, Object> whatsapp = new LinkedHashMap<>();
        whatsapp.put("conversationId", "39f09c41-1bd3-4e81-b829-babed3747d4b");
        whatsapp.put("name", "Dave");
        whatsapp.put("source", "+123456789098");

        Map<String, Object> payment = new LinkedHashMap<>();
        payment.put("product", "chocolate");
        payment.put("amount", 1);
        payment.put("method", "cashapp");
        payment.put("msisdn", "123456789098");
        payment.put("entity", "The Fudge Shop");

        Map<String, Object> body = new HashMap<>();
        body.put("whatsapp", whatsapp);
        body.put("payment", payment);

        Request request = new Request();
        request.setHeaders(Collections.emptyMap());
        request.setMethod("");
        request.setResource("");
        request.setBody(body);
        request.setParameters(Collections.emptyMap());

        return request;
    }

    private static Response createResponse() {
        Response response = new Response();
        response.setCode("202");
        response.setData("");
        response.setMessageId("20210623160202a647d32ee9ae477f9c90d8b1fbfd763a");
        response.setDescription("Processing Request");
        response.setTimestamp("2021-06-23T16:02:02.408");
        return response;
    }
}

class Message {

    private Data data;
    private Request request;
    private Response response;

    public void setData(Data data) {
        this.data = data;
    }

    public void setRequest(Request request) {
        this.request = request;
    }

    public void setResponse(Response response) {
        this.response = response;
    }
}

class Data {

    @JsonProperty("sets")
    private Map<String, Map<String, Object>> dataMap;

    public void setDataMap(Map<String, Map<String, Object>> dataMap) {
        this.dataMap = dataMap;
    }
}

class Request {

    private String method;
    private String resource;
    private Map<String, Object> body;
    private Map<String, String> headers;
    private Map<String, String[]> parameters;

    public void setMethod(String method) {
        this.method = method;
    }

    public void setResource(String resource) {
        this.resource = resource;
    }

    public void setBody(Map<String, Object> body) {
        this.body = body;
    }

    public void setHeaders(Map<String, String> headers) {
        this.headers = headers;
    }

    public void setParameters(Map<String, String[]> parameters) {
        this.parameters = parameters;
    }
}

class Response {

    private String code;
    private String data;
    private String messageId;
    private String timestamp;
    private String description;

    public void setCode(String code) {
        this.code = code;
    }

    public void setData(String data) {
        this.data = data;
    }

    public void setMessageId(String messageId) {
        this.messageId = messageId;
    }

    public void setTimestamp(String timestamp) {
        this.timestamp = timestamp;
    }

    public void setDescription(String description) {
        this.description = description;
    }
}

如果您想使用不可变对象,那么模型的配置会有一些不同,但是在main类中的代码将保持不变。


感谢您提供一个可行的替代方案。 - undefined

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接