Aslak
Aslak

Reputation: 139

Spring boot data Neo4j Maps with value Maps using @CompositeProperty

I'm trying to upgrade from Spring Boot 2.3.12 to 2.7.15 and are having some issues.

In 2.3.12 I had a Neo4j node with the following property with the @Properties annotation:

@Properties(prefix = "params")
var params: MutableMap<String, Any?>

In 2.7.17 the annotation @Properties has bee replaced with @CompositeProperty

@CompositeProperty(prefix = "params")
var params: MutableMap<String, Any?>

This works as long as the value is simple. My problem is that I have values in the map that is a map.

This worked fine in 2.3.12 as the properties got stored in the database like this:

params.key1: "Some string"
params.key2.mapKey1: "Some other string 1"
params.key2.mapKey2: "Some other string 2"

In 2.7.17, if I try to put a map as a value in the map just get the following error message:

Property values can only be of primitive types or arrays thereof. Encountered: Map{}.; Error code 'Neo.ClientError.Statement.TypeError'

Am I missing something, or has this feature been removed?

Yes. I can probably change my database model or replace Neo4j, but I already have a lot of data and I do not really want to migrate.

Thanks

Upvotes: 2

Views: 92

Answers (1)

meistermeier
meistermeier

Reputation: 8262

The Spring Data Neo4j version in place is stricter when it comes to types, as you have already experienced. I am not this experienced with Kotlin type handling but the general way to make this work is to provide a converter for your expected type. But since this is Any, this converter might be triggered also for properties that you don't want to convert.

I am just abstracting from the example, you have given.

@CompositeProperty(prefix = "params")
var params: MutableMap<String, CustomType?>

the converter definition:

public static class CustomTypeConverter implements GenericConverter {

    @Override
    public Set<ConvertiblePair> getConvertibleTypes() {
        Set<ConvertiblePair> convertiblePairs = new HashSet<>();
        convertiblePairs.add(new ConvertiblePair(Value.class, CustomType.class));
        convertiblePairs.add(new ConvertiblePair(CustomType.class, Value.class));
        return convertiblePairs;
    }

    @Override
    public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {

        if (StringValue.class.isAssignableFrom(sourceType.getType())) {
            return CustomType.of(((StringValue) source).asString());
        } else {
            return Values.value(((CustomType) source).getValue());
        }
    }
}

and registering it as:

@Override
public Neo4jConversions neo4jConversions() {
    return new Neo4jConversions(Collections.singleton(new CustomTypeConverter()));
}

It would be also possible to do this explicitly for the attribute, but this would require to also map the Map. On the other hand, you can keep the Any type.

More can be found in the reference: https://docs.spring.io/spring-data/neo4j/reference/appendix/conversions.html#custom.conversions

Upvotes: 0

Related Questions