7

I'm trying to create a utility class/method where a certain method will accept a list of Strings - each string will be constructed as follows:

"Root.Parent.Child... N"

This string can be N long (or basically holds infinite number of parts).

Each Part of the String (parts are the split string by dot) is intended to be a Json Object.

For Example:

  //sample data
    String rec1 = 'Root.Parent.FirstChild';
    String rec2 = 'Root.Parent.FirstChild2';
    String rec3 = 'Root.Parent2.Child';
    String rec4 = 'Root.Parent2.Child2';
    String[] fullPathsList = new List<String>{ rec1,rec2,rec3,rec4  };

 //should create as many maps as needed dynamically based on total size of string paths
    Map<String,Object> output = new Map<String,Object>();
    Map<String,Object> root = new Map<String,Object>();
    Map<String,Object> level1 = new Map<String,Object>();
    Map<String,Object> level2 = new Map<String,Object>();

//iterate over the full list and grab each path
for(String fullJsonPath:fullPathsList ) {
  //check if has a dot
  if( fullJsonPath.indexOf('.') != -1 ){
    String[] pathPartsList =  fullJsonPath.split('\\.');
      Integer totalSize = pathPartsList.size();
      for(Integer i=0;i<totalSize;i++) {
          level2.put(pathPartsList[totalSize-1] , 'VALUE');
          if(totalSize-2 > 0){
           level1.put(pathPartsList[totalSize-2], level2);
          }
         root.put(pathPartsList[0], level1); 
      } 
   }
   output.put('results', root);
}

System.debug('@@@ output ' + JSON.serialize(output));

The output will show :

{ "results": {
    "Root": {
      "Parent2": {
        "Child2": "VALUE",
        "Child": "VALUE",
        "FirstChild2": "VALUE",
        "FirstChild": "VALUE"
      },
      "Parent": {
        "Child2": "VALUE",
        "Child": "VALUE",
        "FirstChild2": "VALUE",
        "FirstChild": "VALUE"
      }
    }
  }
}

My problems here is :

  1. The level 2 map is holding all childs - each parent holds all childs and not only it's own.
  2. I need to have an ability to generate those Maps dynamically based on the number of childs the strings have - was thinking some kind of a recursion method will do the trick.

Anybody see a clever way of achieving this in Apex?

In Javascript it's pretty simple with this one liner which I'm trying to replicate somehow in Apex (any equivalent reduce method?) :

'Root.Parent.FirstChild'.split('.').reduce((o,i)=>o[i], obj);
3

I've done exactly this before. Here's working code for it:

public class JsonBoxer {

    public Map<String, Object> root {get; private set;}

    public JsonBoxer() {
        this.root = new Map<String, Object>();
    }

    public void put(String key, Object value) {
        doPut(root, key.split('\\.'), value);
    }

    private void doPut(Map<String, Object> currentRoot, List<String> keyChain, Object value) {
        if(keyChain.size() == 1) {
            currentRoot.put(keyChain[0], value);
        } else {
            String thisKey = keyChain.remove(0);
            Map<String, Object> child = (Map<String, Object>)currentRoot.get(thisKey);
            if(child == null) {
                child = new Map<String, Object>();
                currentRoot.put(thisKey, child);
            }

            doPut(child, keyChain, value);
        }
    }
}

Even with a test:

@IsTest
private class JsonBoxerTest {

    @IsTest static void noBoxing() {
        JsonBoxer boxer = new JsonBoxer();

        boxer.put('a', 'b');

        System.assertEquals('b', boxer.root.get('a'));
    }

    @IsTest static void boxing() {
        JsonBoxer boxer = new JsonBoxer();

        boxer.put('a.1', 'b');

        System.assertEquals('b', ((Map<String, Object>)boxer.root.get('a')).get('1'));
    }

    @IsTest static void twoSubKeyValues() {
        JsonBoxer boxer = new JsonBoxer();

        boxer.put('a.1', 'b');
        boxer.put('a.2', 'c');

        System.assertEquals('b', ((Map<String, Object>)boxer.root.get('a')).get('1'));
        System.assertEquals('c', ((Map<String, Object>)boxer.root.get('a')).get('2'));
    }

    @IsTest static void overwriteSubKey() {
        JsonBoxer boxer = new JsonBoxer();

        boxer.put('a.1', 'b');
        boxer.put('a.1', 'c');

        System.assertEquals('c', ((Map<String, Object>)boxer.root.get('a')).get('1'));
    }
}
4

Something like This probably

public with sharing class MyClass {

    public  static Map<String, Object> get(String input) {

        if (input == null) return null;
        if (!input.contains('.')) return new  Map<String, Object>{input => null};

        return new Map<String, Object>{
                input.substringBefore('.') => get(input.substringAfter('.'))
        };

    }
}

Then executing:

System.debug(JSON.serialize(MyClass.get('Root.Parent.FirstChild')));

Output : {"Root":{"Parent":{"FirstChild":null}}

3

One of the approaches would be create a inner wrapper class for Map, and define own method to set value based on path.

Most of the code is already written in your question, so check the following pseudo-code:

public class SuperMap {
    Map<String, Object> ResultMap;

    public SuperMap() {
        ResultMap = new Map<String, Object>();
    }

    public void specifyValue(String path, Object value) {
        Map<String, Object> current_map = ResultMap;
        List<String> path_steps = path.split('\\.');
        for(Integer i = 0; i < path_steps.size() - 1; i++) {
            String step = path_steps.get(i);
            if (!current_map.containsKey(step)) {
                current_map.put(step, (Object)new Map<String, Object>());
            }
            current_map = (Map<String, Object>)current_map.get(step);
        }
        current_map.put(path_steps.get(path_steps.size() - 1), value);
    }
    public String returnString() {
         return JSON.serialize(ResultMap);
    }
}

Example of usage:

SuperMap mp = new SuperMap();
mp.specifyValue('kuru.123.dev','Data');
mp.specifyValue('grey.123.dev2','Data2');
mp.specifyValue('grey.123.dev2','Data3');
System.debug(mp.returnString());

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.