Search code examples
neo4jspring-data-neo4jneo4j-ogm

Neo4j - Custom converter for field of type List


I am trying to write a custom converter for a nested object so that this object gets saved as string in Neo4j database.

I am using @Convert annotation on my field and passing ImageConverter.class which is my AttributeConverter class.

enter image description here

enter image description here

Everything works fine as expected and I am able to save string representation of Image class in Neo4j db.

However, now instead of single image I want to have List<Image> as my nested field. In this case, putting @Convert(ImageConverter.class) doesn't work.

I see that there is a class called ConverterBasedCollectionConverter which gets used when I have a field of type List<LocalDateTime.

However, I couldn't find any exammples on how to use this class in case of custom converters.

Please can anyone help me with this or if there is any other approach to use custom converter on field of type List.

I am using Neo4j (version 3.4.1) and Spring-data-neo4j (5.0.10.RELEASE) in my application. I am also using OGM.

PS: I am aware that it is advised to store nested objects as separate node establishing a relationship with parent object. However, my use case demands that the object be stored as string property and not as separate node.

Regards,

V


Solution

  • It is not so difficult as I assumed it would be.

    Given a class (snippet)

    @NodeEntity
    public class Actor {
    
      @Id @GeneratedValue
      private Long id;
    
      @Convert(MyImageListConverter.class)
      public List<MyImage> images = new ArrayList<>();
      // ....
    }
    

    with MyImage as simple as can be

    public class MyImage {
    
      public String blob;
    
      public MyImage(String blob) {
          this.blob = blob;
      }
    
      public static MyImage of(String value) {
          return new MyImage(value);
      }
    }
    

    and a converter

    public class MyImageListConverter implements AttributeConverter<List<MyImage>, String[]> {
    
      @Override
      public String[] toGraphProperty(List<MyImage> value) {
          if (value == null) {
              return null;
          }
          String[] values = new String[(value.size())];
          int i = 0;
          for (MyImage image : value) {
              values[i++] = image.blob;
          }
          return values;
        }
    
        @Override
        public List<MyImage> toEntityAttribute(String[] values) {
          List<MyImage> images = new ArrayList<>(values.length);
          for (String value : values) {
              images.add(MyImage.of(value));
          }
          return images;
        }
    }
    

    will print following debug output on save that I think is what you want:

    UNWIND {rows} as row CREATE (n:Actor) SET n=row.props RETURN row.nodeRef as ref, ID(n) as id, {type} as type with params {type=node, rows=[{nodeRef=-1, props={images=[blobb], name=Jeff}}]}

    especially the images part.

    Test method for this looks like

    @Test
    public void test() {
        Actor jeff = new Actor("Jeff");
        String blobValue = "blobb";
        jeff.images.add(new MyImage(blobValue));
        session.save(jeff);
        session.clear();
        Actor loadedActor = session.load(Actor.class, jeff.getId());
        assertThat(loadedActor.images.get(0).blob).isEqualTo(blobValue);
    
    }