2

This question is not an duplicate of Fastest way to flatten / un-flatten nested JSON objects, because additional values become keys, the order changes largely and my object is not simply "in a line".

I have updated the question description to make it clearer.


I have a nested object which represents a multilanguage path to controller mapping:

{
  "welcome": {
    "news": {
      "de": "nachrichten",
      "en": "news"
    },
    "de": "willkommen",
    "en": "welcome"
  },
  "applications": {
    "application1": {
      "de": "anwendung1",
      "en": "application1"
    },
    "application2": {
      "features": {
        "de": "funktionen",
        "en": "features"
      },
      "de": "anwendung2",
      "en": "application2"
    },
    "de": "anwendungen",
    "en": "applications"
  }
}

This should be converted to an easy-to-use object that accepts a path as key:

{
  "/de/willkommen/": "welcome",
  "/en/welcome/": "welcome",
  "/de/willkommen/nachrichten/": "news",
  "/en/welcome/news/": "news",
  "/de/anwendungen/": "applications",
  "/en/applications/": "applications",
  "/de/anwendungen/anwendung1/": "application1",
  "/en/applications/application1/": "application1",
  "/de/anwendungen/anwendung2/": "application2",
  "/en/applications/application2/": "application2",
  "/de/anwendungen/anwendung2/funktionen/": "features",
  "/en/applications/application2/features/": "features",

}

Now the initial language specific values ("de": "willkommen" etc) buildung the path and are the key and the initial key is the value. But please take a look, it's a little bit more complex.

I have build a function, but they work only for the first level like "/de/anwendungen/", not for "/de/anwendungen/anwendung1/" and lower.

  convertToPath(OldObject, NewObject = {})
  {
    for(let SecondObject in OldObject)
    {
      for(let Key in OldObject[SecondObject])
      {
        NewObject["/" + Key + "/" + OldObject[SecondObject][Key] + "/"] = SecondObject;
      }
    }

    return NewObject;
  }
Community
  • 1
  • 1
Hativ
  • 1,500
  • 1
  • 16
  • 24
  • 2
    The logic seems complex. Can you please explain how you're getting the first JSON object and how you'll be using the 2nd JSON object? Perhaps, we can change something beforehand – Aswin Ramakrishnan Aug 03 '15 at 20:14
  • The first JSON object is the definition for a router defined by a user, which should be well structured. The second JSON object should be internally used by this router and should be easy to use by adding just the window.location.pathname to the object to get the controller., e.g. FirstObject[window.location.pathname] will return the controller name for this route (path). But if the conversion would be too inefficient, we have to change the first user defined json object to something less structured. – Hativ Aug 03 '15 at 20:39

2 Answers2

1

Well, you can create your object by using recursion to walk the tree of data, like so:

var data = {
    "welcome": {
        "news": {
            "de": "nachrichten",
            "en": "news"
        },
        "de": "willkommen",
        "en": "welcome"
    },
    "applications": {
        "application1": {
            "de": "anwendung1",
            "en": "application1"
        },
        "application2": {
            "features": {
                "de": "funktionen",
                "en": "features"
            },
            "de": "anwendung2",
            "en": "application2"
        },
        "de": "anwendungen",
        "en": "applications"
    }
};

var langs = ['en', 'de'];

var path_to_controller = function(data, paths, pathsofar){
    paths = paths || {};
    pathsofar = pathsofar || {};
    Object.keys(data).forEach(function(key){
        var newpathsofar;
        if(langs.some(function(lang){ return key === lang; })){
            return;
        }
        newpathsofar = langs.reduce(function(p, lang){
            p[lang] = pathsofar[lang] || '/' + lang + '/';
            p[lang] += data[key][lang] + '/';
            paths[p[lang]] = key;
            return p;
        }, {});
        path_to_controller(data[key], paths, newpathsofar)
    });
    return paths;
};

var paths = path_to_controller(data);
var pre = document.createElement('pre');
pre.innerHTML = JSON.stringify(paths, null, 4);
document.body.appendChild(pre);

Your nested object does appear to be rather horrible to work with, so I can understand you wanting to do a conversion into another format. Perhaps you could arrange for the path data to be given in format that's easier to use to begin with.

EDIT: generalised to any number of languages.

1983
  • 5,882
  • 2
  • 27
  • 39
  • Thank you! It would be better if you do not have to specify each language in the function. Do you have a solution for this? – Hativ Aug 03 '15 at 21:40
  • 1
    I don't think there should be a `controllerlang`. Instead, the controllers are selected by the property name? I.e. swap `data[key][controllerlang]` for a simple `key`. That they're equivalent to the English name is probably just coincidence. – Bergi Aug 03 '15 at 21:44
  • @Kiltarc: In fact, the edit was done 3min 41s *before* your comment – Bergi Aug 03 '15 at 21:46
  • @Kiltarc, yes, I'd already decided to make that change before you'd commented! – 1983 Aug 03 '15 at 21:46
  • @Bergi You are right. The controller should be the property name, not the english name. In use they will possibly not be the same. – Hativ Aug 03 '15 at 21:50
0

And a slight alternative to answer 1. Although i fully agree with 'King Mob' this structure is flawed and error prone. If you ever need the resource named "en" (which means 'AND' in dutch for instance) you up Bantha poodoo's creek.

// target hold the desired output
var target = {};

// languages to distinguish between "a translated resource" and "a resource".
var ls = [ "en", "de" ];
var path = [];
var recurse = function(a,n) {
        // can be made pretty by using ls.
        if (a.hasOwnProperty("de") || a.hasOwnProperty("en"))
    {
        path.push(a);
    }
    var keys = Object.keys(a);
    keys.forEach( 
       function(o) { 
        if (ls.indexOf(o) > -1) {
 var spath = path.reduce( function(pV, cV) { return pV + "/" + cV[o]; }, o);
            target[spath] = n;
        } else {
            recurse(a[o], o);
        }
       }
      );
        // can be made pretty by using ls.
    if (a.hasOwnProperty("de") || a.hasOwnProperty("en"))
    {
        path.pop();
    }
};

// your example data.
var source = {..use your example data.};

// start the recursion
recurse( source );

// output the result
JSON.stringify(target);
Marvin Smit
  • 4,088
  • 1
  • 22
  • 21
  • Thank you! What do you mean by "using ls". I don't know, what "ls" is. Maybe you can already do it pretty? – Hativ Aug 03 '15 at 21:46
  • Instead of checking for explicit "de" and "en", you could walk the 'ls' array and see if that entry is present as property. That way would drop the "hardcoded en & de" and have a "list of language abreviations used". – Marvin Smit Aug 04 '15 at 06:35