bazaarvoice / jolt

JSON to JSON transformation library written in Java.
Apache License 2.0
1.54k stars 329 forks source link

Nested recursive jolt arrays #1078

Open pktest6 opened 3 years ago

pktest6 commented 3 years ago

Here is my input json { "Parent": { "primary": { "value": true }, "children": [ { "type": "X", "children": [ { "type": "X1", "children": [ { "type": "X11" }, { "type": "X12" } ] }, { "type": "X2" } ] }, { "type": "Y", "children": [ { "type": "Y1" }, { "type": "Y11" } ] } ] } } Children array inside have children array. This is a nested structure that can have n nesting level. I need output json { "PrimaryParent" : true, "children" : [ { "class" : "X", "children" : [ { "class" : "X1", "children" : [ { "class" : "X11" }, { "class" : "X12" } ] }, { "class" : "X2" } ] }, { "class" : "Y", "children" : [ { "class" : "Y1" }, { "class" : "Y11" } ] } ] }

Is there a way to write jolt spec without specifying complete XPath for every 'children' array as it can be nested n levels deep? The spec I have is that looks for hardcoded nested structure three-level deep. [ { "operation": "shift", "spec": { "Parent": { "primary": { // simple match. Put the value '4' in the output under the "Rating" field "value": "PrimaryParent" }, "children": { "": { "type": "children[&1].class", "children": { "": { "type": "children[&3].children[&1].class", "children": { "*": { "type": "children[&5].children[&3].children[&1].class" } } } } } } } } } ]

lucioalmeida commented 3 years ago

It's possible with custom modifies

package br.com.teste.jolt;

import com.bazaarvoice.jolt.common.Optional;
import com.bazaarvoice.jolt.modifier.function.Function;

import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

public class CustomFunctions {

    public static final class recursivelyReplace extends Function.ListFunction {

        @Override
        protected Optional<Object> applyList(List<Object> argList) {
            if (argList.size() != 3) {
                return Optional.empty();
            }
            Object objectValue = argList.get(0);
            String from = (String) argList.get(1);
            String to = (String) argList.get(2);
            recursivelyReplace(objectValue, from, to);
            return Optional.of(objectValue);
        }
    }

    public static void recursivelyReplace(Object input, String from, String to) {

        if (input instanceof List) {
            List inputList = (List) input;
            inputList.forEach(i -> recursivelyReplace(i, from, to));
        } else if (input instanceof Map) {
            Map<String, Object> inputMap = (Map<String, Object>) input;
            replace(inputMap, from, to);

            for (Map.Entry<String, Object> entry : inputMap.entrySet()) {
                recursivelyReplace(entry.getValue(), from, to);
            }
        }
    }

    public static void replace(Map<String, Object> inputMap, String from, String to) {
        Set<String> keys = new HashSet<>(inputMap.keySet());
        for (String key : keys) {
            if (key.equals(from)) {
                Object removedValue = inputMap.remove(from);
                inputMap.put(to, removedValue);
            }
        }
    }
}
package br.com.teste.jolt;

import com.bazaarvoice.jolt.ContextualTransform;
import com.bazaarvoice.jolt.SpecDriven;
import com.bazaarvoice.jolt.common.Optional;
import com.bazaarvoice.jolt.common.tree.MatchedElement;
import com.bazaarvoice.jolt.common.tree.WalkedPath;
import com.bazaarvoice.jolt.exception.SpecException;
import com.bazaarvoice.jolt.modifier.OpMode;
import com.bazaarvoice.jolt.modifier.TemplatrSpecBuilder;
import com.bazaarvoice.jolt.modifier.function.Function;
import com.bazaarvoice.jolt.modifier.spec.ModifierCompositeSpec;

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

public class CustomModifier implements SpecDriven, ContextualTransform {

    private static final Map<String, Function> STOCK_FUNCTIONS = new HashMap<>();

    private final ModifierCompositeSpec rootSpec;

    static {
        STOCK_FUNCTIONS.put("recursivelyReplace", new CustomFunctions.recursivelyReplace());
    }

    @SuppressWarnings("unchecked")
    private CustomModifier(Object spec, OpMode opMode, Map<String, Function> functionsMap) {
        if (spec == null) {
            throw new SpecException(opMode.name() + " expected a spec of Map type, got 'null'.");
        }

        if (!(spec instanceof Map)) {
            throw new SpecException(opMode.name() + " expected a spec of Map type, got " + spec.getClass().getSimpleName());
        }

        if (functionsMap == null || functionsMap.isEmpty()) {
            throw new SpecException(opMode.name() + " expected a populated functions' map type, got " + (functionsMap == null ? "null" : "empty"));
        }

        functionsMap = Collections.unmodifiableMap(functionsMap);
        TemplatrSpecBuilder templatrSpecBuilder = new TemplatrSpecBuilder(opMode, functionsMap);

        rootSpec = new ModifierCompositeSpec(ROOT_KEY, (Map<String, Object>) spec, opMode, templatrSpecBuilder);
    }

    @Override
    public Object transform(final Object input, final Map<String, Object> context) {

        Map<String, Object> contextWrapper = new HashMap<>();
        contextWrapper.put(ROOT_KEY, context);

        MatchedElement rootLpe = new MatchedElement(ROOT_KEY);
        WalkedPath walkedPath = new WalkedPath();
        walkedPath.add(input, rootLpe);

        rootSpec.apply(ROOT_KEY, Optional.of(input), walkedPath, null, contextWrapper);

        return input;
    }

    /**
     * This variant of modifier creates the key/index is missing,
     * and overwrites the value if present
     */
    public static final class Overwritr extends CustomModifier {

        public Overwritr(Object spec) {
            this(spec, STOCK_FUNCTIONS);
        }

        public Overwritr(Object spec, Map<String, Function> functionsMap) {
            super(spec, OpMode.OVERWRITR, functionsMap);
        }
    }

    /**
     * This variant of modifier only writes when the key/index is missing
     */
    public static final class Definr extends CustomModifier {

        public Definr(final Object spec) {
            this(spec, STOCK_FUNCTIONS);
        }

        public Definr(Object spec, Map<String, Function> functionsMap) {
            super(spec, OpMode.DEFINER, functionsMap);
        }
    }

    /**
     * This variant of modifier only writes when the key/index is missing or the value is null
     */
    public static class Defaultr extends CustomModifier {

        public Defaultr(final Object spec) {
            this(spec, STOCK_FUNCTIONS);
        }

        public Defaultr(Object spec, Map<String, Function> functionsMap) {
            super(spec, OpMode.DEFAULTR, functionsMap);
        }
    }
}

spec:

[
  {
    "operation": "shift",
    "spec": {
      "Parent": {
        "primary": {
          "value": "PrimaryParent"
        },
        "children": "children"
      }
    }
    }, {
    "operation": "br.com.teste.jolt.CustomModifier$Overwritr",
    "spec": {
      "children": "=recursivelyReplace(@(1,children), 'type', 'class')"
    }
}
]

output

{
    "PrimaryParent": true,
    "children": [
        {
            "children": [
                {
                    "children": [
                        {
                            "class": "X11"
                        },
                        {
                            "class": "X12"
                        }
                    ],
                    "class": "X1"
                },
                {
                    "class": "X2"
                }
            ],
            "class": "X"
        },
        {
            "children": [
                {
                    "class": "Y1"
                },
                {
                    "class": "Y11"
                }
            ],
            "class": "Y"
        }
    ]
}
vasu-dasari commented 1 year ago

Thank you @lucioalmeida . I was looking for an custom functions example.