Repository: metafacture/metafacture-core
Issue: JSON-LD keywords in input data break _else pass-through in morph

Somehow data input field names like `@id`, `@graph` end up in morph feedback/recursion and are missing in the output. This came up in https://gitlab.com/oersi/oersi-etl/-/issues/29#note_453341447 and I've implemented a fix for metafacture-fix in https://github.com/metafacture/metafacture-fix/commit/f4dc31dbb3a1aae86cfef9811f4f074f1ab87a61. I'll branch off from https://github.com/metafacture/metafacture-core/pull/342 to add a test case for metamorph.



Changed files:
metamorph/src/main/java/org/metafacture/metamorph/Metamorph.java, metamorph/src/test/java/org/metafacture/metamorph/TestMetamorphBasics.java

diff --git a/metamorph/src/main/java/org/metafacture/metamorph/Metamorph.java b/metamorph/src/main/java/org/metafacture/metamorph/Metamorph.java
index 43dd9a17..e38b9d05 100644
--- a/metamorph/src/main/java/org/metafacture/metamorph/Metamorph.java
+++ b/metamorph/src/main/java/org/metafacture/metamorph/Metamorph.java
@@ -338,11 +338,11 @@ else if (fallbackReceiver != null) {
                             elseNestedEntityStarted = true;
                         }
 
-                        send(currentLiteralName, value, fallbackReceiver);
+                        send(escapeFeedbackChar(currentLiteralName), value, fallbackReceiver);
                     }
                 }
                 else {
-                    send(path, value, fallbackReceiver);
+                    send(escapeFeedbackChar(path), value, fallbackReceiver);
                 }
             }
         }
@@ -363,6 +363,14 @@ private void send(final String path, final String value, final List<NamedValueRe
         }
     }
 
+    private boolean startsWithFeedbackChar(final String name) {
+        return name.length() != 0 && name.charAt(0) == FEEDBACK_CHAR;
+    }
+
+    private String escapeFeedbackChar(final String name) {
+        return name == null ? null : (startsWithFeedbackChar(name) ? ESCAPE_CHAR : "") + name;
+    }
+
     /**
      * @param streamReceiver
      *            the outputHandler to set
@@ -388,7 +396,7 @@ public void receive(final String name, final String value, final NamedValueSourc
                     "encountered literal with name='null'. This indicates a bug in a function or a collector.");
         }
 
-        if (name.length() != 0 && name.charAt(0) == FEEDBACK_CHAR) {
+        if (startsWithFeedbackChar(name)) {
             dispatch(name, value, null, false);
             return;
         }
diff --git a/metamorph/src/test/java/org/metafacture/metamorph/TestMetamorphBasics.java b/metamorph/src/test/java/org/metafacture/metamorph/TestMetamorphBasics.java
index 6345b8ea..7f551ba1 100644
--- a/metamorph/src/test/java/org/metafacture/metamorph/TestMetamorphBasics.java
+++ b/metamorph/src/test/java/org/metafacture/metamorph/TestMetamorphBasics.java
@@ -106,6 +106,7 @@ private void testElseData(final String elseKeyword) {
                 "</rules>",
                 i -> {
                     i.startRecord("1");
+                    i.literal("@id", "123");
                     i.literal("Shikotan", "Aekap");
                     i.startEntity("Germany");
                     i.literal("Langeoog", "Moin");
@@ -113,18 +114,21 @@ private void testElseData(final String elseKeyword) {
                     i.literal("Borkum", "Tach");
                     i.endEntity();
                     i.startEntity("Germany");
+                    i.literal("@foo", "bar");
                     i.literal("Baltrum", "Moin Moin");
                     i.endEntity();
                     i.endRecord();
                 },
                 o -> {
                     o.get().startRecord("1");
+                    o.get().literal("@id", "123");
                     o.get().literal("Shikotan", "Aekap");
                     o.get().literal("Germany.Langeoog", "Moin");
                     o.get().startEntity("Germany");
                     o.get().literal("Hawaii", "Aloha");
                     o.get().literal("Germany.Borkum", "Tach");
                     o.get().endEntity();
+                    o.get().literal("Germany.@foo", "bar");
                     o.get().literal("Germany.Baltrum", "Moin Moin");
                     o.get().endRecord();
                 }
@@ -223,8 +227,10 @@ public void shouldHandleUnmatchedLiteralsAndEntitiesInElseNestedSource() {
                 "</rules>",
                 i -> {
                     i.startRecord("1");
+                    i.literal("@id", "123");
                     i.literal("Shikotan", "Aekap");
                     i.startEntity("Germany");
+                    i.literal("@foo", "bar");
                     i.literal("Langeoog", "Moin");
                     i.literal("Baltrum", "Moin Moin");
                     i.endEntity();
@@ -235,8 +241,10 @@ public void shouldHandleUnmatchedLiteralsAndEntitiesInElseNestedSource() {
                 },
                 o -> {
                     o.get().startRecord("1");
+                    o.get().literal("@id", "123");
                     o.get().literal("Shikotan", "Aekap");
                     o.get().startEntity("Germany");
+                    o.get().literal("@foo", "bar");
                     o.get().literal("Langeoog", "Moin");
                     o.get().literal("Baltrum", "Moin Moin");
                     o.get().endEntity();