Джексон 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();
    }
}
Другие вопросы по тегам