eclipse / jnosql

Eclipse JNoSQL is a framework which has the goal to help Java developers to create Jakarta EE applications with NoSQL.
Other
231 stars 72 forks source link

parasite property in document deserialization #259

Closed redmitry closed 2 years ago

redmitry commented 2 years ago

Hello,

I am facing a problem deserializing a mongo document.

The problem arises when there is an embedded object with the property of the same name as a parent class in the model and no sub-document in the storage (e.g. mongo).

Something like:

@Entity
public class Person {
    @Id
    private String id;

    @Column
    private String name;

    @Column
    private City city;
}

@Entity
public class City {
    @Column
    private String id;

    @Column
    private String name;
}

in the case of the JSON file:

{ "id": "1234", "name": "Dmitry" }

It also deserializes the "city" with the "id": "1234"

The problem is located in the AbstractDocumentEntityConverter.java

protected <T> Consumer<String> feedObject(T instance, List<Document> documents, Map<String, FieldMapping> 
fieldsGroupByName) {
    return k -> {
        Optional<Document> document = documents.stream().filter(c -> c.getName().equals(k)).findFirst();
        FieldMapping field = fieldsGroupByName.get(k);
        DocumentFieldConverter fieldConverter = converterFactory.get(field);
        fieldConverter.convert(instance, documents, document.orElse(null), field, this);
    };
}

method passes documents to the convert method while these properties are of the upper layer. the quick fix would be to pass an empty list instead (Collections.EMPTY_LIST).

Could anybody validate this?

Thank you in advance,

Dmitry

P.S. To be fair, I do not understand why empty entities should be created when model document (property) is empty.

    protected <T> Consumer<String> feedObject(T instance, List<Document> documents, Map<String, FieldMapping> fieldsGroupByName) {
        return k -> {
            Optional<Document> document = documents.stream().filter(c -> c.getName().equals(k)).findFirst();
            if (document.isPresent()) {
                FieldMapping field = fieldsGroupByName.get(k);
                DocumentFieldConverter fieldConverter = converterFactory.get(field);
                fieldConverter.convert(instance, Collections.EMPTY_LIST, document.orElse(null), field, this);
            }
        };
    }
otaviojava commented 2 years ago

@redmitry Hello, how are you?

Which version are you using?

I've created these entities:

@Entity
public class Citizen {

    @Id
    private String id;

    @Column
    private String name;

    @Column
    private City city;
}

@Entity
public class City {

    @Column
    private String id;

    @Column
    private String name;

}

Then I executed:


try (SeContainer container = SeContainerInitializer.newInstance().initialize()) {
     City elSalvador = City.of("1", "El Salvador");
     Citizen salvador = Citizen.of("1", "Salvador", elSalvador);

     DocumentTemplate template = container.select(DocumentTemplate.class).get();
     template.insert(salvador);

      final DocumentQuery query = select().from(Citizen.class.getSimpleName())
                    .where("name").eq("Salvador")
                    .and("city.name").eq("El Salvador").build();
      final Optional<Citizen> citizen = template.singleResult(query);
      System.out.println(citizen);

      template.delete(Citizen.class, "1");
}

The MongoDB executed:

{ 
    "_id" : "1", 
    "city" : {
        "name" : "El Salvador", 
        "id" : "1"
    }, 
    "name" : "Salvador"
}

You can use the code here:

redmitry commented 2 years ago

Hello @otaviojava,

I used b3 and master branch to modify the code that works for me. I deserialize from mongo via default Repository ( findById() ). The problem appears when there is no city in mongo. An yes I mistaken in the example (my model is much bigger, so I made a simple one) - the Person id should be @Id("id") id So the mongo id is an "id" (apart of the "_id"). Not sure if this helps reproduce...

Kind reagards,

Dmitry

otaviojava commented 2 years ago

@redmitry I got it. In MongoDB, we cannot rewrite the ID field; thus, it must be "_id"; otherwise, it will create another one. We can start this discussion on the e-mail list.

{ 
    "_id" : ObjectId("6218fa899fe4d06f1970dbf9"), 
    "city" : {
        "name" : "El Salvador", 
        "id" : "1"
    }, 
    "name" : "Salvador", 
    "id" : "1"
}
redmitry commented 2 years ago

Ok. I do not know jnosql mongo driver internals. I have no control over the mongo data. It has autogenerated "_id" and the "id" which is used.

I even tried to override Repository method with query:

    @Override
    @Query("select * from Biosamples where id = @id")
    public Optional<BiosampleEntity> findById(@Param("id") String id);

and use dummy "_id" property:

    @Id private String _id;
    @Column("id") private String id;

with the same effect :-(

otaviojava commented 2 years ago

Thank you, it is great feedback. I'm working on it!

otaviojava commented 2 years ago

Hey @redmitry about the deserialization, could you check/test this PR? https://github.com/eclipse/jnosql/pull/260

redmitry commented 2 years ago

Hi @otaviojava, I confirm that "fix_mapper_ambigous" branch fixed my problem. Thank you for the quick response!

Just a comment:

if (document.isPresent()) {
    fieldConverter.convert(instance, null, document.orElse(null), field, this);
}

if document.isPresent() it can't be null so document.orElse(null) is excessive.

Cheers,

Dmitry

otaviojava commented 2 years ago

Thank you @redmitry I did you suggestion, thank you twice :)