From 21f989ad6e3ea0d2babc9a997f7eed657a18eb83 Mon Sep 17 00:00:00 2001
From: Johannes Mey <johannes.mey@tu-dresden.de>
Date: Fri, 25 Oct 2019 13:22:55 +0200
Subject: [PATCH] implement json pointer (built-in) and manual pointer
 treatment with two examples (json pointer and relative json pointer)

---
 build.gradle                                  |  30 ++
 src/main/jastadd/Backend.jadd                 | 276 ++++++++++++++++--
 .../org/jastadd/relast/compiler/Compiler.java |  13 +-
 src/test/jastadd/resolver/MyRefResolver.jadd  |  19 +-
 src/test/jastadd/resolver/ResolverUtils.jadd  |   5 +-
 src/test/jastadd/resolver2/ResolverUtils.jadd |   5 +-
 .../JsonPointer.jrag                          | 204 +++++++++++++
 .../Serializer.relast                         |  23 ++
 .../serializer-manual/JsonPointer.jrag        | 158 ++++++++++
 .../serializer-manual/Serializer.relast       |  23 ++
 .../serializer-pointer/Serializer.relast      |  23 ++
 .../relast/tests/SerializerManual.java        | 135 +++++++++
 .../tests/SerializerManualRelative.java       | 135 +++++++++
 .../relast/tests/SerializerPointer.java       | 137 +++++++++
 14 files changed, 1145 insertions(+), 41 deletions(-)
 create mode 100644 src/test/jastadd/serializer-manual-relative/JsonPointer.jrag
 create mode 100644 src/test/jastadd/serializer-manual-relative/Serializer.relast
 create mode 100644 src/test/jastadd/serializer-manual/JsonPointer.jrag
 create mode 100644 src/test/jastadd/serializer-manual/Serializer.relast
 create mode 100644 src/test/jastadd/serializer-pointer/Serializer.relast
 create mode 100644 src/test/java/org/jastadd/relast/tests/SerializerManual.java
 create mode 100644 src/test/java/org/jastadd/relast/tests/SerializerManualRelative.java
 create mode 100644 src/test/java/org/jastadd/relast/tests/SerializerPointer.java

diff --git a/build.gradle b/build.gradle
index 7598b77..1d96120 100644
--- a/build.gradle
+++ b/build.gradle
@@ -246,6 +246,36 @@ task compileSerializerDefaultNamesTest(type: RelastTest) {
     moreInputFiles 'src/test/jastadd/Utils.jadd'
 }
 
+task compileSerializerPointerTest(type: RelastTest) {
+    verbose = true
+    resolverHelper = true
+    relastFiles 'src/test/jastadd/serializer-pointer/Serializer.relast'
+    grammarName = 'src/test/jastadd/serializer-pointer/Serializer'
+    serializer = 'jackson-json-pointer'
+    packageName = 'pointer.serializer.ast'
+    moreInputFiles 'src/test/jastadd/Utils.jadd'
+}
+
+task compileSerializerManualTest(type: RelastTest) {
+    verbose = true
+    resolverHelper = true
+    relastFiles 'src/test/jastadd/serializer-manual/Serializer.relast'
+    grammarName = 'src/test/jastadd/serializer-manual/Serializer'
+    serializer = 'jackson-manual-references'
+    packageName = 'manual.serializer.ast'
+    moreInputFiles 'src/test/jastadd/Utils.jadd', 'src/test/jastadd/serializer-manual/JsonPointer.jrag'
+}
+
+task compileSerializerManualRelativeTest(type: RelastTest) {
+    verbose = true
+    resolverHelper = true
+    relastFiles 'src/test/jastadd/serializer-manual-relative/Serializer.relast'
+    grammarName = 'src/test/jastadd/serializer-manual-relative/Serializer'
+    serializer = 'jackson-manual-references'
+    packageName = 'manual.relative.serializer.ast'
+    moreInputFiles 'src/test/jastadd/Utils.jadd', 'src/test/jastadd/serializer-manual-relative/JsonPointer.jrag'
+}
+
 test {
     outputs.upToDateWhen { false }
     useJUnitPlatform()
diff --git a/src/main/jastadd/Backend.jadd b/src/main/jastadd/Backend.jadd
index f5d69d3..2f7490b 100644
--- a/src/main/jastadd/Backend.jadd
+++ b/src/main/jastadd/Backend.jadd
@@ -5,6 +5,8 @@ aspect BackendAbstractGrammar {
 
   public static boolean ASTNode.resolverHelper = false;
   public static boolean ASTNode.serializer = false;
+  public static boolean ASTNode.jsonPointer = false;
+  public static boolean ASTNode.manualReferences = false;
   public static boolean ASTNode.useJastAddNames = false;
 
   public String Program.generateAbstractGrammar() {
@@ -823,13 +825,73 @@ aspect NameResolutionHelper {
       r.generateContextDependentNameResolution(sb);
     }
 
-    if (resolverHelper) {
+    if (resolverHelper || ASTNode.jsonPointer || ASTNode.manualReferences) {
       for (TypeDecl decl : getTypeDeclList()) {
         decl.generateContextIndependentNameResolution(sb);
         sb.append("\n");
       }
     }
     sb.append("}\n\n");
+
+    if (ASTNode.manualReferences) {
+      sb.append("aspect RefCreatorStubs {\n\n");
+
+      for (Relation r: getRelations()) {
+        r.generateContextDependentRefCreation(sb);
+      }
+
+      generateGenericRefCreation(sb);
+      sb.append("\n");
+
+      for (TypeDecl decl : getTypeDeclList()) {
+        decl.generateContextIndependentRefCreation(sb);
+        sb.append("\n");
+      }
+
+      sb.append("}\n\n");
+    }
+  }
+
+  public void Program.generateGenericRefCreation(StringBuilder sb) {
+    sb.append(ind(1) + "// generic reference creation\n");
+    sb.append(ind(1) + "syn String ASTNode.createReference();\n");
+    sb.append(ind(1) + "eq ASTNode.createReference() {\n");
+    sb.append(ind(2) + "throw new RuntimeException(\"Generic reference creation not implemented.\");\n");
+    sb.append(ind(1) + "}\n");
+  }
+
+  public void Relation.generateContextDependentRefCreation(StringBuilder sb) {
+    sb.append(ind(1) + "// " + prettyPrint() + "\n");
+    getDirection().generateContextDependentRefCreation(sb);
+    sb.append("\n");
+  }
+
+  public abstract void Direction.generateContextDependentRefCreation(StringBuilder sb);
+  public void RightDirection.generateContextDependentRefCreation(StringBuilder sb) {
+    relation().getLeft().generateContextDependentRefCreation(sb);
+  }
+  public void LeftDirection.generateContextDependentRefCreation(StringBuilder sb) {
+    relation().getRight().generateContextDependentRefCreation(sb);
+  }
+  public void Bidirectional.generateContextDependentRefCreation(StringBuilder sb) {
+    relation().getLeft().generateContextDependentRefCreation(sb);
+    relation().getRight().generateContextDependentRefCreation(sb);
+  }
+
+  public void RelationComponent.generateContextDependentRefCreation(StringBuilder sb) {
+    sb.append(ind(1) + "// context-dependent reference creation\n");
+    sb.append(ind(1) + "syn String " + toTypeDecl() + ".createRefTo" + nameCapitalized() + "(" + ofTypeDecl() + " target) {\n");
+    sb.append(ind(2) + "// default to context-independent reference creation\n");
+    sb.append(ind(2) + "return target.createReference();\n");
+    sb.append(ind(1) + "}\n");
+  }
+
+  public void TypeDecl.generateContextIndependentRefCreation(StringBuilder sb) {
+    sb.append(ind(1) + "// context-independent reference creation\n");
+    sb.append(ind(1) + "eq " + getID() + ".createReference() {\n");
+    sb.append(ind(2) + "// default to generic reference creation\n");
+    sb.append(ind(2) + "return super.createReference();\n");
+    sb.append(ind(1) + "}\n");
   }
 
   public void Program.generateRewriteToSuperTypeStub(StringBuilder sb) {
@@ -894,8 +956,12 @@ aspect NameResolutionHelper {
     sb.append(ind(1) + "// context-independent name resolution\n");
     sb.append(ind(1) + "uncache ASTNode.globallyResolve" + getID() + "ByToken(String id);\n");
     sb.append(ind(1) + "syn " + getID() + " ASTNode.globallyResolve" + getID() + "ByToken(String id) {\n");
-    if (serializer) {
-      sb.append(ind(2) + "return (" + getID() + ") globallyResolveASTNodeByUID(id);\n");
+    if (serializer && !manualReferences) {
+      if (jsonPointer) {
+        sb.append(ind(2) + "return (" + getID() + ") resolveJsonPointer(id);\n");
+      } else {
+        sb.append(ind(2) + "return (" + getID() + ") globallyResolveASTNodeByUID(id);\n");
+      }
     } else {
       sb.append(ind(2) + "// perform context independent name resolution here using the id\n");
       sb.append(ind(2) + "throw new RuntimeException(\"Context-independent name resolution for " + getID() + " not implemented.\");\n");
@@ -1002,11 +1068,18 @@ aspect Serializer {
   protected static String ASTNode.jsonTypeKey = "type";
   protected static String ASTNode.jsonNodeType = "com.fasterxml.jackson.databind.JsonNode";
   protected static String ASTNode.jsonNodeTypeAccessor = ".get(\"" + jsonTypeKey + "\").asText()";
-  public String Program.generateSerializer() {
+  public String Program.generateJacksonSerializer() {
     StringBuilder sb = new StringBuilder();
     generateFromJson(sb);
     generateToJson(sb);
-    writeUID(sb);
+    if (jsonPointer) {
+      writeJsonPointer(sb);
+    } else if (manualReferences) {
+      // TODO
+    } else {
+      writeUID(sb);
+    }
+
     return sb.toString();
   }
 
@@ -1083,9 +1156,11 @@ aspect Serializer {
     sb.append(ind(3) + "} else {\n");
     sb.append(ind(4) + "g.writeObjectFieldStart(fieldName);\n");
     sb.append(ind(3) + "}\n");
-    sb.append(ind(3) + "g.writeStringField(\"" + jsonTypeKey + "\", \"" + getID() + "\");\n");
-    sb.append(ind(3) + "if (unique$Id() == null) throw new SerializationException(\"The unique identifier of " + getID() + " is missing.\");\n");
-    sb.append(ind(3) + "g.writeStringField(\"id\", unique$Id());\n");
+    if (!jsonPointer && !manualReferences) {
+      sb.append(ind(3) + "g.writeStringField(\"" + jsonTypeKey + "\", \"" + getID() + "\");\n");
+      sb.append(ind(3) + "if (unique$Id() == null) throw new SerializationException(\"The unique identifier of " + getID() + " is missing.\");\n");
+      sb.append(ind(3) + "g.writeStringField(\"id\", unique$Id());\n");
+    }
     if (componentsTransitive().size() > 0) {
       sb.append(ind(3) + "g.writeObjectFieldStart(\"children\");\n");
       for (Component child : componentsTransitive()) {
@@ -1196,18 +1271,42 @@ aspect Serializer {
 
   public void OneRelationComponent.serialize(StringBuilder sb, int indent) {
     if (useJastAddNames){
-      sb.append(ind(indent) + "g.writeStringField(\""+getID()+"\", get" + getID() + "().unique$Id());\n");
+      if (jsonPointer) {
+        sb.append(ind(indent) + "g.writeStringField(\"" + getID() + "\", get" + getID() + "().jsonPointer());\n");
+      } else if (manualReferences) {
+        sb.append(ind(indent) + "g.writeStringField(\"" + getID() + "\", createRefTo" + getID() + "(" + getID() + "()));\n");
+      } else {
+        sb.append(ind(indent) + "g.writeStringField(\"" + getID() + "\", get" + getID() + "().unique$Id());\n");
+      }
     } else {
-      sb.append(ind(indent) + "g.writeStringField(\""+getID()+"\", " + getID() + "().unique$Id());\n");
+      if (jsonPointer) {
+        sb.append(ind(indent) + "g.writeStringField(\"" + getID() + "\", " + getID() + "().jsonPointer());\n");
+      } else if (manualReferences) {
+        sb.append(ind(indent) + "g.writeStringField(\"" + getID() + "\", createRefTo" + getID() + "(" + getID() + "()));\n");
+      } else {
+        sb.append(ind(indent) + "g.writeStringField(\"" + getID() + "\", " + getID() + "().unique$Id());\n");
+      }
     }
   }
 
   public void OptionalRelationComponent.serialize(StringBuilder sb, int indent) {
     sb.append(ind(indent) + "if (has" + nameCapitalized() + "()) {\n");
     if (useJastAddNames){
-      sb.append(ind(indent + 1) + "g.writeStringField(\"" + getID() + "\", get" + getID() + "().unique$Id());\n");
+      if (jsonPointer) {
+        sb.append(ind(indent + 1) + "g.writeStringField(\"" + getID() + "\", get" + getID() + "().jsonPointer());\n");
+      } else if (manualReferences) {
+        sb.append(ind(indent + 1) + "g.writeStringField(\"" + getID() + "\", get" + getID() + "().createReference());\n");
+      } else {
+        sb.append(ind(indent + 1) + "g.writeStringField(\"" + getID() + "\", get" + getID() + "().unique$Id());\n");
+      }
     } else {
-      sb.append(ind(indent + 1) + "g.writeStringField(\"" + getID() + "\", " + getID() + "().unique$Id());\n");
+      if (jsonPointer) {
+        sb.append(ind(indent + 1) + "g.writeStringField(\"" + getID() + "\", " + getID() + "().jsonPointer());\n");
+      } else if (manualReferences) {
+        sb.append(ind(indent + 1) + "g.writeStringField(\"" + getID() + "\", " + "createRefTo" + getID() + "(" + getID()  + "()));\n");
+      } else {
+        sb.append(ind(indent + 1) + "g.writeStringField(\"" + getID() + "\", " + getID() + "().unique$Id());\n");
+      }
     }
     sb.append(ind(indent) + "}\n");
   }
@@ -1219,7 +1318,14 @@ aspect Serializer {
     } else {
       sb.append(ind(indent) + "for (" + ofTypeDecl().getID() + " child : " + getID() + "()) {\n");
     }
-    sb.append(ind(indent + 1) + "g.writeString(child.unique$Id());\n");
+    if (jsonPointer) {
+      sb.append(ind(indent + 1) + "g.writeString(child.jsonPointer());\n");
+    }  else if (manualReferences) {
+      sb.append(ind(indent + 1) + "g.writeString(createRefTo" + getID() + "(child));\n");
+    }else {
+      sb.append(ind(indent + 1) + "g.writeString(child.unique$Id());\n");
+    }
+
     sb.append(ind(indent) + "}\n");
     sb.append(ind(indent) + "g.writeEndArray();\n");
   }
@@ -1256,10 +1362,12 @@ aspect Serializer {
       sb.append(ind(2) + "element = new " + getID() + "();\n");
     }
 
-    // deserialize id
-    sb.append(ind(2) + "if (node.has(\"id\")) {\n");
-    sb.append(ind(3) + "element.unique$Id = node.get(\"id\").asText();\n");
-    sb.append(ind(2) + "}\n");
+    if (!jsonPointer && !manualReferences) {
+      // deserialize id
+      sb.append(ind(2) + "if (node.has(\"id\")) {\n");
+      sb.append(ind(3) + "element.unique$Id = node.get(\"id\").asText();\n");
+      sb.append(ind(2) + "}\n");
+    }
 
     // deserialize containment children
     if (componentsTransitive().size() > 0) {
@@ -1361,6 +1469,140 @@ aspect Serializer {
     }
   }
 
+  public void Component.writeJsonPointer(StringBuilder sb) {
+    // do nothing for other components
+  }
+
+  public void NormalComponent.writeJsonPointer(StringBuilder sb) {
+    sb.append(ind(1) + "eq " + enclosingTypeDecl().getID() + (useJastAddNames?".get":".") + getID() + "().jsonPointerInh() {\n");
+    sb.append(ind(2) + "return this.jsonPointer() + \"/children/" + getID() + "\";\n");
+    sb.append(ind(1) + "}\n");
+  }
+
+  public void ListComponent.writeJsonPointer(StringBuilder sb) {
+    sb.append(ind(1) + "eq " + enclosingTypeDecl().getID() + (useJastAddNames?".get":".") + getID() + "(int index).jsonPointerInh() {\n");
+    sb.append(ind(2) + "return this.jsonPointer() + \"/children/" + getID() + "/\" + index;\n");
+    sb.append(ind(1) + "}\n");
+  }
+
+  public void OptComponent.writeJsonPointer(StringBuilder sb) {
+    sb.append(ind(1) + "eq " + enclosingTypeDecl().getID() + (useJastAddNames?".get":".") + getID() + "().jsonPointerInh() {\n");
+    sb.append(ind(2) + "return this.jsonPointer() + \"/children/" + getID() + "\";\n");
+    sb.append(ind(1) + "}\n");
+  }
+
+  public void NTAComponent.writeJsonPointer(StringBuilder sb) {
+    sb.append(ind(1) + "eq " + enclosingTypeDecl().getID() + (useJastAddNames?".get":".") + getID() + "().jsonPointerInh() {\n");
+    sb.append(ind(2) + "return this.jsonPointer() + \"/children/" + getID() + "\";\n");
+    sb.append(ind(1) + "}\n");
+  }
+
+  public void NTAListComponent.writeJsonPointer(StringBuilder sb) {
+    sb.append(ind(1) + "eq " + enclosingTypeDecl().getID() + (useJastAddNames?".get":".") + getID() + "(int index).jsonPointerInh() {\n");
+    sb.append(ind(2) + "return this.jsonPointer() + \"/children/" + getID() + "/\" + index;\n");
+    sb.append(ind(1) + "}\n");
+  }
+
+  public void NTAOptComponent.writeJsonPointer(StringBuilder sb) {
+    sb.append(ind(1) + "eq " + enclosingTypeDecl().getID() + (useJastAddNames?".get":".") + getID() + "().jsonPointerInh() {\n");
+    sb.append(ind(2) + "return this.jsonPointer() + \"/children/" + getID() + "\";\n");
+    sb.append(ind(1) + "}\n");
+  }
+
+  public void Component.resolveJsonPointer(StringBuilder sb) {
+    // do nothing for other components
+  }
+
+  public void NormalComponent.resolveJsonPointer(StringBuilder sb) {
+    sb.append(ind(4) + "case \"" + getID() + "\":\n");
+    sb.append(ind(5) + "return get" + getID() + "().resolveJsonPointer(pointer, index + 2);\n");
+  }
+
+  public void ListComponent.resolveJsonPointer(StringBuilder sb) {
+    sb.append(ind(4) + "case \"" + getID() + "\":\n");
+    sb.append(ind(5) + "return get" + getID() + "(Integer.valueOf(pointer[index + 2])).resolveJsonPointer(pointer, index + 3);\n");
+  }
+
+  public void OptComponent.resolveJsonPointer(StringBuilder sb) {
+    sb.append(ind(4) + "case \"" + getID() + "\":\n");
+    sb.append(ind(5) + "return get" + getID() + "().resolveJsonPointer(pointer, index + 2);\n");
+  }
+
+  public void NTAComponent.resolveJsonPointer(StringBuilder sb) {
+    sb.append(ind(4) + "case \"" + getID() + "\":\n");
+    sb.append(ind(5) + "return get" + getID() + "().resolveJsonPointer(pointer, index + 2);\n");
+  }
+
+  public void NTAListComponent.resolveJsonPointer(StringBuilder sb) {
+    sb.append(ind(4) + "case \"" + getID() + "\":\n");
+    sb.append(ind(5) + "return get" + getID() + "(Integer.valueOf(pointer[index + 2])).resolveJsonPointer(pointer, index + 3);\n");
+  }
+
+  public void NTAOptComponent.resolveJsonPointer(StringBuilder sb) {
+    sb.append(ind(4) + "case \"" + getID() + "\":\n");
+    sb.append(ind(5) + "return get" + getID() + "().resolveJsonPointer(pointer, index + 2);\n");
+  }
+
+  public void TypeDecl.resolveJsonPointer(StringBuilder sb) {
+    sb.append(ind(1) + "eq " + getID() + ".resolveJsonPointer(String[] pointer, int index) {\n");
+    sb.append(ind(2) + "if (pointer.length == 0 || pointer.length == index) {\n");
+    sb.append(ind(3) + "return this;\n");
+    sb.append(ind(2) + "} else if (pointer.length == index + 1) {\n");
+    sb.append(ind(3) + "throw new RuntimeException(\"there is only one child called \" + pointer[index]);\n");
+    sb.append(ind(2) + "} else {\n");
+    sb.append(ind(3) + "switch (pointer[index + 1]) {\n");
+
+    for (Component c: getComponentList()) {
+      c.resolveJsonPointer(sb);
+    }
+
+    sb.append(ind(4) + "default:\n");
+    sb.append(ind(5) + "return super.resolveJsonPointer(pointer, index);\n");
+    sb.append(ind(3) + "}\n");
+    sb.append(ind(2) + "}\n");
+    sb.append(ind(1) + "}\n");
+  }
+
+  public void Program.writeJsonPointer(StringBuilder sb) {
+    sb.append("aspect JsonPointer {\n");
+
+    sb.append(ind(1) + "syn String ASTNode.jsonPointer() {\n");
+    sb.append(ind(2) + "if (getParent() == null) {\n");
+    sb.append(ind(3) + "return \"\";\n");
+    sb.append(ind(2) + "} else {\n");
+    sb.append(ind(3) + "return jsonPointerInh();\n");
+    sb.append(ind(2) + "}\n");
+    sb.append(ind(1) + "}\n");
+    sb.append(ind(1) + "inh String ASTNode.jsonPointerInh();\n");
+
+    // define json pointer for each component
+    for (TypeDecl td: getTypeDeclList()) {
+      for (Component c: td.getComponentList()) {
+        c.writeJsonPointer(sb);
+      }
+    }
+
+    sb.append(ind(1) + "syn ASTNode ASTNode.resolveJsonPointer(String pointer) = root().resolveJsonPointer(pointer.split(\"/\"), 1);\n");
+    sb.append(ind(1) + "ASTNode ASTNode.root() {\n");
+    sb.append(ind(2) + "if (getParent() == null) return this;\n");
+    sb.append(ind(2) + "else return getParent().root();\n");
+    sb.append(ind(1) + "}\n");
+    sb.append(ind(1) + "syn ASTNode ASTNode.resolveJsonPointer(String[] pointer, int index) {\n");
+    sb.append(ind(2) + "if (index < pointer.length) {\n");
+    sb.append(ind(3) + "throw new RuntimeException(\"found wrong child  \" + pointer[index + 1] + \" in class \" + this.getClass().getSimpleName());\n");
+    sb.append(ind(2) + "} else {\n");
+    sb.append(ind(3) + "throw new RuntimeException(\"Cannot resolve JSON pointer for class \" + this.getClass().getSimpleName());\n");
+    sb.append(ind(2) + "}\n");
+    sb.append(ind(1) + "}\n");
+
+    // resolve JSON pointers in each nonterminal
+    for (TypeDecl td: getTypeDeclList()) {
+      td.resolveJsonPointer(sb);
+    }
+
+    sb.append("}\n");
+  }
+
   public void Program.writeUID(StringBuilder sb) {
     sb.append("aspect UID {\n");
     sb.append(ind(1) + "class UIDProvider {\n");
diff --git a/src/main/java/org/jastadd/relast/compiler/Compiler.java b/src/main/java/org/jastadd/relast/compiler/Compiler.java
index 22b6168..80a0db9 100644
--- a/src/main/java/org/jastadd/relast/compiler/Compiler.java
+++ b/src/main/java/org/jastadd/relast/compiler/Compiler.java
@@ -89,8 +89,17 @@ public class Compiler {
           ASTNode.serializer = true;
           switch (optionSerializer.getValue()) {
             case "jackson":
-              writeToFile(grammarName + "Serializer.jadd", p.generateSerializer());
+              writeToFile(grammarName + "Serializer.jadd", p.generateJacksonSerializer());
               break;
+            case "jackson-json-pointer":
+              ASTNode.jsonPointer = true;
+              writeToFile(grammarName + "Serializer.jadd", p.generateJacksonSerializer());
+              break;
+            case "jackson-manual-references":
+              ASTNode.manualReferences = true;
+              writeToFile(grammarName + "Serializer.jadd", p.generateJacksonSerializer());
+              break;
+
           }
         }
 
@@ -151,7 +160,7 @@ public class Compiler {
     optionResolverHelper = addOption(new FlagOption("resolverHelper", "create a subtype for each type containing a string that can be used to resolve the type later"));
     optionJastAddList = addOption(new StringOption("jastAddList", "set the name of the List type in JastAdd (has to match the option '--List' or its default List)"));
     optionUseJastaddNames = addOption(new FlagOption("useJastAddNames", "generate names in the form of addX, removeX and setX. If omitted, the default, original naming scheme resulting in addToX, removeFromX and setX will be used."));
-    optionSerializer = addOption(new EnumOption("serializer", "generate a (de-)serializer", Arrays.asList("jackson"), "jackson"));
+    optionSerializer = addOption(new EnumOption("serializer", "generate a (de-)serializer", Arrays.asList("jackson", "jackson-json-pointer", "jackson-manual-references"), "jackson"));
     optionQuiet = addOption(new FlagOption("quiet", "do not output anything on stdout"));
   }
 
diff --git a/src/test/jastadd/resolver/MyRefResolver.jadd b/src/test/jastadd/resolver/MyRefResolver.jadd
index fa585c9..1a22700 100644
--- a/src/test/jastadd/resolver/MyRefResolver.jadd
+++ b/src/test/jastadd/resolver/MyRefResolver.jadd
@@ -1,22 +1,9 @@
 aspect MyRewrites {
 
   // context-independent name resolution
-  refine RefResolverStubs eq ASTNode.globallyResolveNamedElementByToken(String id) {
-    System.out.println("resolving " + id + " to " + root().findNamedElement(id));
-    return root().findNamedElement(id);
-  }
-
-  // context-independent name resolution
-  refine RefResolverStubs eq ASTNode.globallyResolveAByToken(String id) {
-    System.out.println("resolving " + id + " to " + root().findNamedElement(id));
-    return root().findA(id);
-  }
-
-  // context-independent name resolution
-  refine RefResolverStubs eq ASTNode.globallyResolveBByToken(String id) {
-    System.out.println("resolving " + id + " to " + root().findNamedElement(id));
-    return root().findB(id);
-  }
+  refine RefResolverStubs eq ASTNode.globallyResolveNamedElementByToken(String id) = root().findNamedElement(id);
+  refine RefResolverStubs eq ASTNode.globallyResolveAByToken(String id) = root().findA(id);
+  refine RefResolverStubs eq ASTNode.globallyResolveBByToken(String id) = root().findB(id);
 
 }
 
diff --git a/src/test/jastadd/resolver/ResolverUtils.jadd b/src/test/jastadd/resolver/ResolverUtils.jadd
index a8d5473..105d50e 100644
--- a/src/test/jastadd/resolver/ResolverUtils.jadd
+++ b/src/test/jastadd/resolver/ResolverUtils.jadd
@@ -1,8 +1,7 @@
 aspect Utils {
 
   inh Root ASTNode.root();
-  eq Root.getA(int i).root() = this;
-  eq Root.getB(int i).root() = this;
+  eq Root.getChild().root() = this;
 
   syn NamedElement Root.findNamedElement(String name) {
     for (A a : getAList()) {
@@ -35,4 +34,4 @@ aspect Utils {
     }
     return null;
   }
-}
\ No newline at end of file
+}
diff --git a/src/test/jastadd/resolver2/ResolverUtils.jadd b/src/test/jastadd/resolver2/ResolverUtils.jadd
index a8d5473..105d50e 100644
--- a/src/test/jastadd/resolver2/ResolverUtils.jadd
+++ b/src/test/jastadd/resolver2/ResolverUtils.jadd
@@ -1,8 +1,7 @@
 aspect Utils {
 
   inh Root ASTNode.root();
-  eq Root.getA(int i).root() = this;
-  eq Root.getB(int i).root() = this;
+  eq Root.getChild().root() = this;
 
   syn NamedElement Root.findNamedElement(String name) {
     for (A a : getAList()) {
@@ -35,4 +34,4 @@ aspect Utils {
     }
     return null;
   }
-}
\ No newline at end of file
+}
diff --git a/src/test/jastadd/serializer-manual-relative/JsonPointer.jrag b/src/test/jastadd/serializer-manual-relative/JsonPointer.jrag
new file mode 100644
index 0000000..6a32e35
--- /dev/null
+++ b/src/test/jastadd/serializer-manual-relative/JsonPointer.jrag
@@ -0,0 +1,204 @@
+aspect JsonPointer {
+
+  refine RefCreatorStubs eq A.createRefToDi1(B target) = createReferenceTo(target);
+  refine RefCreatorStubs eq A.createRefToDi2(B target) = createReferenceTo(target);
+  refine RefCreatorStubs eq A.createRefToDi3(B target) = createReferenceTo(target);
+  refine RefCreatorStubs eq A.createRefToBi1(B target) = createReferenceTo(target);
+  refine RefCreatorStubs eq B.createRefToBi1(A target) = createReferenceTo(target);
+  refine RefCreatorStubs eq A.createRefToBi2(B target) = createReferenceTo(target);
+  refine RefCreatorStubs eq B.createRefToBi2(A target) = createReferenceTo(target);
+  refine RefCreatorStubs eq A.createRefToBi3(B target) = createReferenceTo(target);
+  refine RefCreatorStubs eq B.createRefToBi3(A target) = createReferenceTo(target);
+  refine RefCreatorStubs eq A.createRefToBi5(B target) = createReferenceTo(target);
+  refine RefCreatorStubs eq B.createRefToBi5(A target) = createReferenceTo(target);
+  refine RefCreatorStubs eq A.createRefToBi6(B target) = createReferenceTo(target);
+  refine RefCreatorStubs eq B.createRefToBi6(A target) = createReferenceTo(target);
+  refine RefCreatorStubs eq A.createRefToBi9(B target) = createReferenceTo(target);
+  refine RefCreatorStubs eq B.createRefToBi9(A target) = createReferenceTo(target);
+  refine RefCreatorStubs eq Root.createRefToD(D target) = createReferenceTo(target);
+  refine RefCreatorStubs eq D.createRefToRoot(Root target) = createReferenceTo(target);
+
+  syn String ASTNode.createReferenceTo(ASTNode target) {
+      // find common subtree
+      java.util.List<ASTNode> myParents = parents();
+      java.util.List<ASTNode> targetParents = target.parents();
+      int minSize = Math.min(myParents.size(), targetParents.size());
+      if (minSize > 0) {
+        int commonDepth = 0;
+        while (commonDepth < minSize && myParents.get(commonDepth) == targetParents.get(commonDepth)) {
+          commonDepth ++;
+        }
+
+        int stepsUp = 0;
+        for (int i = commonDepth; i < myParents.size(); i++) {
+          stepsUp += (myParents.get(i).inList()) ? 3 : 2;
+        }
+
+        return stepsUp + "#" + target.jsonPointerFrom(myParents.get(commonDepth-1));
+      } else {
+        return (myParents.size() == 0) ? target.jsonPointerFrom(this) : myParents.size() + "#";
+      }
+  }
+
+  syn boolean ASTNode.inList() = (getParent() == null) ? false : inListInh();
+
+  inh boolean ASTNode.inListInh();
+  eq Root.getA().inListInh() = true;
+  eq Root.getB().inListInh() = true;
+  eq Root.getC().inListInh() = false;
+  eq C.getD1().inListInh() = false;
+  eq C.getD2().inListInh() = false;
+  eq C.getD3().inListInh() = true;
+
+
+  syn ASTNode ASTNode.goUp(int steps) = steps > 0 ? goUpInh(steps) : this;
+
+  inh ASTNode ASTNode.goUpInh(int steps);
+  eq Root.getA().goUpInh(int steps) = goUp(steps-3);
+  eq Root.getB().goUpInh(int steps) = goUp(steps-3);
+  eq Root.getC().goUpInh(int steps) = goUp(steps-2);
+  eq C.getD1().goUpInh(int steps) = goUp(steps-2);
+  eq C.getD2().goUpInh(int steps) = goUp(steps-2);
+  eq C.getD3().goUpInh(int steps) = goUp(steps-3);
+
+  /**
+   * return the parent from root to the current node
+   */
+  syn java.util.List<ASTNode> ASTNode.parents() {
+    java.util.List<ASTNode> result = new java.util.ArrayList<>();
+    if (getParent() != null) {
+      result.addAll(getParent().parents());
+      if (!(this instanceof List) && !(this instanceof Opt)) result.add(this);
+    } else {
+      // add the root
+      result.add(this);
+    }
+    return result;
+  }
+
+  syn String ASTNode.jsonPointerFrom(ASTNode p) = (getParent() == null || this == p) ? "" : jsonPointerInh(p);
+
+  inh String ASTNode.jsonPointerInh(ASTNode p);
+  eq Root.A(int index).jsonPointerInh(ASTNode p) = this.jsonPointerFrom(p) + "/children/A/" + index;
+  eq Root.B(int index).jsonPointerInh(ASTNode p) = this.jsonPointerFrom(p) + "/children/B/" + index;
+  eq Root.C().jsonPointerInh(ASTNode p)          = this.jsonPointerFrom(p) + "/children/C";
+  eq C.D1().jsonPointerInh(ASTNode p)            = this.jsonPointerFrom(p) + "/children/D1";
+  eq C.D2().jsonPointerInh(ASTNode p)            = this.jsonPointerFrom(p) + "/children/D2";
+  eq C.D3(int index).jsonPointerInh(ASTNode p)   = this.jsonPointerFrom(p) + "/children/D3/" + index;
+
+  // context-independent name resolution
+  refine RefResolverStubs eq ASTNode.globallyResolveRootByToken(String id) = (Root) resolveJsonPointer(id);
+  refine RefResolverStubs eq ASTNode.globallyResolveAByToken(String id) = (A) resolveJsonPointer(id);
+  refine RefResolverStubs eq ASTNode.globallyResolveBByToken(String id) = (B) resolveJsonPointer(id);
+  refine RefResolverStubs eq ASTNode.globallyResolveCByToken(String id) = (C) resolveJsonPointer(id);
+  refine RefResolverStubs eq ASTNode.globallyResolveDByToken(String id) = (D) resolveJsonPointer(id);
+  refine RefResolverStubs eq ASTNode.globallyResolveNamedElementByToken(String id) = (NamedElement) resolveJsonPointer(id);
+
+  syn ASTNode ASTNode.resolveJsonPointer(String pointer) {
+    if (pointer.isEmpty() || pointer.startsWith("/")) {
+      return root().resolveJsonPointer(pointer.split("/"),1);
+    } else {
+      // a lot of assumptions here...
+      int depth = Integer.valueOf(pointer.split("#")[0]);
+      ASTNode result = goUp(depth);
+      return result.resolveJsonPointer(pointer.split("/"),1);
+    }
+  }
+
+  ASTNode ASTNode.root() {
+    if (getParent() == null) return this;
+    else return getParent().root();
+  }
+
+  syn ASTNode ASTNode.resolveJsonPointer(String[] pointer, int index) {
+    if (index < pointer.length) {
+      throw new RuntimeException("found wrong child  " + pointer[index + 1] + " in class " + this.getClass().getSimpleName());
+    } else {
+      throw new RuntimeException("Cannot resolve JSON pointer for class " + this.getClass().getSimpleName());
+    }
+  }
+  eq Root.resolveJsonPointer(String[] pointer, int index) {
+    if (pointer.length == 0 || pointer.length == index) {
+      return this;
+    } else if (pointer.length == index + 1) {
+      throw new RuntimeException("there is only one child called " + pointer[index]);
+    } else {
+      switch (pointer[index + 1]) {
+        case "A":
+          return getA(Integer.valueOf(pointer[index + 2])).resolveJsonPointer(pointer, index + 3);
+        case "B":
+          return getB(Integer.valueOf(pointer[index + 2])).resolveJsonPointer(pointer, index + 3);
+        case "C":
+          return getC().resolveJsonPointer(pointer, index + 2);
+        default:
+          return super.resolveJsonPointer(pointer, index);
+      }
+    }
+  }
+  eq A.resolveJsonPointer(String[] pointer, int index) {
+    if (pointer.length == 0 || pointer.length == index) {
+      return this;
+    } else if (pointer.length == index + 1) {
+      throw new RuntimeException("there is only one child called " + pointer[index]);
+    } else {
+      switch (pointer[index + 1]) {
+        default:
+          return super.resolveJsonPointer(pointer, index);
+      }
+    }
+  }
+  eq B.resolveJsonPointer(String[] pointer, int index) {
+    if (pointer.length == 0 || pointer.length == index) {
+      return this;
+    } else if (pointer.length == index + 1) {
+      throw new RuntimeException("there is only one child called " + pointer[index]);
+    } else {
+      switch (pointer[index + 1]) {
+        default:
+          return super.resolveJsonPointer(pointer, index);
+      }
+    }
+  }
+  eq C.resolveJsonPointer(String[] pointer, int index) {
+    if (pointer.length == 0 || pointer.length == index) {
+      return this;
+    } else if (pointer.length == index + 1) {
+      throw new RuntimeException("there is only one child called " + pointer[index]);
+    } else {
+      switch (pointer[index + 1]) {
+        case "D1":
+          return getD1().resolveJsonPointer(pointer, index + 2);
+        case "D2":
+          return getD2().resolveJsonPointer(pointer, index + 2);
+        case "D3":
+          return getD3(Integer.valueOf(pointer[index + 2])).resolveJsonPointer(pointer, index + 3);
+        default:
+          return super.resolveJsonPointer(pointer, index);
+      }
+    }
+  }
+  eq D.resolveJsonPointer(String[] pointer, int index) {
+    if (pointer.length == 0 || pointer.length == index) {
+      return this;
+    } else if (pointer.length == index + 1) {
+      throw new RuntimeException("there is only one child called " + pointer[index]);
+    } else {
+      switch (pointer[index + 1]) {
+        default:
+          return super.resolveJsonPointer(pointer, index);
+      }
+    }
+  }
+  eq NamedElement.resolveJsonPointer(String[] pointer, int index) {
+    if (pointer.length == 0 || pointer.length == index) {
+      return this;
+    } else if (pointer.length == index + 1) {
+      throw new RuntimeException("there is only one child called " + pointer[index]);
+    } else {
+      switch (pointer[index + 1]) {
+        default:
+          return super.resolveJsonPointer(pointer, index);
+      }
+    }
+  }
+}
diff --git a/src/test/jastadd/serializer-manual-relative/Serializer.relast b/src/test/jastadd/serializer-manual-relative/Serializer.relast
new file mode 100644
index 0000000..0e850a7
--- /dev/null
+++ b/src/test/jastadd/serializer-manual-relative/Serializer.relast
@@ -0,0 +1,23 @@
+
+Root ::= A* B* C;
+A:NamedElement;
+B:NamedElement;
+C:NamedElement ::= D1:D [D2:D] D3:D*;
+D:NamedElement;
+
+abstract NamedElement ::= <Name>;
+
+rel A.Di1  -> B;
+rel A.Di2? -> B;
+rel A.Di3* -> B;
+
+rel A.Bi1 <-> B.Bi1;
+rel A.Bi2 <-> B.Bi2?;
+rel A.Bi3 <-> B.Bi3*;
+
+rel A.Bi5? <-> B.Bi5?;
+rel A.Bi6? <-> B.Bi6*;
+
+rel A.Bi9* <-> B.Bi9*;
+
+rel Root.D <-> D.Root?;
diff --git a/src/test/jastadd/serializer-manual/JsonPointer.jrag b/src/test/jastadd/serializer-manual/JsonPointer.jrag
new file mode 100644
index 0000000..db94ab9
--- /dev/null
+++ b/src/test/jastadd/serializer-manual/JsonPointer.jrag
@@ -0,0 +1,158 @@
+aspect JsonPointer {
+
+  refine RefCreatorStubs eq ASTNode.createReference() = jsonPointer();
+
+  syn String ASTNode.jsonPointer() {
+    if (getParent() == null) {
+      return "";
+    } else {
+      return jsonPointerInh();
+    }
+  }
+  inh String ASTNode.jsonPointerInh();
+  eq Root.A(int index).jsonPointerInh() {
+    return this.jsonPointer() + "/children/A/" + index;
+  }
+  eq Root.B(int index).jsonPointerInh() {
+    return this.jsonPointer() + "/children/B/" + index;
+  }
+  eq Root.C().jsonPointerInh() {
+    return this.jsonPointer() + "/children/C";
+  }
+  eq C.D1().jsonPointerInh() {
+    return this.jsonPointer() + "/children/D1";
+  }
+  eq C.D2().jsonPointerInh() {
+    return this.jsonPointer() + "/children/D2";
+  }
+  eq C.D3(int index).jsonPointerInh() {
+    return this.jsonPointer() + "/children/D3/" + index;
+  }
+
+  // context-independent name resolution
+  refine RefResolverStubs eq ASTNode.globallyResolveRootByToken(String id) {
+    return (Root) resolveJsonPointer(id);
+  }
+
+  // context-independent name resolution
+  refine RefResolverStubs eq ASTNode.globallyResolveAByToken(String id) {
+    return (A) resolveJsonPointer(id);
+  }
+
+  // context-independent name resolution
+  refine RefResolverStubs eq ASTNode.globallyResolveBByToken(String id) {
+    return (B) resolveJsonPointer(id);
+  }
+
+  // context-independent name resolution
+  refine RefResolverStubs eq ASTNode.globallyResolveCByToken(String id) {
+    return (C) resolveJsonPointer(id);
+  }
+
+  // context-independent name resolution
+  refine RefResolverStubs eq ASTNode.globallyResolveDByToken(String id) {
+    return (D) resolveJsonPointer(id);
+  }
+
+  // context-independent name resolution
+  refine RefResolverStubs eq ASTNode.globallyResolveNamedElementByToken(String id) {
+    return (NamedElement) resolveJsonPointer(id);
+  }
+
+  syn ASTNode ASTNode.resolveJsonPointer(String pointer) = root().resolveJsonPointer(pointer.split("/"), 1);
+  ASTNode ASTNode.root() {
+    if (getParent() == null) return this;
+    else return getParent().root();
+  }
+  syn ASTNode ASTNode.resolveJsonPointer(String[] pointer, int index) {
+    if (index < pointer.length) {
+      throw new RuntimeException("found wrong child  " + pointer[index + 1] + " in class " + this.getClass().getSimpleName());
+    } else {
+      throw new RuntimeException("Cannot resolve JSON pointer for class " + this.getClass().getSimpleName());
+    }
+  }
+  eq Root.resolveJsonPointer(String[] pointer, int index) {
+    if (pointer.length == 0 || pointer.length == index) {
+      return this;
+    } else if (pointer.length == index + 1) {
+      throw new RuntimeException("there is only one child called " + pointer[index]);
+    } else {
+      switch (pointer[index + 1]) {
+        case "A":
+          return getA(Integer.valueOf(pointer[index + 2])).resolveJsonPointer(pointer, index + 3);
+        case "B":
+          return getB(Integer.valueOf(pointer[index + 2])).resolveJsonPointer(pointer, index + 3);
+        case "C":
+          return getC().resolveJsonPointer(pointer, index + 2);
+        default:
+          return super.resolveJsonPointer(pointer, index);
+      }
+    }
+  }
+  eq A.resolveJsonPointer(String[] pointer, int index) {
+    if (pointer.length == 0 || pointer.length == index) {
+      return this;
+    } else if (pointer.length == index + 1) {
+      throw new RuntimeException("there is only one child called " + pointer[index]);
+    } else {
+      switch (pointer[index + 1]) {
+        default:
+          return super.resolveJsonPointer(pointer, index);
+      }
+    }
+  }
+  eq B.resolveJsonPointer(String[] pointer, int index) {
+    if (pointer.length == 0 || pointer.length == index) {
+      return this;
+    } else if (pointer.length == index + 1) {
+      throw new RuntimeException("there is only one child called " + pointer[index]);
+    } else {
+      switch (pointer[index + 1]) {
+        default:
+          return super.resolveJsonPointer(pointer, index);
+      }
+    }
+  }
+  eq C.resolveJsonPointer(String[] pointer, int index) {
+    if (pointer.length == 0 || pointer.length == index) {
+      return this;
+    } else if (pointer.length == index + 1) {
+      throw new RuntimeException("there is only one child called " + pointer[index]);
+    } else {
+      switch (pointer[index + 1]) {
+        case "D1":
+          return getD1().resolveJsonPointer(pointer, index + 2);
+        case "D2":
+          return getD2().resolveJsonPointer(pointer, index + 2);
+        case "D3":
+          return getD3(Integer.valueOf(pointer[index + 2])).resolveJsonPointer(pointer, index + 3);
+        default:
+          return super.resolveJsonPointer(pointer, index);
+      }
+    }
+  }
+  eq D.resolveJsonPointer(String[] pointer, int index) {
+    if (pointer.length == 0 || pointer.length == index) {
+      return this;
+    } else if (pointer.length == index + 1) {
+      throw new RuntimeException("there is only one child called " + pointer[index]);
+    } else {
+      switch (pointer[index + 1]) {
+        default:
+          return super.resolveJsonPointer(pointer, index);
+      }
+    }
+  }
+  eq NamedElement.resolveJsonPointer(String[] pointer, int index) {
+    if (pointer.length == 0 || pointer.length == index) {
+      return this;
+    } else if (pointer.length == index + 1) {
+      throw new RuntimeException("there is only one child called " + pointer[index]);
+    } else {
+      switch (pointer[index + 1]) {
+        default:
+          return super.resolveJsonPointer(pointer, index);
+      }
+    }
+  }
+}
diff --git a/src/test/jastadd/serializer-manual/Serializer.relast b/src/test/jastadd/serializer-manual/Serializer.relast
new file mode 100644
index 0000000..0e850a7
--- /dev/null
+++ b/src/test/jastadd/serializer-manual/Serializer.relast
@@ -0,0 +1,23 @@
+
+Root ::= A* B* C;
+A:NamedElement;
+B:NamedElement;
+C:NamedElement ::= D1:D [D2:D] D3:D*;
+D:NamedElement;
+
+abstract NamedElement ::= <Name>;
+
+rel A.Di1  -> B;
+rel A.Di2? -> B;
+rel A.Di3* -> B;
+
+rel A.Bi1 <-> B.Bi1;
+rel A.Bi2 <-> B.Bi2?;
+rel A.Bi3 <-> B.Bi3*;
+
+rel A.Bi5? <-> B.Bi5?;
+rel A.Bi6? <-> B.Bi6*;
+
+rel A.Bi9* <-> B.Bi9*;
+
+rel Root.D <-> D.Root?;
diff --git a/src/test/jastadd/serializer-pointer/Serializer.relast b/src/test/jastadd/serializer-pointer/Serializer.relast
new file mode 100644
index 0000000..0e850a7
--- /dev/null
+++ b/src/test/jastadd/serializer-pointer/Serializer.relast
@@ -0,0 +1,23 @@
+
+Root ::= A* B* C;
+A:NamedElement;
+B:NamedElement;
+C:NamedElement ::= D1:D [D2:D] D3:D*;
+D:NamedElement;
+
+abstract NamedElement ::= <Name>;
+
+rel A.Di1  -> B;
+rel A.Di2? -> B;
+rel A.Di3* -> B;
+
+rel A.Bi1 <-> B.Bi1;
+rel A.Bi2 <-> B.Bi2?;
+rel A.Bi3 <-> B.Bi3*;
+
+rel A.Bi5? <-> B.Bi5?;
+rel A.Bi6? <-> B.Bi6*;
+
+rel A.Bi9* <-> B.Bi9*;
+
+rel Root.D <-> D.Root?;
diff --git a/src/test/java/org/jastadd/relast/tests/SerializerManual.java b/src/test/java/org/jastadd/relast/tests/SerializerManual.java
new file mode 100644
index 0000000..a870f56
--- /dev/null
+++ b/src/test/java/org/jastadd/relast/tests/SerializerManual.java
@@ -0,0 +1,135 @@
+package org.jastadd.relast.tests;
+
+import org.junit.jupiter.api.Test;
+import manual.serializer.ast.*;
+
+import java.io.File;
+import java.io.IOException;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+
+class SerializerManual {
+
+
+  @Test
+  void testDi1() throws SerializationException, DeserializationException, IOException {
+
+    Root r = new Root();
+    A a1 = new A("a1");
+    A a2 = new A("a2");
+    A a3 = new A("a3");
+    B b1 = new B("b1");
+    B b2 = new B("b2");
+    B b3 = new B("b3");
+    C c = new C();
+    c.setName("c");
+
+    // non-terminals
+
+    D d1 = new D("d1");
+
+    c.setD1(d1);
+    c.setD2(new D("d2"));
+    c.addD3(new D("D3-1"));
+    c.addD3(new D("D3-2"));
+    c.addD3(new D("D3-3"));
+
+    r.setC(c);
+
+    // non-containment relations
+    r.addA(a1);
+    r.addA(a2);
+    r.addA(a3);
+    r.addB(b1);
+    r.addB(b2);
+    r.addB(b3);
+
+    // Di1
+    a1.setDi1(b2);
+    a2.setDi1(b1);
+    a3.setDi1(b3);
+
+    // Di2
+    a1.setDi2(b2);
+    a3.setDi2(b1);
+
+    // Di3
+    a1.addToDi3(b1);
+    a1.addToDi3(b2);
+    a1.addToDi3(b3);
+    a2.addToDi3(b2);
+
+    // Bi1
+    a1.setBi1(b3);
+    a2.setBi1(b2);
+    a3.setBi1(b1);
+
+    // Bi2
+    a1.setBi2(b1);
+    a2.setBi2(b2);
+    a3.setBi2(b3);
+
+    // Bi3
+    a1.setBi3(b2);
+    a2.setBi3(b2);
+    a3.setBi3(b2);
+
+    // Bi5
+    a1.setBi5(b1);
+    a2.setBi5(b3);
+
+    // Bi6
+    a2.setBi6(b3);
+    a3.setBi6(b3);
+
+    // Bi9
+    a1.addToBi9(b1);
+    a1.addToBi9(b3);
+    a2.addToBi9(b3);
+
+    // D
+    r.setD(d1);
+
+    File f1a = File.createTempFile("original", ".json");
+    System.out.println(f1a.getAbsoluteFile());
+    r.serialize(f1a);
+
+    com.fasterxml.jackson.core.JsonFactory factory = new com.fasterxml.jackson.core.JsonFactory();
+    com.fasterxml.jackson.core.JsonGenerator generator = factory.createGenerator(System.out, com.fasterxml.jackson.core.JsonEncoding.UTF8);
+    generator.setPrettyPrinter(new com.fasterxml.jackson.core.util.DefaultPrettyPrinter());
+    r.serialize(generator);
+    generator.close();
+
+    Root copy = Root.deserialize(f1a);
+    File f1b = File.createTempFile("copy", ".json");
+    copy.serialize(f1b);
+
+    assertThat(f1b).hasSameContentAs(f1a);
+
+    // remove a2
+    a1.setDi1(b3);
+    a1.setDi2(b3);
+    a1.removeFromDi3(b2);
+    a1.removeFromDi3(b2);
+    a1.setBi3(b1);
+    a3.setBi3(b1);
+    b3.clearBi5();
+    b3.removeFromBi6(a2);
+    b3.removeFromBi9(a2);
+    r.getAList().removeChild(r.getAList().getIndexOfChild(a2));
+    r.getBList().removeChild(r.getBList().getIndexOfChild(b2));
+
+    File f2a = File.createTempFile("original", ".json");
+    System.out.println(f2a.getAbsoluteFile());
+    r.serialize(f2a);
+
+    copy = Root.deserialize(f2a);
+    File f2b = File.createTempFile("copy", ".json");
+    copy.serialize(f2b);
+
+    assertThat(f2b).hasSameContentAs(f2a);
+
+  }
+
+}
diff --git a/src/test/java/org/jastadd/relast/tests/SerializerManualRelative.java b/src/test/java/org/jastadd/relast/tests/SerializerManualRelative.java
new file mode 100644
index 0000000..6d3ccc2
--- /dev/null
+++ b/src/test/java/org/jastadd/relast/tests/SerializerManualRelative.java
@@ -0,0 +1,135 @@
+package org.jastadd.relast.tests;
+
+import manual.relative.serializer.ast.*;
+import org.junit.jupiter.api.Test;
+
+import java.io.File;
+import java.io.IOException;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+
+class SerializerManualRelative {
+
+
+  @Test
+  void testDi1() throws SerializationException, DeserializationException, IOException {
+
+    Root r = new Root();
+    A a1 = new A("a1");
+    A a2 = new A("a2");
+    A a3 = new A("a3");
+    B b1 = new B("b1");
+    B b2 = new B("b2");
+    B b3 = new B("b3");
+    C c = new C();
+    c.setName("c");
+
+    // non-terminals
+
+    D d1 = new D("d1");
+
+    c.setD1(d1);
+    c.setD2(new D("d2"));
+    c.addD3(new D("D3-1"));
+    c.addD3(new D("D3-2"));
+    c.addD3(new D("D3-3"));
+
+    r.setC(c);
+
+    // non-containment relations
+    r.addA(a1);
+    r.addA(a2);
+    r.addA(a3);
+    r.addB(b1);
+    r.addB(b2);
+    r.addB(b3);
+
+    // Di1
+    a1.setDi1(b2);
+    a2.setDi1(b1);
+    a3.setDi1(b3);
+
+    // Di2
+    a1.setDi2(b2);
+    a3.setDi2(b1);
+
+    // Di3
+    a1.addToDi3(b1);
+    a1.addToDi3(b2);
+    a1.addToDi3(b3);
+    a2.addToDi3(b2);
+
+    // Bi1
+    a1.setBi1(b3);
+    a2.setBi1(b2);
+    a3.setBi1(b1);
+
+    // Bi2
+    a1.setBi2(b1);
+    a2.setBi2(b2);
+    a3.setBi2(b3);
+
+    // Bi3
+    a1.setBi3(b2);
+    a2.setBi3(b2);
+    a3.setBi3(b2);
+
+    // Bi5
+    a1.setBi5(b1);
+    a2.setBi5(b3);
+
+    // Bi6
+    a2.setBi6(b3);
+    a3.setBi6(b3);
+
+    // Bi9
+    a1.addToBi9(b1);
+    a1.addToBi9(b3);
+    a2.addToBi9(b3);
+
+    // D
+    r.setD(d1);
+
+    File f1a = File.createTempFile("original", ".json");
+    System.out.println(f1a.getAbsoluteFile());
+    r.serialize(f1a);
+
+    com.fasterxml.jackson.core.JsonFactory factory = new com.fasterxml.jackson.core.JsonFactory();
+    com.fasterxml.jackson.core.JsonGenerator generator = factory.createGenerator(System.out, com.fasterxml.jackson.core.JsonEncoding.UTF8);
+    generator.setPrettyPrinter(new com.fasterxml.jackson.core.util.DefaultPrettyPrinter());
+    r.serialize(generator);
+    generator.close();
+
+    Root copy = Root.deserialize(f1a);
+    File f1b = File.createTempFile("copy", ".json");
+    copy.serialize(f1b);
+
+    assertThat(f1b).hasSameContentAs(f1a);
+
+    // remove a2
+    a1.setDi1(b3);
+    a1.setDi2(b3);
+    a1.removeFromDi3(b2);
+    a1.removeFromDi3(b2);
+    a1.setBi3(b1);
+    a3.setBi3(b1);
+    b3.clearBi5();
+    b3.removeFromBi6(a2);
+    b3.removeFromBi9(a2);
+    r.getAList().removeChild(r.getAList().getIndexOfChild(a2));
+    r.getBList().removeChild(r.getBList().getIndexOfChild(b2));
+
+    File f2a = File.createTempFile("original", ".json");
+    System.out.println(f2a.getAbsoluteFile());
+    r.serialize(f2a);
+
+    copy = Root.deserialize(f2a);
+    File f2b = File.createTempFile("copy", ".json");
+    copy.serialize(f2b);
+
+    assertThat(f2b).hasSameContentAs(f2a);
+
+  }
+
+}
diff --git a/src/test/java/org/jastadd/relast/tests/SerializerPointer.java b/src/test/java/org/jastadd/relast/tests/SerializerPointer.java
new file mode 100644
index 0000000..2f1d141
--- /dev/null
+++ b/src/test/java/org/jastadd/relast/tests/SerializerPointer.java
@@ -0,0 +1,137 @@
+package org.jastadd.relast.tests;
+
+import org.junit.jupiter.api.Test;
+import pointer.serializer.ast.*;
+
+import java.io.File;
+import java.io.IOException;
+import java.time.Instant;
+import java.time.Period;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+
+class SerializerPointer {
+
+
+  @Test
+  void testDi1() throws SerializationException, DeserializationException, IOException {
+
+    Root r = new Root();
+    A a1 = new A("a1");
+    A a2 = new A("a2");
+    A a3 = new A("a3");
+    B b1 = new B("b1");
+    B b2 = new B("b2");
+    B b3 = new B("b3");
+    C c = new C();
+    c.setName("c");
+
+    // non-terminals
+
+    D d1 = new D("d1");
+
+    c.setD1(d1);
+    c.setD2(new D("d2"));
+    c.addD3(new D("D3-1"));
+    c.addD3(new D("D3-2"));
+    c.addD3(new D("D3-3"));
+
+    r.setC(c);
+
+    // non-containment relations
+    r.addA(a1);
+    r.addA(a2);
+    r.addA(a3);
+    r.addB(b1);
+    r.addB(b2);
+    r.addB(b3);
+
+    // Di1
+    a1.setDi1(b2);
+    a2.setDi1(b1);
+    a3.setDi1(b3);
+
+    // Di2
+    a1.setDi2(b2);
+    a3.setDi2(b1);
+
+    // Di3
+    a1.addToDi3(b1);
+    a1.addToDi3(b2);
+    a1.addToDi3(b3);
+    a2.addToDi3(b2);
+
+    // Bi1
+    a1.setBi1(b3);
+    a2.setBi1(b2);
+    a3.setBi1(b1);
+
+    // Bi2
+    a1.setBi2(b1);
+    a2.setBi2(b2);
+    a3.setBi2(b3);
+
+    // Bi3
+    a1.setBi3(b2);
+    a2.setBi3(b2);
+    a3.setBi3(b2);
+
+    // Bi5
+    a1.setBi5(b1);
+    a2.setBi5(b3);
+
+    // Bi6
+    a2.setBi6(b3);
+    a3.setBi6(b3);
+
+    // Bi9
+    a1.addToBi9(b1);
+    a1.addToBi9(b3);
+    a2.addToBi9(b3);
+
+    // D
+    r.setD(d1);
+
+    File f1a = File.createTempFile("original", ".json");
+    System.out.println(f1a.getAbsoluteFile());
+    r.serialize(f1a);
+
+    com.fasterxml.jackson.core.JsonFactory factory = new com.fasterxml.jackson.core.JsonFactory();
+    com.fasterxml.jackson.core.JsonGenerator generator = factory.createGenerator(System.out, com.fasterxml.jackson.core.JsonEncoding.UTF8);
+    generator.setPrettyPrinter(new com.fasterxml.jackson.core.util.DefaultPrettyPrinter());
+    r.serialize(generator);
+    generator.close();
+
+    Root copy = Root.deserialize(f1a);
+    File f1b = File.createTempFile("copy", ".json");
+    copy.serialize(f1b);
+
+    assertThat(f1b).hasSameContentAs(f1a);
+
+    // remove a2
+    a1.setDi1(b3);
+    a1.setDi2(b3);
+    a1.removeFromDi3(b2);
+    a1.removeFromDi3(b2);
+    a1.setBi3(b1);
+    a3.setBi3(b1);
+    b3.clearBi5();
+    b3.removeFromBi6(a2);
+    b3.removeFromBi9(a2);
+    r.getAList().removeChild(r.getAList().getIndexOfChild(a2));
+    r.getBList().removeChild(r.getBList().getIndexOfChild(b2));
+
+    File f2a = File.createTempFile("original", ".json");
+    System.out.println(f2a.getAbsoluteFile());
+    r.serialize(f2a);
+
+    copy = Root.deserialize(f2a);
+    File f2b = File.createTempFile("copy", ".json");
+    copy.serialize(f2b);
+
+    assertThat(f2b).hasSameContentAs(f2a);
+
+  }
+
+}
-- 
GitLab