0

在使用 mongo DB 驱动程序时,遇到了一个非常愚蠢的异常Maps MUST have string keys, found class Enum instead。我发现了很多这样的问题而其他人像我一样失败了。

我真的不明白为什么 mondoDb 驱动程序不编码已知的键类型,如值。我的愿望是 mongoDB 驱动程序修复了这个问题,并且像 Quarkus 这样的框架可以像我一样为这个问题提供修复。对于所有其他人,请随时贡献和清理我的 hack,因为我确信它可以做得更好。

4

1 回答 1

0

所以我创建了以下 hack,因为我的调试时间和耐心真的很有限,我想继续工作而不是无休止地调试其他代码

编解码器:

import org.bson.BsonDocument;
import org.bson.BsonDocumentWriter;
import org.bson.BsonReader;
import org.bson.BsonType;
import org.bson.BsonWriter;
import org.bson.codecs.Codec;
import org.bson.codecs.DecoderContext;
import org.bson.codecs.EncoderContext;
import org.bson.codecs.configuration.CodecConfigurationException;
import org.bson.json.JsonReader;

import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

public class MapCodec<K, T> implements Codec<Map<K, T>> {
    private final Class<Map<K, T>> encoderClass;
    private final Codec<K> keyCodec;
    private final Codec<T> valueCodec;

    MapCodec(final Class<Map<K, T>> encoderClass, final Codec<K> keyCodec, final Codec<T> valueCodec) {
        this.encoderClass = encoderClass;
        this.keyCodec = keyCodec;
        this.valueCodec = valueCodec;
    }

    @Override
    public void encode(final BsonWriter writer, final Map<K, T> map, final EncoderContext encoderContext) {
        try (var dummyWriter = new BsonDocumentWriter(new BsonDocument())) {
            dummyWriter.writeStartDocument();
            writer.writeStartDocument();
            for (final Map.Entry<K, T> entry : map.entrySet()) {
                var dummyId = UUID.randomUUID().toString();
                dummyWriter.writeName(dummyId);
                keyCodec.encode(dummyWriter, entry.getKey(), encoderContext);
                //TODO: could it be simpler by something like JsonWriter?
                writer.writeName(dummyWriter.getDocument().asDocument().get(dummyId).asString().getValue());
                valueCodec.encode(writer, entry.getValue(), encoderContext);
            }
            dummyWriter.writeEndDocument();
        }
        writer.writeEndDocument();
    }

    @Override
    public Map<K, T> decode(final BsonReader reader, final DecoderContext context) {
        reader.readStartDocument();
        Map<K, T> map = getInstance();
        while (reader.readBsonType() != BsonType.END_OF_DOCUMENT) {
            //TODO: what if the key is not a String aka not wrapped in double quotes?
            var nameReader = new JsonReader("{\"key:\":\"" + reader.readName() + "\"}");
            nameReader.readStartDocument();
            nameReader.readBsonType();
            if (reader.getCurrentBsonType() == BsonType.NULL) {
                map.put(keyCodec.decode(nameReader, context), null);
                reader.readNull();
            } else {
                map.put(keyCodec.decode(nameReader, context), valueCodec.decode(reader, context));
            }
            nameReader.readEndDocument();
        }
        reader.readEndDocument();
        return map;
    }

    @Override
    public Class<Map<K, T>> getEncoderClass() {
        return encoderClass;
    }

    private Map<K, T> getInstance() {
        if (encoderClass.isInterface()) {
            return new HashMap<>();
        }
        try {
            return encoderClass.getDeclaredConstructor().newInstance();
        } catch (final Exception e) {
            throw new CodecConfigurationException(e.getMessage(), e);
        }
    }
}

提供者:

import org.bson.BsonReader;
import org.bson.BsonType;
import org.bson.BsonWriter;
import org.bson.codecs.Codec;
import org.bson.codecs.DecoderContext;
import org.bson.codecs.EncoderContext;
import org.bson.codecs.configuration.CodecConfigurationException;
import org.bson.codecs.pojo.PropertyCodecProvider;
import org.bson.codecs.pojo.PropertyCodecRegistry;
import org.bson.codecs.pojo.TypeWithTypeParameters;

import java.util.HashMap;
import java.util.Map;

public class MapCodecProvider implements PropertyCodecProvider {

    @Override
    @SuppressWarnings({"rawtypes", "unchecked"})
    public <T> Codec<T> get(final TypeWithTypeParameters<T> type, final PropertyCodecRegistry registry) {
        if (Map.class.isAssignableFrom(type.getType()) && type.getTypeParameters().size() == 2) {
            return new berlin.yuna.royaltanks.config.db.MapCodec(type.getType(), registry.get(type.getTypeParameters().get(0)), registry.get(type.getTypeParameters().get(1)));
        } else {
            return null;
        }
    }

    private static class MapCodec<T> implements Codec<Map<String, T>> {
        private final Class<Map<String, T>> encoderClass;
        private final Codec<T> codec;

        MapCodec(final Class<Map<String, T>> encoderClass, final Codec<T> codec) {
            this.encoderClass = encoderClass;
            this.codec = codec;
        }

        @Override
        public void encode(final BsonWriter writer, final Map<String, T> map, final EncoderContext encoderContext) {
            writer.writeStartDocument();
            for (final Map.Entry<String, T> entry : map.entrySet()) {
                writer.writeName(entry.getKey());
                if (entry.getValue() == null) {
                    writer.writeNull();
                } else {
                    codec.encode(writer, entry.getValue(), encoderContext);
                }
            }
            writer.writeEndDocument();
        }

        @Override
        public Map<String, T> decode(final BsonReader reader, final DecoderContext context) {
            reader.readStartDocument();
            Map<String, T> map = getInstance();
            while (reader.readBsonType() != BsonType.END_OF_DOCUMENT) {
                if (reader.getCurrentBsonType() == BsonType.NULL) {
                    map.put(reader.readName(), null);
                    reader.readNull();
                } else {
                    map.put(reader.readName(), codec.decode(reader, context));
                }
            }
            reader.readEndDocument();
            return map;
        }

        @Override
        public Class<Map<String, T>> getEncoderClass() {
            return encoderClass;
        }

        private Map<String, T> getInstance() {
            if (encoderClass.isInterface()) {
                return new HashMap<>();
            }
            try {
                return encoderClass.getDeclaredConstructor().newInstance();
            } catch (final Exception e) {
                throw new CodecConfigurationException(e.getMessage(), e);
            }
        }
    }
}
于 2021-06-05T12:46:31.867 回答