8

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 Answers 3

5

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'));
    }
}
5

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}}

4

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());

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .