Cannot store Java enum values in MongoDB

Hi,
I have a codec provider for enum suggested by https://dev.to/harithay:

public class EnumCodecProvider implements CodecProvider {
    @Override
    public <T> Codec<T> get(Class<T> clazz, CodecRegistry registry) {
        log.info("Inside EnumCodecProvider: {}", clazz.getSimpleName());
        if (clazz == ShipmentStatus.class) {
            return (Codec<T>) new ShipmentStatusCodec();
        }
        return null; 
    }
}

EnumCodecProvider was registered this way:

CodecRegistry codecRegistry = CodecRegistries.fromRegistries(
				CodecRegistries.fromProviders(new EnumCodecProvider()),
				MongoClients.getDefaultCodecRegistry()
		);

		MongoClientSettings settings = MongoClientSettings.builder()
				.uuidRepresentation(UuidRepresentation.STANDARD)
				.retryReads(true)
				.retryWrites(true)
				.codecRegistry(codecRegistry)
				.applyConnectionString(connectionString).build();
		return MongoClients.create(settings);

During debugging I can see that all BSON types are processed by EnumCustomProvider except my
enum ShipmentStatus:

10:32:15.538 INFO  6ad6a0f68e2d c.t.t.p.c.v.codec.EnumCodecProvider - Inside EnumCodecProvider: Document 
10:32:15.540 INFO  6ad6a0f68e2d c.t.t.p.c.v.codec.EnumCodecProvider - Inside EnumCodecProvider: MaxKey 
10:32:15.540 INFO  6ad6a0f68e2d c.t.t.p.c.v.codec.EnumCodecProvider - Inside EnumCodecProvider: BsonRegularExpression 
10:32:15.541 INFO  6ad6a0f68e2d c.t.t.p.c.v.codec.EnumCodecProvider - Inside EnumCodecProvider: Integer 
10:32:15.541 INFO  6ad6a0f68e2d c.t.t.p.c.v.codec.EnumCodecProvider - Inside EnumCodecProvider: Date 
10:32:15.541 INFO  6ad6a0f68e2d c.t.t.p.c.v.codec.EnumCodecProvider - Inside EnumCodecProvider: BsonDbPointer 
10:32:15.541 INFO  6ad6a0f68e2d c.t.t.p.c.v.codec.EnumCodecProvider - Inside EnumCodecProvider: Symbol 
10:32:15.541 INFO  6ad6a0f68e2d c.t.t.p.c.v.codec.EnumCodecProvider - Inside EnumCodecProvider: ObjectId 
10:32:15.541 INFO  6ad6a0f68e2d c.t.t.p.c.v.codec.EnumCodecProvider - Inside EnumCodecProvider: BsonTimestamp 
10:32:15.541 INFO  6ad6a0f68e2d c.t.t.p.c.v.codec.EnumCodecProvider - Inside EnumCodecProvider: MinKey 
10:32:15.541 INFO  6ad6a0f68e2d c.t.t.p.c.v.codec.EnumCodecProvider - Inside EnumCodecProvider: String 
10:32:15.541 INFO  6ad6a0f68e2d c.t.t.p.c.v.codec.EnumCodecProvider - Inside EnumCodecProvider: List 
10:32:15.542 INFO  6ad6a0f68e2d c.t.t.p.c.v.codec.EnumCodecProvider - Inside EnumCodecProvider: Binary 
10:32:15.542 INFO  6ad6a0f68e2d c.t.t.p.c.v.codec.EnumCodecProvider - Inside EnumCodecProvider: Double 
10:32:15.543 INFO  6ad6a0f68e2d c.t.t.p.c.v.codec.EnumCodecProvider - Inside EnumCodecProvider: Code 
10:32:15.543 INFO  6ad6a0f68e2d c.t.t.p.c.v.codec.EnumCodecProvider - Inside EnumCodecProvider: Long 
10:32:15.543 INFO  6ad6a0f68e2d c.t.t.p.c.v.codec.EnumCodecProvider - Inside EnumCodecProvider: Boolean 
10:32:15.543 INFO  6ad6a0f68e2d c.t.t.p.c.v.codec.EnumCodecProvider - Inside EnumCodecProvider: Decimal128 
10:32:15.543 INFO  6ad6a0f68e2d c.t.t.p.c.v.codec.EnumCodecProvider - Inside EnumCodecProvider: CodeWithScope 
10:32:15.544 INFO  6ad6a0f68e2d c.t.t.p.c.v.codec.EnumCodecProvider - Inside EnumCodecProvider: BsonUndefined

Could you give me an advice what is missing in the code? How we can force MongoDB to process the enums using custom ShipmentStatusCodec?

Thanks,
Elena

Hi @Elena_Alexandrova,

Thanks for reaching out. I tried to reproduce your results with the following test:

public class EnumCodecProviderTest {
public static void main(String[] args) {
    CodecRegistry codecRegistry = CodecRegistries.fromRegistries(
            CodecRegistries.fromProviders(new EnumCodecProvider()),
            MongoClients.getDefaultCodecRegistry()
    );

    // I'm assuming that you are using Document as the container, but it's
    // not clear from your description
    Document doc = new Document("status", ShipmentStatus.SHIPPED);

    // this is essentially what happens when using MongoClient, but avoids
    // the need to actually use a MongoClient
    String json = doc.toJson(codecRegistry.get(Document.class));

    System.out.println(json);
}

public static class EnumCodecProvider implements CodecProvider {
    @Override
    public <T> Codec<T> get(Class<T> clazz, CodecRegistry registry) {
        if (clazz == ShipmentStatus.class) {
            //noinspection unchecked
            return (Codec<T>) new ShipmentStatusCodec();
        }
        return null;
    }

}

public static class ShipmentStatusCodec implements Codec<ShipmentStatus> {
    @Override
    public ShipmentStatus decode(BsonReader reader, DecoderContext decoderContext) {
        return ShipmentStatus.valueOf(reader.readString());
    }

    @Override
    public void encode(BsonWriter writer, ShipmentStatus value, EncoderContext encoderContext) {
        writer.writeString(value.name());
    }

    @Override
    public Class<ShipmentStatus> getEncoderClass() {
        return ShipmentStatus.class;
    }
}

public enum ShipmentStatus {
    SHIPPED
}
}

but it prints

{"status": "SHIPPED"}

as expected. Can you provide a complete, minimal reproducer that demonstrates what you’re seeing?

Regards,
Jeff

2 Likes

Thanks, @Jeffrey_Yemin.
I used ReactiveMongoRepository to store my data.

Actually, I’ve already found the solution that works for me. I used the custom converters.

@Configuration
@Slf4j
public class MongoDBConfig extends AbstractReactiveMongoConfiguration {
	@Override
	protected void configureConverters(MongoCustomConversions.MongoConverterConfigurationAdapter adapter) {
		adapter.registerConverter(new ShipmentStatusReadConverter());
		adapter.registerConverter(new ShipmentStatusWriteConverter());
	}
}

@Slf4j
@Component
@WritingConverter
public class ShipmentStatusWriteConverter implements Converter<ShipmentStatus, String> {
    @Override
    public String convert(ShipmentStatus status) {
        log.info("Writing Converter called");
        return status.getValue();
    }
}

@Slf4j
@Component
@ReadingConverter
public class ShipmentStatusReadConverter implements Converter<String, ShipmentStatus> {
    @Override
    public ShipmentStatus convert(String value) {
        log.info("Reading Converter called");
        return ShipmentStatus.fromValue(value);
    }
}

@Slf4j
public enum ShipmentStatus {
    NEW("New"),                                 
    PLANNED("Planned"),
    ASSIGNED("Assigned");

    private String value;

    ShipmentStatus(String value) {
        this.value = value;
    }

    public String value() {
        return this.value;
    }

    @JsonValue
    public String getValue() {
        return value;
    }

    @JsonCreator
    public static ShipmentStatus fromValue(String value) {
        for (ShipmentStatus e : ShipmentStatus.values()) {
            if (e.value.equalsIgnoreCase(value)) {
                return e;
            }
        }
        throw new UnsupportedEnumValueException(value, ShipmentStatus.class);
    }

    @Override
    public String toString() {
        return this.value;
    }
}

This topic was automatically closed 5 days after the last reply. New replies are no longer allowed.