How to create a recursive method in Apex which takes a dot notation string and convert it into Hierarchical Map/Json?

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


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

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

Tags:

Json

Apex