Почему Firestore не использует сеттеры при конвертации JSON-подобных данных в POJO? - PullRequest
0 голосов
/ 17 сентября 2018

Это относится к преобразованию между внутренним JSON-подобным представлением Firestore и POJO и обратно из него.

Из моих экспериментов (у которых не было времени прочитать код) это похоже на преобразованиеиз POJO в JSON-подобный код Firestore использует общедоступные методы получения.

Однако при преобразовании из JSON-подобного в POJO код Firestore, похоже, ищет только совпадающие имена полей.

Почему этоявляется проблемой

Чтобы поддерживать теги для объекта, логическая структура данных, которую следует использовать, - Set, а не List.Я могу написать:

private Set<String> tags = new HashedSet<>();

public ImmutableList<String> getTags() {
    ImmutableList.copyOf(this.tags);
}

Это прекрасно работает при сохранении моего POJO в Firestore, преобразовывая мой (внутренний) набор тегов в массив в Firestore.

Однако при чтении этого документав мой POJO (DocumentSnapshot.toObject()) Firestore жалуется, что массивы можно десериализовать только до List, а не любого другого типа Collection.

Я попытался добавить:

private void setTags(List<String> tags) { //public method gives the same result
    this.tags.clear();
    this.tags.addAll(tags);
}

который теоретически должен был разрешить преобразование массива в JSON-подобных данных в мой POJO, но этот метод никогда не используется.Вместо этого я получаю следующее исключение:

Caused by: java.lang.RuntimeException: Could not deserialize object. Collections are not supported, please use Lists instead (found in field 'tags')
    at com.google.cloud.firestore.CustomClassMapper.deserializeError(CustomClassMapper.java:524)
    at com.google.cloud.firestore.CustomClassMapper.deserializeToParameterizedType(CustomClassMapper.java:280)
    at com.google.cloud.firestore.CustomClassMapper.deserializeToType(CustomClassMapper.java:178)
    at com.google.cloud.firestore.CustomClassMapper.access$200(CustomClassMapper.java:50)
    at com.google.cloud.firestore.CustomClassMapper$BeanMapper.deserialize(CustomClassMapper.java:714)
    at com.google.cloud.firestore.CustomClassMapper$BeanMapper.deserialize(CustomClassMapper.java:674)
    at com.google.cloud.firestore.CustomClassMapper.convertBean(CustomClassMapper.java:503)
    at com.google.cloud.firestore.CustomClassMapper.deserializeToClass(CustomClassMapper.java:242)
    at com.google.cloud.firestore.CustomClassMapper.convertToCustomClass(CustomClassMapper.java:96)
    at com.google.cloud.firestore.DocumentSnapshot.toObject(DocumentSnapshot.java:234)
    at com.example.backend.data.FirestoreRepository.setId(FirestoreRepository.java:277)
    at com.example.backend.data.FirestoreRepository.get(FirestoreRepository.java:124)
    at com.example.backend.data.FirestoreRepository.get(FirestoreRepository.java:169)
    at com.example.backend.web.pojos.Test.getQuestionById(Test.java:129)
    ... 51 more

Мой текущий обходной путь - иметь как List, так и Set внутри, а затем преобразовать из List в Set после загрузки данных.Это работает, но очень громоздко и подвержено ошибкам (легко допустить глупые ошибки программирования).Представьте себе, что вам нужно выполнить этот маленький танец для 10 различных объектов и возможность появления непреднамеренных ошибок.

Использование Firebase Admin SDK версии 6.3.0.

Некоторые тестовые коды и тестовые POJO

Вот базовый POJO, с которым я снова протестировал:

import java.util.List;
import java.util.Set;
import java.util.TreeSet;

import com.google.common.collect.ImmutableList;

public class TestQ {

    private String id;

    private Set<String> topicTags = new TreeSet<>();

    public String getId() {
        return this.id;
    }

    public ImmutableList<String> getTopicTags() {
        return ImmutableList.copyOf(this.topicTags);
    }

    public TestQ setId(String id) {
        this.id = id;
        return this;
    }

    public TestQ setTopicTags(List<String> topicTags) {
        this.topicTags.clear();
        this.topicTags.addAll(topicTags);
        return this;
    }

}

Тестовый код, который пытается создать экземпляр этого класса из документа Firestore:

public void getTestQById(HttpServletRequest req, HttpServletResponse res, List<String> pathParts)
        throws IOException {
    String id = req.getParameter("id");
    DocumentSnapshot ds;
    try {
        ds = db().collection("questions").document(id).get().get();
        TestQ question = ds.toObject(TestQ.class);
        PrintWriter writer = res.getWriter();
        if (null != question) {
            question.setId(id);
        }
    } catch (InterruptedException | ExecutionException | RuntimeException e) {
        log().log(Level.WARNING, "Unexpected error", e);
    }
}

Обратите внимание, что многие изсвойства документа Firestore были исключены из TestQ POJO.Это показано в виде предупреждений ниже:

Sep 18, 2018 10:19:06 AM com.google.cloud.firestore.CustomClassMapper$BeanMapper deserialize
WARNING: No setter/field for approved found on class com.example.backend.model.questions.TestQ
Sep 18, 2018 10:19:06 AM com.google.cloud.firestore.CustomClassMapper$BeanMapper deserialize
WARNING: No setter/field for dateLastModified found on class com.example.backend.model.questions.TestQ
Sep 18, 2018 10:19:06 AM com.google.cloud.firestore.CustomClassMapper$BeanMapper deserialize
WARNING: No setter/field for dateCreated found on class com.example.backend.model.questions.TestQ
Sep 18, 2018 10:19:06 AM com.google.cloud.firestore.CustomClassMapper$BeanMapper deserialize
WARNING: No setter/field for level found on class com.example.backend.model.questions.TestQ
Sep 18, 2018 10:19:06 AM com.google.cloud.firestore.CustomClassMapper$BeanMapper deserialize
WARNING: No setter/field for options found on class com.example.backend.model.questions.TestQ
Sep 18, 2018 10:19:06 AM com.google.cloud.firestore.CustomClassMapper$BeanMapper deserialize
WARNING: No setter/field for approvedBy found on class com.example.backend.model.questions.TestQ
Sep 18, 2018 10:19:06 AM com.google.cloud.firestore.CustomClassMapper$BeanMapper deserialize
WARNING: No setter/field for text found on class com.example.backend.model.questions.TestQ
Sep 18, 2018 10:19:06 AM com.google.cloud.firestore.CustomClassMapper$BeanMapper deserialize
WARNING: No setter/field for lang found on class com.example.backend.model.questions.TestQ
Sep 18, 2018 10:19:06 AM com.example.backend.web.pojos.Test getTestQById
WARNING: Unexpected error
java.lang.RuntimeException: Could not deserialize object. Collections are not supported, please use Lists instead (found in field 'topicTags')
at com.google.cloud.firestore.CustomClassMapper.deserializeError(CustomClassMapper.java:524)
at com.google.cloud.firestore.CustomClassMapper.deserializeToParameterizedType(CustomClassMapper.java:280)
at com.google.cloud.firestore.CustomClassMapper.deserializeToType(CustomClassMapper.java:178)
at com.google.cloud.firestore.CustomClassMapper.access$200(CustomClassMapper.java:50)
at com.google.cloud.firestore.CustomClassMapper$BeanMapper.deserialize(CustomClassMapper.java:714)
at com.google.cloud.firestore.CustomClassMapper$BeanMapper.deserialize(CustomClassMapper.java:674)
at com.google.cloud.firestore.CustomClassMapper.convertBean(CustomClassMapper.java:503)
at com.google.cloud.firestore.CustomClassMapper.deserializeToClass(CustomClassMapper.java:242)
at com.google.cloud.firestore.CustomClassMapper.convertToCustomClass(CustomClassMapper.java:96)
at com.google.cloud.firestore.DocumentSnapshot.toObject(DocumentSnapshot.java:234)
at com.example.backend.web.pojos.Test.getTestQById(Test.java:146)

Соответствующая часть документа Firestore выглядит следующим образом:

topicTags property in Firestore document

...