Search code examples
jsontreeviewjolt

Convert the field names in a unlimited depth tree using Jolt


Can someone please help with Jolt transformation taking the following into consideration

  1. No limitation on the depth of children array, i.e though the example below shows a maximum depth on 2 levels Adam -> John -> Sarah this hierarchy can extend to any level
  2. Rename name -> childname, title -> childtitle if the node is present in children array in the hierarchy

Input:

{
"person": "Adam",
"personTitle": "MR",
"children": [
    {
        "name": "John",
        "title": "MR",
        "children": [
            {
                "name": "Sarah",
                "title": "MR",
                "children": [
                ]
            }
        ]
    },
    {
        "name": "Jimmy",
        "title": "MR",
        "children": [
            {
                "name": "Stephen",
                "title": "MR",
                "children": [
                ]
            }
        ]
    }
]

}

Output:

{
    "person": "Adam",
    "personTitle": "MR",
    "children": [
        {
            "childname": "John",
            "childtitle": "MR",
            "children": [
                {
                    "childname": "Sarah",
                    "childtitle": "MR",
                    "children": [
                    ]
                }
            ]
        },
        {
            "childname": "Jimmy",
            "childtitle": "MR",
            "children": [
                {
                    "childname": "Stephen",
                    "childtitle": "MR"
                }
            ]
        }
    ]
}

Solution

  • It's not possible to do unlimited deep exploration with Jolt. I see two solutions: (1) set a sufficiently but limited depth with the jolt specification or (2) use the recursivity of a programming language (without jolt).

    For solution 2, I solved a problem this way:

    package kapia.gbo.JsonProcessing;
    
    import java.util.function.Function;
    
    public interface JsonProcessing<T,R> {
    
        /**
         * In the {@code json}, rename all {@code key} attributes to {@code newKey} then apply the {@code function} with the
         * old value linked to {@code key} as input and returns the new value linked to {@code newKey}.
         * 
         * @param json     to transform
         * @param key      to search and delete
         * @param newKey   to insert with the value returning by {@code function}
         * @param function with {@code T} the type of old value {@code R} the type of new value
         * @return json as String after transformation
         */
        public String apply(String json, String key, String newKey, Function<T,R> function);
    
    }
    
    package kapia.gbo.JsonProcessing;
    
    import java.util.function.Function;
    
    import com.google.gson.JsonArray;
    import com.google.gson.JsonElement;
    import com.google.gson.JsonObject;
    import com.google.gson.JsonParser;
    
    public class JsonProcessingWithGsonImpl implements kapia.gbo.JsonProcessing.JsonProcessing<JsonElement,JsonElement> {
    
        public JsonProcessingWithGsonImpl() {
        }
    
        public String apply(String json, String key, String newKey, Function<JsonElement, JsonElement> function) {
            JsonElement jsonElement = JsonParser.parseString(json).getAsJsonObject();
    
            if (jsonElement.isJsonObject()) {
                apply((JsonObject) jsonElement, key, newKey, (Function<JsonElement, JsonElement>) function);
            }
    
            return jsonElement.toString();
        }
    
        private void apply(JsonObject jsonObject, String key, String newKey,
                Function<JsonElement, JsonElement> function) {
            if (jsonObject.has(key)) {
                JsonElement value = jsonObject.get(key);
                jsonObject.remove(key);
                jsonObject.add(newKey, function.apply(value));
    
            }
    
            // recursive exploration
            for (String currentKey : jsonObject.keySet()) {
                JsonElement element = jsonObject.get(currentKey);
                if (element.isJsonObject()) {
                    apply((JsonObject) element, key, newKey, function);
                } else if (element.isJsonArray()) {
                    apply((JsonArray) element, key, newKey, function);
                }
            }
        }
    
        private void apply(JsonArray jsonArray, String key, String newKey,
                Function<JsonElement, JsonElement> function) {
            for (JsonElement element : jsonArray) {
                if (element.isJsonObject()) {
                    apply((JsonObject) element, key, newKey, function);
                } else if (element.isJsonArray()) {
                    apply((JsonArray) element, key, newKey, function);
                }
            }
        }
    }
    

    Test :

    package kapia.gbo.JsonProcessing;
    
    import static org.junit.jupiter.api.Assertions.assertEquals;
    
    import java.util.Map;
    import java.util.function.Function;
    
    import org.json.JSONException;
    import org.junit.jupiter.api.Test;
    import org.skyscreamer.jsonassert.JSONAssert;
    
    import com.google.gson.JsonArray;
    import com.google.gson.JsonElement;
    import com.google.gson.JsonPrimitive;
    
    public class AppTest {
    
        private JsonProcessing<JsonElement, JsonElement> jsonParser = new JsonProcessingWithGsonImpl();
    
        private final String JSON_03_INPUT = """
                {
                  "policies": {
                    "insured": [
                      {
                        "addressType": 6,
                        "partyRef": "REF01",
                        "partyOrder": 1
                      },
                      {
                        "addressType": 6,
                        "partyRef": "REF02",
                        "partyOrder": 2
                      }
                    ],
                    "subscribers": [
                      {
                        "addressType": 6,
                        "partyRef": "REF01",
                        "partyOrder": 1
                      }
                    ]
                  }
                }
                            """;
    
        private final String JSON_03_EXPECTED = "{\"policies\":{\"insured\":[{\"addressType\":6,\"partyId\":1,\"partyOrder\":1},{\"addressType\":6,\"partyId\":2,\"partyOrder\":2}],\"subscribers\":[{\"addressType\":6,\"partyId\":1,\"partyOrder\":1}]}}";
    
        /**
         * Replace all partyRef with the corresponding partyId.
         * 
         * @throws JSONException
         */
        @Test
        public void test_03() throws JSONException {
            Function<JsonElement, JsonElement> fonction = ancienneValeur -> {
                Map<String, Integer> parties = Map.of("REF01", 1, "REF02", 2);
                String ancienneValeurAsString = (ancienneValeur.isJsonPrimitive()) ? ancienneValeur.getAsString() : null;
                return new JsonPrimitive(parties.get(ancienneValeurAsString));
            };
            String jsonAfter = jsonParser.apply(JSON_03_INPUT, "partyRef", "partyId", fonction);
            JSONAssert.assertEquals(JSON_03_EXPECTED, jsonAfter, false);
        }
    }