25

I have an object like

{ "status": "success", "auth": { "code": "23123213", "name": "qwerty asdfgh" } }

I want to convert it to dot notation (one level) version like:

{ "status": "success", "auth.code": "23123213", "auth.name": "qwerty asdfgh" }

Currently I am converting the object by hand using fields but I think there should be a better and more generic way to do this. Is there any?

Note: There are some examples showing the opposite way, but i couldn't find the exact method.

Note 2: I want it for to use with my serverside controller action binding.

Machavity
  • 30,841
  • 27
  • 92
  • 100
vardars
  • 541
  • 1
  • 8
  • 20
  • 1
    parse the JSON string ... iterata over it's subitems and after that convert it back to JSON string – Reflective Nov 04 '12 at 13:04
  • 1
    @Elias Van Ootegem: It's not a valid identifier, but every string is a valid property name. – pimvdb Nov 04 '12 at 13:08
  • after parsing, you can access `code` by `author.code` because `auth` is an Object and `code` is a property of this Object – Reflective Nov 04 '12 at 13:08
  • My question is why? There must be a JSON implementation for your favorite server language – mplungjan Nov 04 '12 at 13:09
  • @mplungjan Actually I am using C#, and I want to use the posted object with an actual object, not a dictionary. If I directly post the first object, it postes like "auth[code]". That's why. Thanks – vardars Nov 04 '12 at 13:36
  • @EliasVanOotegem *"because a dot in a property name is just not allowed"* Yes, it is, as pimvdb pointed out. Probably best to delete the comment. – T.J. Crowder Oct 05 '15 at 14:27
  • This has nothing to do with JSON. If you're dealing with program source code, and you're not dealing with a *string*, you're not dealing with JSON. JSON is a textual notation for data exchange. Even if your object above was parsed from JSON, it's not JSON anymore once parsed. – T.J. Crowder Oct 05 '15 at 14:28
  • related: [Fastest way to flatten / un-flatten nested JSON objects](http://stackoverflow.com/q/19098797/1048572) – Bergi Dec 09 '15 at 22:12

9 Answers9

39

You can recursively add the properties to a new object, and then convert to JSON:

var res = {};
(function recurse(obj, current) {
  for(var key in obj) {
    var value = obj[key];
    var newKey = (current ? current + "." + key : key);  // joined key with dot
    if(value && typeof value === "object") {
      recurse(value, newKey);  // it's a nested object, so do it again
    } else {
      res[newKey] = value;  // it's not an object, so set the property
    }
  }
})(obj);
var result = JSON.stringify(res);  // convert result to JSON
pimvdb
  • 151,816
  • 78
  • 307
  • 352
  • I wrote another function with prefix feature. I couldn't run your code but I got the answer. Thanks – vardars Nov 04 '12 at 13:25
  • for future reference: the library, @vardars is mentioning can is linked below (https://stackoverflow.com/a/13218902/2602592) or can directly be found at https://github.com/vardars/dotize – gaugau Sep 19 '18 at 14:21
11

Here is a fix/hack for when you get undefined for the first prefix. (I did)

var dotize = dotize || {};

dotize.parse = function(jsonobj, prefix) {
  var newobj = {};
  function recurse(o, p) {
    for (var f in o)
    {
      var pre = (p === undefined ? '' : p + ".");
      if (o[f] && typeof o[f] === "object"){
        newobj = recurse(o[f], pre + f);
      } else {
        newobj[pre + f] = o[f];
      }
    }
    return newobj;
  }
  return recurse(jsonobj, prefix);
};
rhalff
  • 181
  • 2
  • 4
8

You can use the NPM dot-object (Github) for transform to object to dot notation and vice-versa.

var dot = require('dot-object'); 
var obj = {
  id: 'my-id',
  nes: { ted: { value: true } },
  other: { nested: { stuff: 5 } },
  some: { array: ['A', 'B'] }
};

var tgt = dot.dot(obj);

Produces

{
  "id": "my-id",
  "nes.ted.value": true,
  "other.nested.stuff": 5,
  "some.array[0]": "A",
  "some.array[1]": "B"
}
Dennis
  • 3,962
  • 7
  • 26
  • 44
Guillem Puche
  • 1,199
  • 13
  • 16
2

const sourceObj = { "status": "success", "auth": { "code": "23123213", "name": "qwerty asdfgh" } }
;

const { auth, ...newObj } = sourceObj;

const resultObj = {
  ...newObj,
  ..._.mapKeys(auth, (val, key) => `auth.${key}`)
}


// function approach
const dotizeField = (obj, key) => {
  const { ...newObj } = sourceObj;

  delete newObj[key];

  return {
    ...newObj,
    ..._.mapKeys(obj[key], (val, subKey) => `${key}.${subKey}`)
  }
}

const resultObj2 = dotizeField(sourceObj, 'auth');

console.log(sourceObj, resultObj, resultObj2);
<script src="https://cdn.jsdelivr.net/npm/lodash@4.17.20/lodash.min.js"></script>
2

i have done some fix:

export function toDotNotation(obj,res={}, current='') {
  for(const key in obj) {
    let value = obj[key];
    let newKey = (current ? current + "." + key : key);  // joined key with dot
    if(value && typeof value === "object") {
      toDotNotation(value,res, newKey);  // it's a nested object, so do it again
    } else {
      res[newKey] = value;  // it's not an object, so set the property
    }
  }
  return res;
}


zia
  • 278
  • 1
  • 10
1

I wrote another function with prefix feature. I couldn't run your code but I got the answer. Thanks

https://github.com/vardars/dotize

var dotize = dotize || {};

dotize.convert = function(jsonobj, prefix) {
    var newobj = {};

    function recurse(o, p, isArrayItem) {
        for (var f in o) {
            if (o[f] && typeof o[f] === "object") {
                if (Array.isArray(o[f]))
                    newobj = recurse(o[f], (p ? p + "." : "") + f, true); // array
                else {
                    if (isArrayItem)
                        newobj = recurse(o[f], (p ? p : "") + "[" + f + "]"); // array item object
                    else
                        newobj = recurse(o[f], (p ? p + "." : "") + f); // object
                }
            } else {
                if (isArrayItem)
                    newobj[p + "[" + f + "]"] = o[f]; // array item primitive
                else
                    newobj[p + "." + f] = o[f]; // primitive
            }
        }
        return newobj;
    }

    return recurse(jsonobj, prefix);
};
vardars
  • 541
  • 1
  • 8
  • 20
  • 1
    If it works, then that's fine, but this code requires each property name to have at least one dot in it. That's not the case for `success`. – pimvdb Nov 04 '12 at 13:27
  • yes I realized that, and tried to fix with using global var. :) – vardars Nov 04 '12 at 13:39
  • github library is updated, has some tests. I can't regularly update this answer, sorry. – vardars Aug 21 '15 at 17:09
1

Following what @pimvdb did (a compact and effective solution he submitted), I added a little modification that allows me have a function that can be easily exported:

    function changeObjectToDotNotationFormat(inputObject, current, prefinalObject) {
      const result = prefinalObject ? prefinalObject : {}; // This allows us to use the most recent result object in the recursive call
      for (let key in inputObject) {
        let value = inputObject[key];
        let newKey = current ? `${current}.${key}` : key;
        if (value && typeof value === "object") {
          changeObjectToDotNotationFormat(value, newKey, result);
        } else {
          result[newKey] = value;
        }
      }
      return result;
    }
jiobiagba
  • 46
  • 3
1

i think this would be more elegant...

const toDotNot = (input, parentKey) => Object.keys(input || {}).reduce((acc, key) => {
  const value = input[key];
  const outputKey = parentKey ? `${parentKey}.${key}` : `${key}`;

  // NOTE: remove `&& (!Array.isArray(value) || value.length)` to exclude empty arrays from the output
  if (value && typeof value === 'object' && (!Array.isArray(value) || value.length)) return ({ ...acc, ...toDotNot(value, outputKey) });

  return ({ ...acc, [outputKey]: value });
}, {});

const input = {a: {b: 'c', e: {f: ['g', null, {g: 'h'}]}}, d: []};
const output = toDotNot(input);

console.log(output);

results in:

// output:
{
    "a.b": "c",
    "a.e.f.0": "g",
    "a.e.f.1": null,
    "a.e.f.2.g": "h",
    "d": []
}
jayk
  • 11
  • 2
1

There are already lots of answers here, but for Typescript this solution works pretty well for me and is typed:

type EncapsulatedStringObject =  Record<string, string | object>;
export function convertKeysToDotNotation( object: EncapsulatedStringObject, prefix: string = '' ): Record<string, string> {
    const result: Record<string, string> = {};
    Object.keys( object ).forEach( key => {
        const newPrefix = prefix ? `${prefix}.${key}` : key;
        const value = object[ key ];
        if ( typeof value === 'object' ) {
            Object.assign( result, convertKeysToDotNotation( object[ key ] as EncapsulatedStringObject, newPrefix ) );
        } else {
            result[ newPrefix ] = value;
        }
    } );
    return result;
}
Jeremias Nater
  • 650
  • 3
  • 18