源头

在家里的homelab上部署了一个MeiliSearch服务,用来替换ElasticSearch。

MeiliSearch的搜索速度确实快,但是在使用过程中,发现它的搜索结果是一个HashMap的列表,而不是Java Bean的列表,而因为MeiliSearch中存储long类型的数据时,会转换为double类型,所以在将HashMap转换为Java Bean时,需要自定义反序列化器,将double转换为long。

解决方案

import com.au92.common.util.json.JsonUtils;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.meilisearch.sdk.model.SearchResult;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * MeiliSearch 结果映射器
 *
 * @author p_x_c
 */
public class MeiliSearchResultMapper {

    private static final ObjectMapper objectMapper = new ObjectMapper();

    static {
        // 自定义模块,处理 double 到 long 的转换
        SimpleModule module = new SimpleModule();
        module.addDeserializer(Long.class, new LongDeserializer());
        module.addDeserializer(long.class, new LongDeserializer());
        objectMapper.registerModule(module);
    }

    /**
     * 自定义 Long 反序列化器,处理 double 到 long 的转换
     */
    private static class LongDeserializer extends JsonDeserializer<Long> {
        @Override
        public Long deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
            double doubleValue = p.getDoubleValue();
            return Double.valueOf(doubleValue)
                         .longValue();
        }
    }

    /**
     * 通用方法,将 JSON 结果转换为指定类型的 Java Bean 列表
     *
     * @param searchResult 搜索结果
     * @param clazz        Java Bean 类型
     * @param <T>
     * @return
     * @throws JsonProcessingException
     */
    public static <T> List<T> parseResults(SearchResult searchResult, Class<T> clazz) throws JsonProcessingException {
        // 将 searchResult 的 hits 转换为 JSON 数组字符串
        ArrayNode hitsArray = (ArrayNode) objectMapper.readTree(JsonUtils.toJSONString(searchResult.getHits()));
        List<T> results = new ArrayList<>();
        for (JsonNode node : hitsArray) {
            T bean = objectMapper.treeToValue(node, clazz);
            results.add(bean);
        }

        return results;
    }

    /**
     * 将单个 HashMap<String, Object> 转换为指定的 Java Bean 类型
     *
     * @param resultMap
     * @param clazz
     * @param <T>
     * @return
     */
    public static <T> T parseResult(Map<String, Object> resultMap, Class<T> clazz) {
        return objectMapper.convertValue(resultMap, clazz);
    }
}