Джексон JSON Десериализация MongoDB ObjectId
Итак, во-первых, вот JSON, который возвращается из моего веб-сервиса. Я пытаюсь десериализовать его в pojos после асинхронного запроса в ResponseHandler в моем Android ContentProvider.
{"exampleList" : [{
"locationId" : "00001" ,
"owners" : [
{
"paidID" : { "$oid" : "50a9c951300493f64fbffdb6"} ,
"userID" : { "$oid" : "50a9c951300493f64fbffdb6"}
} ,
{
"paidID" : { "$oid" : "50a9c951300493f64fbffdb7"} ,
"userID" : { "$oid" : "50a9c951300493f64fbffdb7"}
}
]
}]}
Сначала я был озадачен проблемой, с которой столкнулся, поскольку я использую те же бобы, аннотированные Джексоном, для своего веб-сервиса, что и в своем приложении для Android, - но потом я понял, что owners
объект никогда не отправлялся в образце JSON на мой веб-сервис (он пропускает POJO на моем веб-сервисе и добавляется в документы в mongoDB посредством атомарных обновлений из DAO).
Так окей До сих пор Джексон не должен был справляться с owners
объект, и теперь, когда он задыхается, а именно:
JsonMappingException
: Не может десериализовать экземплярjava.lang.String
снаружиSTART_OBJECT
токен на [символ позиции, где вы можете найти"userID"
а также"paidID"
] через цепочку ссылок [путь к моему бобу Джексона, который содержит класс владельцев]
У моего боба Джексона есть обертка, о чем и говорит этот "exampleList":
public class Examples extends HashMap<String, ArrayList<Example>> {
}
И тогда фактический Example
учебный класс:
@JsonIgnoreProperties(ignoreUnknown = true)
public class Example implements Comparable<Example> {
@ObjectId @Id
private String id;
@JsonProperty(Constants.Example.location)
private String location;
@JsonProperty(Constants.Example.OWNERS)
private List<Owners> owners;
public int compareTo(Example _o) {
return getId().compareTo(_o.getId());
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getLocation() {
return location;
}
public void setLocation(String location) {
this.location = location;
}
public List<Example.Owners> getOwners() {
return owners;
}
public void setOwners(List<Example.Owners> owners) {
this.owners = owners;
}
public Example() {
}
@JsonCreator
public Example(@Id @ObjectId String id) {
this.id = id;
}
@JsonIgnoreProperties(ignoreUnknown = true)
public static class Owners implements Comparable<Owners> {
@JsonProperty(Constants.Example.USERID)
private String userID;
@JsonProperty(Constants.Example.PAIDID)
private String paidID;
public Owners() {
}
public int compareTo(Owners _o) {
return getUserID().compareTo(_o.getUserID());
}
@ObjectId
public String getUserID() {
return userID;
}
@ObjectId
public void setUserID(String userID) {
this.userID = userID;
}
@ObjectId
public String getPaidID() {
return paidID;
}
@ObjectId
public void setPaidID(String paidID) {
this.paidID = paidID;
}
}
}
И, наконец, код в ResponseHandler, где все это терпит неудачу (2-я строка создает исключение JsonMappingException):
objectMapper = MongoJacksonMapperModule.configure(objectMapper);
mExamples = objectMapper.readValue(jsonParser, Examples.class);
У меня есть ощущение, что проблема в том, что Джексон до сих пор не знает, как отобразить эти $oid
, которые являются объектными идентификаторами mongoDB. Библиотека MongoJacksonMapper должна помочь этому, предоставляя @ObjectId
аннотация и способ настроить ObjectMapper для использования этой библиотеки, но она все еще не работает. По какой-то причине он по-прежнему ищет идентификатор пользователя или оплачиваемый идентификатор как строку, а не как идентификатор объекта. Есть идеи?
5 ответов
В моем коде было по крайней мере две проблемы, которые были довольно сложными, чтобы отследить ответы в Интернете, поэтому я обязательно сделаю ссылку здесь. По сути, дочерним классам нужен конструктор в родительском классе, который вызывает метод readValue() Джексона для сопоставления дочернего класса. Что касается mongoDB $oid, вы должны создать отдельный класс MongoId для представления этих объектов mongo и следовать тому же шаблону, что и для дочернего класса, для сопоставления данных, когда они поступают для десериализации. Вот блог, который я нашел, который хорошо описывает это и дает несколько примеров.
Другая альтернатива com.fasterxml.jackson.databind.ser.std.ToStringSerializer
,
@Id
@JsonSerialize(using = ToStringSerializer.class)
private final ObjectId id;
Это приведет к:
{
"id": "5489f420c8306b6ac8d33897"
}
Для будущих пользователей: используйте специальный десериализатор Джексона для преобразования $oid обратно в ObjectId.
public class ObjectIdDeserializer extends JsonDeserializer<ObjectId> {
@Override
public ObjectId deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
JsonNode oid = ((JsonNode)p.readValueAsTree()).get("$oid");
return new ObjectId(oid.asText());
}
}
Как пользоваться:
ObjectMapper mapper = new ObjectMapper();
SimpleModule mod = new SimpleModule("ObjectId", new Version(1, 0, 0, null, null, null));
mod.addDeserializer(ObjectId.class, new ObjectIdDeserializer());
registerModule(mod);
YourClass obj = mapper.readValue("{your json with $oid}", YourClass.class);
Джексон не знает, как сериализовать ObjectId. Я подправил код Арни для сериализации любого ObjectId и предоставил этот рабочий пример:
public class SerialiserTest {
private ObjectMapper mapper = new ObjectMapper();
public static class T {
private ObjectId objectId;
public ObjectId getObjectId() {
return objectId;
}
public void setObjectId(ObjectId objectId) {
this.objectId = objectId;
}
}
@Test
public final void serDeser() throws IOException {
T t = new T();
t.setObjectId(new ObjectId());
List<T> ls = Collections.singletonList(t);
String json = mapper.writeValueAsString(ls);
System.out.println(json);
SimpleModule mod = new SimpleModule("ObjectId", new Version(1, 0, 0, null, null, null));
mod.addDeserializer(ObjectId.class, new ObjectIdDeserializer());
mapper.registerModule(mod);
JavaType type = mapper.getTypeFactory().
constructCollectionType(List.class, T.class);
List<?> l = mapper.readValue(json, type);
System.out.println(l);
}
}
public class ObjectIdDeserializer extends JsonDeserializer<ObjectId> {
@Override
public ObjectId deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
JsonNode n = (JsonNode)p.readValueAsTree();
return new ObjectId(n.get("timestamp").asInt(), n.get("machineIdentifier").asInt(), (short) n.get("processIdentifier").asInt(), n.get("counter").asInt());
}
}
Здесь описан еще более простой способ, который спас мне жизнь. Теперь вы можете использовать ObjectId в Java, но когда вы перейдете в / из JSON, это будет String.
public class ObjectIdJsonSerializer extends JsonSerializer<ObjectId> {
@Override
public void serialize(ObjectId o, JsonGenerator j, SerializerProvider s) throws IOException, JsonProcessingException {
if(o == null) {
j.writeNull();
} else {
j.writeString(o.toString());
}
}
}
А потом в твоих бобах
@JsonSerialize(using=ObjectIdJsonSerializer.class)
private ObjectId id;
Я сделал это так:
@Configuration
public class SpringWebFluxConfig {
@Bean
@Primary
ObjectMapper objectMapper() {
Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
builder.serializerByType(ObjectId.class, new ToStringSerializer());
builder.deserializerByType(ObjectId.class, new JsonDeserializer() {
@Override
public Object deserialize(JsonParser p, DeserializationContext ctxt)
throws IOException {
Map oid = p.readValueAs(Map.class);
return new ObjectId(
(Integer) oid.get("timestamp"),
(Integer) oid.get("machineIdentifier"),
((Integer) oid.get("processIdentifier")).shortValue(),
(Integer) oid.get("counter"));
}
});
return builder.build();
}
}