2

I have a nested option object in javascript. What is the best way to provide default values for them?

Custom javascript object read from a json file with ajax:

{
  "fields": {
    "markdown": {
      "preview": {
        "delay": null,
        "custom": "hello"
      },
      "revisions": {
        "path": "revisions/markdown",
        "limit": 5
      }
    }
  }
}

Default options as fallback if values is not set:

{
  "fields": {
    "markdown": {
      "preview": {
        "delay": 5,
        "css": "https://example.com/my/style.css"
      },
      "revisions": {
        "path": "revisions",
        "limit": 10
      }
    }
  }
}

Expected results

Keep everything from custom object and complete it with default values, "css" in this case.

{
  "fields": {
    "markdown": {
      "preview": {
        "delay": null,
        "css": "https://example.com/my/style.css",
        "custom": "hello"
      },
      "revisions": {
        "path": "revisions/markdown",
        "limit": 5
      }
    }
  }
}

Notes:

  • The custom object may not include all the nested values. If values are missing default value should apply.
  • Null, empty and 0 is accepted values and should not be overwritten by default values.
  • Values in the custom object that is missing as default values makes no harm and does not need to be dealt with.

What I've tried to far

Jens Törnell
  • 23,180
  • 45
  • 124
  • 206
  • 1
    Can you use a library? `jQuery.extend()` has a `deep` option that will recurse. Lodash has `_.merge()`. – Barmar Sep 13 '19 at 06:45
  • @Barmar I don't want to do that as I already use this in Vue. But in general, that's good information. Thanks! – Jens Törnell Sep 13 '19 at 06:48

2 Answers2

1

You could take a recursive approach by checking the value of the object, if an object and check the nested objects or assign the value if the key does not exist.

function update(object, fallback) {
    Object
        .entries(fallback)
        .forEach(([k, v]) => {
            if (v && typeof v === 'object') {
                update(object[k] = object[k] || (Array.isArray(v) ? [] : {}), v);
            } else if (!(k in object)) {
                object[k] = v;
            }
        });
}

var object = { fields: { markdown: { preview: { delay: null, custom: "hello" }, revisions: { path: "revisions/markdown", limit: 5 } } } },
    fallback = { fields: { markdown: { preview: { delay: 5, css: "https://example.com/my/style.css" }, revisions: { path: "revisions", limit: 10 } } } };

update(object, fallback);

console.log(object);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Nina Scholz
  • 376,160
  • 25
  • 347
  • 392
0

You can use default-composer library that work with nested objects:

import { defaultComposer, setConfig } from "default-composer";

// Create a custom emptyChecker
setConfig({ isDefaultableValue: (key, value) => value === undefined })

const data = {
  "fields": {
    "markdown": {
      "preview": {
        "delay": null,
        "custom": "hello"
      },
      "revisions": {
        "path": "revisions/markdown",
        "limit": 5
      }
    }
  }
}

const fallback = {
  "fields": {
    "markdown": {
      "preview": {
        "delay": 5,
        "css": "https://example.com/my/style.css"
      },
      "revisions": {
        "path": "revisions",
        "limit": 10
      }
    }
  }
}

const result = defaultComposer(fallback, data)
console.log(result)

And the output:

{
  "fields": {
    "markdown": {
      "preview": {
        "delay": null,
        "css": "https://example.com/my/style.css",
        "custom": "hello"
      },
      "revisions": {
        "path": "revisions/markdown",
        "limit": 5
      }
    }
  }
}
Aral Roca
  • 5,442
  • 8
  • 47
  • 78