spring-data-neo4j icon indicating copy to clipboard operation
spring-data-neo4j copied to clipboard

handle CompositeProperty objects "like nodes"

Open xenoterracide opened this issue 4 years ago • 11 comments

https://docs.spring.io/spring-data/neo4j/docs/current/reference/html/#custom.conversions.composite-properties

should we be able to convert any map into an object using the keys as the field names and the values as the values, all the way down? isn't this exactly what Jackson and many deserialization libraries do? This seems convoluted for what is probably the most common use case. Seems like you could apply the exact same logic as you do when converting a node into an object, since aren't the properties on the node the same thing, just a map?

xenoterracide avatar Jul 05 '21 04:07 xenoterracide

There is no map attribute type inside the Neo4j database. You are free to use Jackson or any other tool in your custom conversion for composite attribute types.

michael-simons avatar Jul 05 '21 09:07 michael-simons

maybe I'm misunderstanding... I thought you could store a map inside of a database https://neo4j.com/docs/cypher-manual/4.3/syntax/maps/ and if you can't why does a CompositeProperty deserialize to a Map? since you'd have to have a map property in order to have a map on a node.

xenoterracide avatar Jul 05 '21 11:07 xenoterracide

Hi Caleb,

yes, that's a misconception I head to deal with a couple of years as well. No, Neo4j does not store Maps. It supports them as parameters, though.

Here's the list of supported property types https://neo4j.com/docs/cypher-manual/current/syntax/values/#property-types.

As opposed the list of composite types: https://neo4j.com/docs/cypher-manual/current/syntax/values/#composite-types

Cannot be stored as properties

When the core product changes or supports this, we will do to.

michael-simons avatar Jul 05 '21 11:07 michael-simons

So is a composite type only for the results of a dynamic query then? confusing, but ok, sorry

xenoterracide avatar Jul 05 '21 11:07 xenoterracide

No, you can have a map attribute on a @Node or @RelationshipProperties on the Java side / client side of things.

This will be stored than as separate properties on the graph database objects, with a common prefix by default or in any form you decompose it if you add a custom method to it.

Here's our canonical test:

https://github.com/spring-projects/spring-data-neo4j/blob/main/src/test/java/org/springframework/data/neo4j/integration/shared/conversion/ThingWithCompositeProperties.java#L73-L133

When stored, we take the graph db node (the internal representation), turn it into a map (which is a driver feature) https://github.com/spring-projects/spring-data-neo4j/blob/main/src/test/java/org/springframework/data/neo4j/integration/shared/conversion/CompositePropertiesITBase.java#L207 and make sure the contents of the map is written as single properties into the graph.

this is not about dynamic queries.

michael-simons avatar Jul 05 '21 11:07 michael-simons

ah, well what this ticket is about, was being able to (per the test) have a generic converter for SomeOtherDto instead of having to write one for every single "nested object", it seems like a simple enough problem.

xenoterracide avatar Jul 05 '21 11:07 xenoterracide

https://github.com/spring-projects/spring-data-neo4j/blob/ea28dcadea3cd42196edca8e21f8faa00a69f73a/src/test/java/org/springframework/data/neo4j/integration/shared/conversion/ThingWithCompositeProperties.java#L238

Please checkout if you use Object as Value for the interface: Neo4jPersistentPropertyToMapConverter<String, Object>

The key is the prefix (either String or Enum). And then pipe your Object instance through whatever you like.

I guess that should work.

michael-simons avatar Jul 05 '21 11:07 michael-simons

yes, the point is that you should be able to have a generic converter that does this, you shouldn't have to reimplement it for every class, there's no reason that I can think of that spring-data-neo4j couldn't provide or even automatically use such a converter behind the scene's. I was trying to figure out how you're doing Node in the first place since it should essentially be the same problem.

xenoterracide avatar Jul 05 '21 11:07 xenoterracide

any chance you could point me to the code that allows neo4j to dynamically construct a node or whatever?

xenoterracide avatar Jul 05 '21 14:07 xenoterracide

now I see the problem, while this should work for half of the conversion

/*
 * Copyright © 2021 Caleb Cushing.
 * Apache 2.0. See https://github.com/xenoterracide/brix/LICENSE
 * https://choosealicense.com/licenses/apache-2.0/#
 */
package com.xenoterracide.ppm.util.modelgraph;

import org.neo4j.driver.Value;
import org.neo4j.driver.exceptions.ClientException;
import org.springframework.data.neo4j.core.convert.Neo4jConversionService;
import org.springframework.data.neo4j.core.convert.Neo4jPersistentPropertyToMapConverter;
import org.springframework.util.ReflectionUtils;

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

import static org.neo4j.driver.Values.value;

public class MapToObjectConverter implements Neo4jPersistentPropertyToMapConverter<String, Object> {

  @Override
  public Map<String, Value> decompose(
    Object property,
    Neo4jConversionService neo4jConversionService
  ) {
    var map = new HashMap<String, Value>();
    ReflectionUtils.doWithFields(property.getClass(), (f) -> {
      var o = f.get( property );
      var name = f.getName();
      try {
        map.put( name, value( o ) );
      }
      catch ( ClientException e ) {
        map.put( name, value( this.decompose( o, neo4jConversionService ) ) );
      }
    });
    return map;
  }

  @Override
  public Object compose(
    Map<String, Value> source,
    Neo4jConversionService neo4jConversionService
  ) {

    return null;
  }
}

the other half has a problem that it doesn't actually have the type data that Spring Data Neo4j has because it's able to get that from the parent object. This leads me back to begging for a reopen, I could probably even implement it myself if I had some pointers, it could just be a different annotation.

xenoterracide avatar Jul 06 '21 23:07 xenoterracide

Hey. You what kind of object to hydrate? Sure, that makes sense.

And please, no need to beg.

Caleb Cushing @.***> schrieb am Mi. 7. Juli 2021 um 01:07:

now I see the problem, while this should work for half of the conversion

/*

  • Copyright © 2021 Caleb Cushing.
  • Apache 2.0. See https://github.com/xenoterracide/brix/LICENSE
  • https://choosealicense.com/licenses/apache-2.0/# */ package com.xenoterracide.ppm.util.modelgraph;

import org.neo4j.driver.Value; import org.neo4j.driver.exceptions.ClientException; import org.springframework.data.neo4j.core.convert.Neo4jConversionService; import org.springframework.data.neo4j.core.convert.Neo4jPersistentPropertyToMapConverter; import org.springframework.util.ReflectionUtils;

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

import static org.neo4j.driver.Values.value;

public class MapToObjectConverter implements Neo4jPersistentPropertyToMapConverter<String, Object> {

@Override

public Map<String, Value> decompose(

Object property,

Neo4jConversionService neo4jConversionService

) {

var map = new HashMap<String, Value>();

ReflectionUtils.doWithFields(property.getClass(), (f) -> {

  var o = f.get( property );

  var name = f.getName();

  try {

    map.put( name, value( o ) );

  }

  catch ( ClientException e ) {

    map.put( name, value( this.decompose( o, neo4jConversionService ) ) );

  }

});

return map;

}

@Override

public Object compose(

Map<String, Value> source,

Neo4jConversionService neo4jConversionService

) {

return null;

}

}

the other half has a problem that it doesn't actually have the type data that Spring Data Neo4j has because it's able to get that from the parent object. This leads me back to begging for a reopen, I could probably even implement it myself if I had some pointers, it could just be a different annotation.

— You are receiving this because you modified the open/close state. Reply to this email directly, view it on GitHub https://github.com/spring-projects/spring-data-neo4j/issues/2315#issuecomment-875140206, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAEAQL4XOXYU2ZHPT3H3I5TTWOEB3ANCNFSM47Z6DQPA .

--

Neo4j Germany GmbH Viktualienmarkt 8, c/o WorkRepublic, 80331 München, Germany Seat (Sitz): München; Amtsgericht München HRB 252331 Managing Directors (Geschäftsführer): Michael Kenneth Asher, Gustav Emil Eifrém

michael-simons avatar Jul 07 '21 05:07 michael-simons