## JSON Schema-based Object Initialization in JavaScript

An initializer function is itself a function of a JSON Schema. Initializer functions are generated by parsing a schema and generating code to perform object initialization based on default schema values, etc. The purpose of generating functions rather than traversing the schema during initialization is performance. Code without iteration and repetitive heap access is orders of magnitude faster.

A generated function might conform to a set of predetermined behaviors, or it might take options. There are many ways the code for initializer functions could be expressed.

There are two options that need to be considered in the initialization process.

- `defaults`: This option specifies whether default values should be included in the resultant object (where values are not specified on the source). This defaults to true.

- `filter`: When filter is true, the source object is not being deep copied onto the target. Rather, the value of the source object -- of any field that is defined on the schema as it is being traversed -- is copied to the target object. Thus, data that is specified by the schema (regardless of correctness) is copied and fields that are not specified by the schema are ignored. This defaults to false.

These two options in any combination produce the following behaviour:

defaults | filter additional | behavior
--- | --- | ---
0 | 1 | iterate over schema and assign, without initializing defaults
0 | 0 | deep copy
1 | 1 | iterate over schema and assign, generating defaults
1 | 0 | deep copy, then iterate over schema and assign, generating defaults

### Initialize with no defaults and filtering additional properties

In order to rigorously examine and evaluate the various forms generated code might take, we shall herein build up from the simplest possible cases.

Greg says:

> When 'additional' is false, we're not deep copying the source object to the target, but we're not validating the data either. Thus, incorrect data (according the schema) is allowed, but data that is not present on the schema at all is disallowed.

> "Deep copy for all fields that are defined on the schema"

Given the following schema:

    let schema = {
      type: 'object',
      properties: {
        a: {
          type: 'object',
          properties: {
            b: {
              type: 'number'
            }
          }
        },
        c: {
          type: 'boolean'
        }
      }
    }

The most naive initializer function implementation performs direct referencing for assignment.

    function (target, source) {
      target.a.b = source.a.b
      target.c = source.c
    }

There are two immediate problems with the foregoing. First, we don't want to assign `undefined` to the target if the property is not a member of the source object. Second, In any case where `target.a` or `source.a` is not defined an error will be thrown.

We only want to set properties on the target object if they are present on the source. We also only want to create the nested container object `target.a` if `source.a` is defined.

Let's deal with the latter problem first.

    function (target, source) {
      if (source.a && !target.a) {
        target.a = {}
      }

      target.a.b = source.a.b
      target.c = source.c
    }

And now let's not assign undefined properties of the source object to undefined properties of the target.

    function (target, source) {
      if (source.a && !target.a) {
        target.a = {}
      }

      if (source.a && source.a.b) {
        target.a.b = source.a.b
      }

      if (source.c) {
        target.c = source.c
      }
    }

This code suffers from the truthiness qualities of JavaScript primitives. In the case where a member of the source object is present with the value `null`, `false`, `""`, or `undefined`, the corresponding member will not be set on the target object.

Let's fix that. While we're at it, let's use bracket references to accommodate property names that are not valid JavaScript symbols.

    function (target, source) {
      if (source.hasOwnProperty('a') && !target.hasOwnProperty('a')) {
        target['a'] = {}
      }

      if (source.hasOwnProperty('a') && source.a.hasOwnProperty('b')) {
        target['a']['b'] = source['a']['b']
      }

      if (source.hasOwnProperty('c')) {
        target['c'] = source['c']
      }
    }

Our next problem is repetitive access to values which live on the heap. Introducing new variables for the `a` object on both source and target let's us manipulate that object directly without reference to it's parent.

    function (target, source) {
      if (source.hasOwnProperty('a') && !target.hasOwnProperty('a')) {
        target['a'] = {}
      }

      let sc = source['a']
      let tc = target['a']

      if (sc && sc.hasOwnProperty('b')) {
        tc['b'] = sc['b']
      }

      if (source.hasOwnProperty('c')) {
        target['c'] = source['c']
      }
    }

This may have a negligible impact with one member to assign, but with a large number of properties on `a` the difference becomes meaningful. Let's extend our schema to illustrate.

    let schema = {
      type: 'object',
      properties: {
        a: {
          type: 'object',
          properties: {
            b: { type: 'number' },
            c: { type: 'string' },
            d: { type: 'boolean' },
            e: { type: 'array' }
          }
        },
        f: {
          type: 'boolean'
        }
      }
    }

And the initializer function:

    function (target, source) {
      if (source.hasOwnProperty('a') && !target.hasOwnProperty('a')) {
        target['a'] = {}
      }

      let sc = source['a']
      let tc = target['a']

      if (sc) {
        if (sc.hasOwnProperty('b')) {
          tc['b'] = sc['b']
        }

        if (sc.hasOwnProperty('c')) {
          tc['c'] = sc['c']
        }

        if (sc.hasOwnProperty('d')) {
          tc['d'] = sc['d']
        }

        if (sc.hasOwnProperty('e')) {
          tc['e'] = sc['e']
        }
      }

      if (source.hasOwnProperty('f')) {
        target['f'] = source['f']
      }
    }

What happens when we have more than one nested schema at the root level

    let schema = {
      type: 'object',
      properties: {
        a: {
          type: 'object',
          properties: {
            b: { type: 'number' },
            c: { type: 'string' }
          }
        },
        d: {
          type: 'object',
          properties: {
            e: { type: 'boolean' },
            f: { type: 'array' }
          }
        },
        g: {
          type: 'boolean'
        }
      }
    }

We can avoid creating additional variables on the stack by reusing the symbols `sc` and `tc` (defined for `a` in the previous example) with `d`.

    function (target, source) {
      var sc
      var tc

      if (source.hasOwnProperty('a') && !target.hasOwnProperty('a')) {
        target['a'] = {}
      }

      sc = source['a']
      tc = target['a']

      if (typeof sc === 'object') {
        if (sc.hasOwnProperty('b')) {
          tc['b'] = sc['b']
        }

        if (sc.hasOwnProperty('c')) {
          tc['c'] = sc['c']
        }
      }

      if (source.hasOwnProperty('d') && !target.hasOwnProperty('d')) {
        target['d'] = {}
      }

      sc = source['d']
      tc = target['d']

      if (typeof sc === 'object') {
        if (sc.hasOwnProperty('e')) {
          tc['e'] = sc['e']
        }

        if (sc.hasOwnProperty('f')) {
          tc['f'] = sc['f']
        }
      }

      if (source.hasOwnProperty('g')) {
        target['g'] = source['g']
      }
    }

> NOTE: in creating nested containers on target, we'll eventually need to consider the case of Array containers.

What happens when we have more than one level of nesting.

    let schema = {
      type: 'object',
      properties: {
        a: {
          type: 'object',
          properties: {
            b: {
              type: 'object',
              properties: {
                c: { type: 'boolean' },
                d: { type: 'array' }
              }
            },
            e: { type: 'number' }
          }
        },
        f: {
          type: 'object',
          properties: {
            g: { type: 'boolean' }
          }
        }
      }
    }

As we traverse this schema, each time we nest deeper, we need to create new symbols bound to the parent of properties at that new level. Without doing so, we will lose track of the grandparent (`a`), which we'll need in order to reference any subsequent siblings (`e`) of the parent (`b`).

We can reuse symbols as long as they're being resused for the same depth in the nested schema.

    function (target, source) {
      var source1
      var target1
      var source2
      var target2

      if (source.hasOwnProperty('a') && !target.hasOwnProperty('a')) {
        target['a'] = {}
      }

      source1 = source['a']
      target1 = target['a']

      if (typeof source1 === 'object') {
        if (source1.hasOwnProperty('b') && !target1.hasOwnProperty('b')) {
          target1['b'] = {}
        }

        // we can't reuse source1/target1 here because we'll need a reference
        // to it's current value when moving on from `b`.
        source2 = source1['b']
        target2 = target1['b']

        if (typeof source2 === 'object') {
          if (source2.hasOwnProperty('c')) {
            target2['c'] = source2['c']
          }

          if (source2.hasOwnProperty('d')) {
            target2['d'] = source2['d']
          }
        }

        if (source1.hasOwnProperty('e')) {
          target1['e'] = source1['e']
        }
      }

      if (source.hasOwnProperty('f') && !target.hasOwnProperty('f')) {
        target['f'] = {}
      }

      source1 = source['f']
      target1 = target['f']

      if (typeof source1 === 'object') {
        if (source1.hasOwnProperty('g')) {
          target1['g'] = source1['g']
        }
      }
    }

We now address the problems of source values that differ from the expectation of the schema. If a source object contains a value such as `false` where a nested object is expected, we should copy that value, instead of trying to traverse deeper into the source.

We also want to avoid overwriting existing values on the target if there is no value defined on the source and a non-object value defined for the same property on the target.

Further, we want to ensure that if the source member is an object, that the target becomes an object, regardless of what may or may not be defined there.

    function (target, source) {
      var source1
      var target1
      var source2
      var target2

      // CHANGED FROM PREVIOUS
      if (source.hasOwnProperty('a')) {
        if (typeof source['a'] === 'object') {
          if (!target.hasOwnProperty('a') || typeof target['a'] !== 'object') {
            target['a'] = {}
          }
        } else {
          target['a'] = source['a']
        }
      }

      source1 = source['a']
      target1 = target['a']

      if (typeof source1 === 'object') {
        if (source1.hasOwnProperty('b')) {
          target1['b'] = source1['b']
        }

        if (source1.hasOwnProperty('c')) {
          if (typeof source1['c'] === 'object') {
            if (!target1.hasOwnProperty('c') || typeof target['c'] !== 'object') {
              target1['c'] = {}
            }
          } else {
            target1['c'] = source1['c']
          }
        }

        source2 = source1['c']
        target2 = target1['c']

        if (typeof source2 === 'object') {
          if (source2.hasOwnProperty('d')) {
            target2['d'] = source2['d']
          }

          if (source2.hasOwnProperty('e')) {
            target2['e'] = source2['e']
          }
        }
      }

      if (source.hasOwnProperty('f')) {
        if (typeof source['f'] === 'object') {
          if (!target.hasOwnProperty('f') || typeof target['f'] !== 'object') {
            target['f'] = {}
          }
        } else {
          target['f'] = source['f']
        }
      }

      source1 = source['f']
      target1 = target['f']

      if (typeof source1 === 'object') {
        if (source1.hasOwnProperty('g')) {
          target1['g'] = source1['g']
        }
      }
    }

In the preceding example, we're still prematurely creating nested container objects on the target where there may be no corresponding values defined in the source object. In order to avoid this, we'll need to delay assigning the container object until we know there are properties assigned to it. This requires keeping a counter of properties assigned and checking that value upon completion of the branch.

Once again, count symbols can be reused. However, we'll need a new counter for each level of nesting.

    function (target, source) {
      var source1
      var target1
      var count1

      var source2
      var target2
      var count2

      if (source.hasOwnProperty('a')) {
        if (typeof source['a'] === 'object') {
          if (!target.hasOwnProperty('a') || typeof target['a'] !== 'object') {
            target1 = {}
          }
        } else {
          target['a'] = source['a']
        }
      }

      source1 = source['a']
      count1 = 0

      if (typeof source1 === 'object') {
        if (source1.hasOwnProperty('b')) {
          target1['b'] = source1['b']
          count1++
        }

        if (source1.hasOwnProperty('c')) {
          if (typeof source1['c'] === 'object') {
            if (!target1.hasOwnProperty('c') || typeof target['c'] !== 'object') {
              target2 = {}
            }
          } else {
            target1['c'] = source1['c']
            count1++
          }
        }

        source2 = source1['c']
        count2 = 0

        if (typeof source2 === 'object') {
          if (source2.hasOwnProperty('d')) {
            target2['d'] = source2['d']
            count2++
          }

          if (source2.hasOwnProperty('e')) {
            target2['e'] = source2['e']
            count2++
          }
        }

        if (count2 > 0) {
          target1['c'] = target2
          count1++
        }
      }

      if (count1 > 0) {
        target['a'] = target1
      }

      if (source.hasOwnProperty('f')) {
        if (typeof source['f'] === 'object') {
          if (!target.hasOwnProperty('f') || typeof target['f'] !== 'object') {
            target1 = {}
          }
        } else {
          target['f'] = source['f']
        }
      }

      source1 = source['f']
      count1 = 0

      if (typeof source1 === 'object') {
        if (source1.hasOwnProperty('g')) {
          target1['g'] = source1['g']
          count1++
        }
      }

      if (count1 > 0) {
        target['f'] = target1
      }
    }

We're potentially performing a superfluous variable assignment in the above code along with duplicating conditional logic.

      if (source.hasOwnProperty('a')) {
        if (typeof source['a'] === 'object') {
          if (!target.hasOwnProperty('a') || typeof target['a'] !== 'object') {
            target1 = {}
          }
        } else {
          target['a'] = source['a']
        }
      }

      // this is unnecessary if `source.hasOwnProperty('a')` is false
      source1 = source['a']
      count1 = 0

      // this check is being duplicated and is not necessary when `source.hasOwnProperty('a')` is false
      if (typeof source1 === 'object') {
        if (source1.hasOwnProperty('b')) {

This issue is addressed by refactoring the potentially unnecessary code to happen only if the relevant conditional passes.

    function (target, source) {
      var source1
      var target1
      var count1

      var source2
      var target2
      var count2

      if (source.hasOwnProperty('a')) {
        if (typeof source['a'] === 'object') {
          if (!target.hasOwnProperty('a') || typeof target['a'] !== 'object') {
            target1 = {}
          } else {
            target1 = target['a']
          }

          source1 = source['a']
          count1 = 0

          if (source1.hasOwnProperty('b')) {
            target1['b'] = source1['b']
            count1++
          }

          if (source1.hasOwnProperty('c')) {
            if (typeof source1['c'] === 'object') {
              if (!target1.hasOwnProperty('c') || typeof target['c'] !== 'object') {
                target2 = {}
              } else {
                target2 = target['a']['c']
              }

              source2 = source1['c']
              count2 = 0

              if (source2.hasOwnProperty('d')) {
                target2['d'] = source2['d']
                count2++
              }

              if (source2.hasOwnProperty('e')) {
                target2['e'] = source2['e']
                count2++
              }

              if (count2 > 0) {
                target1['c'] = target2
                count1++
              }

            } else {
              target1['c'] = source1['c']
              count1++
            }
          }

          if (count1 > 0) {
            target['a'] = target1
          }

        } else {
          target['a'] = source['a']
        }
      }

      if (source.hasOwnProperty('f')) {
        if (typeof source['f'] === 'object') {
          if (!target.hasOwnProperty('f') || typeof target['f'] !== 'object') {
            target1 = {}
          } else {
            target1 = target['f']
          }

          source1 = source['f']
          count1 = 0

          if (source1.hasOwnProperty('g')) {
            target1['g'] = source1['g']
            count1++
          }

          if (count1 > 0) {
            target['f'] = target1
          }

        } else {
          target['f'] = source['f']
        }
      }
    }

### Initialize with no defaults and include additional properties

if you want to allow additional attributes and don't need to generate defaults, the most efficient initialization is deep copy.

    function initialize1 (target, source, options) {
      return Object.assign(target, JSON.parse(JSON.stringify(source)))
    }

### Initialize with defaults and filtering additional properties

Simplest possible case.

    let schema = {
      type: 'object',
      properties: {
        a: { default: 'foo' }
      }
    }

    function (target, source) {
      target['a'] = source['a'] || 'foo'
    }

    function (target, source) {
      if (source.hasOwnProperty('a')) {
        target['a'] = source['a']
      } else {
        target['a'] = 'foo'
      }
    }

    let schema = {
      type: 'object',
      properties: {
        a: {
          type: 'object',
          properties: {
            b: { type: 'number' },
            c: {
              type: 'object',
              properties: {
                d: { type: 'boolean', default: false },
                e: { type: 'string' }
              }
            }
          }
        },
        f: {
          type: 'object',
          properties: {
            g: { type: 'boolean' }
          }
        }
      }
    }

Optional defaults

    function (target, source) {
      var source1
      var target1
      var count1

      var source2
      var target2
      var count2

      if (source.hasOwnProperty('a')) {
        if (typeof source['a'] === 'object') {
          if (!target.hasOwnProperty('a') || typeof target['a'] !== 'object') {
            target1 = {}
          } else {
            target1 = target['a']
          }

          source1 = source['a']
          count1 = 0

          if (source1.hasOwnProperty('b')) {
            target1['b'] = source1['b']
            count1++
          }

          if (source1.hasOwnProperty('c')) {
            if (typeof source1['c'] === 'object') {
              if (!target1.hasOwnProperty('c') || typeof target['c'] !== 'object') {
                target2 = {}
              } else {
                target2 = target['a']['c']
              }

              source2 = source1['c']
              count2 = 0

              if (source2.hasOwnProperty('d')) {
                target2['d'] = source2['d']
                count2++

              // CHANGED TO ADD DEFAULT
              } else if (options.defaults !== false) {
                target2['d'] = false
                count2++
              }

              if (source2.hasOwnProperty('e')) {
                target2['e'] = source2['e']
                count2++
              }

              if (count2 > 0) {
                target1['c'] = target2
                count1++
              }

            } else {
              target1['c'] = source1['c']
              count1++
            }
          }

          if (count1 > 0) {
            target['a'] = target1
          }

        } else {
          target['a'] = source['a']
        }
      }

      if (source.hasOwnProperty('f')) {
        if (typeof source['f'] === 'object') {
          if (!target.hasOwnProperty('f') || typeof target['f'] !== 'object') {
            target1 = {}
          } else {
            target1 = target['f']
          }

          source1 = source['f']
          count1 = 0

          if (source1.hasOwnProperty('g')) {
            target1['g'] = source1['g']
            count1++
          }

          if (count1 > 0) {
            target['f'] = target1
          }

        } else {
          target['f'] = source['f']
        }
      }
    }

### ARRAY CONTAINER

    let schema = {
      type: 'object',
      properties: {
        a: {
          type: 'object',
          properties: {
            b: { type: 'number' },
            c: {
              type: 'object',
              properties: {
                d: {
                  type: 'array',
                  items: {
                    e: { type: 'boolean', default: false },
                    f: { type: 'string' }
                  }
                },
              }
            }
          }
        }
      }
    }

    let example = {
      a: {
        b: 3,
        c: {
          d: [
            { e: true, f: 'foo' },
            { e: false, f: 'bar' },
            { e: true, f: 'baz' },
            { e: false, f: 'qux' },
          ]
        }
      }
    }

    function (target, source) {
      var source1
      var target1
      var count1

      var source2
      var target2
      var count2

      if (source.hasOwnProperty('a')) {
        if (typeof source['a'] === 'object') {
          if (!target.hasOwnProperty('a') || typeof target['a'] !== 'object') {
            target1 = {}
          } else {
            target1 = target['a']
          }

          source1 = source['a']
          count1 = 0

          if (source1.hasOwnProperty('b')) {
            target1['b'] = source1['b']
            count1++
          }

          if (source1.hasOwnProperty('c')) {
            if (typeof source1['c'] === 'object') {
              if (!target1.hasOwnProperty('c') || typeof target1['c'] !== 'object') {
                target2 = {}
              } else {
                target2 = target1['c']
              }

              source2 = source1['c']
              count2 = 0

              if (source2.hasOwnProperty('d')) {
                if (Array.isArray(source2['d'])) {
                  if (!target2.hasOwnProperty('d') || !Array.isArray(target2['d']) {
                    target3 = []
                  } else {
                    target3 = target2['d']
                  }

                  source3 = source2['d']
                  count3 = 0

                  for (i = 0, l = source3.length; i < l; i++) {

                    // should check for nulls and arrays here
                    if (typeof source3[i] === 'object') {
                      if (!target3[i] || typeof target3[i] !== 'object') {
                        target4 = {}
                      } else {
                        target4 = target3[i]
                      }

                      source4 = source3[i]
                      count4 = 0

                      if (source4.hasOwnProperty('e')) {
                        target4['e'] = source4['e']
                        count4++
                      }

                      if (source4.hasOwnProperty('f')) {
                        target4['f'] = source4['f']
                        count4++
                      }

                      if (count4 > 0) {
                        target3[i] = target4
                        count3++
                      }
                    } else {
                      target3[i] = source3[i]
                      count3++
                    }
                  }

                  if (count3 > 0) {
                    target2['d'] = target3
                    count2++
                  }
                }
              }

              if (count2 > 0) {
                target1['c'] = target2
                count1++
              }

            } else {
              target1['c'] = source1['c']
              count1++
            }
          }

          if (count1 > 0) {
            target['a'] = target1
          }

        } else {
          target['a'] = source['a']
        }
      }
    }

### Arrays with schemas per index and "additionalItems"

    let schema = {
      type: 'object',
      properties: {
        a: {
          type: 'object',
          properties: {
            b: { type: 'number' },
            c: {
              type: 'object',
              properties: {
                d: {
                  type: 'array',
                  items: [
                    { type: 'integer', default: 3 },
                    { type: 'object', properties: { e: { default: 'null' } } }
                  ],
                  additionalItems: {
                    properties: {
                      e: { default: 'w00t' }
                    }
                  }
                }
              }
            }
          }
        }
      }
    }

    let source = {
      a: {
        b: {
          g: [
            {},
            {},
            {},
            {}
          ]
        }
      }
    }

    let target = {
      a: {
        b: {
          g: [
            {},
            {},
            {},
            { h: 'w00t' }
          ]
        }
      }
    }

    function (target, source) {
      var source1
      var target1
      var count1

      var source2
      var target2
      var count2

      if (source.hasOwnProperty('a')) {
        if (typeof source['a'] === 'object') {
          if (!target.hasOwnProperty('a') || typeof target['a'] !== 'object') {
            target1 = {}
          } else {
            target1 = target['a']
          }

          source1 = source['a']
          count1 = 0

          if (source1.hasOwnProperty('b')) {
            target1['b'] = source1['b']
            count1++
          }

          if (source1.hasOwnProperty('c')) {
            if (typeof source1['c'] === 'object') {
              if (!target1.hasOwnProperty('c') || typeof target1['c'] !== 'object') {
                target2 = {}
              } else {
                target2 = target1['c']
              }

              source2 = source1['c']
              count2 = 0

              if (source2.hasOwnProperty('d')) {
                if (Array.isArray(source2['d'])) {
                  if (!target2.hasOwnProperty('d') || !Array.isArray(target2['d']) {
                    target3 = []
                  } else {
                    target3 = target2['d']
                  }

                  source3 = source2['d']
                  count3 = 0

                  if (0 < source3.length) {
                    target3[0] = source3[0]
                    count3++
                  }

                  if (1 < source3.length) {
                    if (typeof source3[1] === 'object') {
                      if (1 >= target3.length || typeof target3[1] !== 'object') {
                        target4 = {}
                      } else {
                        target4 = target3[1]
                      }

                      source4 = source3[1]
                      count4 = 0

                      if (source4.hasOwnProperty('e')) {
                        target4['b'] = source4['e']
                        count4++
                      }

                      if (count4 > 0) {
                        target3[1] = target4
                        count3++
                      }
                    } else {
                      target3[1] = source3[1]
                      count3++
                    }
                  }

                  for (i = 3, l = source3.length; i < l; i++) {

                    // should check for nulls and arrays here
                    if (typeof source3[i] === 'object') {
                      if (!target3[i] || typeof target3[i] !== 'object') {
                        target4 = {}
                      } else {
                        target4 = target3[i]
                      }

                      source4 = source3[i]
                      count4 = 0

                      if (source4.hasOwnProperty('e')) {
                        target4['e'] = source4['e']
                        count4++
                      } else if (options.defaults !== false) {
                        target4['e'] = 'w00t'
                        count4++
                      }

                      if (count4 > 0) {
                        target3[i] = target4
                        count3++
                      }
                    } else {
                      target3[i] = source3[i]
                      count3++
                    }
                  }

                  if (count3 > 0) {
                    target2['d'] = target3
                    count2++
                  }
                }
              }

              if (count2 > 0) {
                target1['c'] = target2
                count1++
              }

            } else {
              target1['c'] = source1['c']
              count1++
            }
          }

          if (count1 > 0) {
            target['a'] = target1
          }

        } else {
          target['a'] = source['a']
        }
      }
    }

### Defaults on parent and current nested objects

    let schema = {
      type: 'object',
      properties: {
        a: {
          type: 'object',
          default: { b: 100 },
          properties: {
            b: { type: 'number', default: 20 },
            c: { type: 'string', default: 'foo' }
          }
        }
      }
    }

    function (target, source) {
      var source0 = source
      var target0 = target
      var count0 = 0

      var source1
      var target1
      var count1

      if (options.defaults !== false) {
        target0['a'] = { b: 100 }
        count0++
      }

      if (source0.hasOwnProperty('a')) {
        if (typeof source['a'] === 'object') {
          if (!target0.hasOwnProperty('a') || typeof target0['a'] !== 'object') {
            target1 = {}
          } else {
            target1 = target0['a']
          }

          source1 = source0['a']
          count1 = 0

          // EXISTING
          // if (source1.hasOwnProperty('b')) {
          //   target1['b'] = source1['b']
          //   count1++
          // } else if (options.defaults != false) {
          //   target1['b'] = 20
          //   count1++
          // }

          // CHANGES HERE
          //-----------------
          if (options.defaults !== false) {
            target1['b'] = 20
            count1++
          }

          if (source1.hasOwnProperty('b')) {
            target1['b'] = source1['b']
            count1++
          }

          //-----------------

          if (options.defaults !== false) {
            target1['c'] = 'foo'
            count1++
          }

          if (source1.hasOwnProperty('c')) {
            target1['c'] = source1['c']
            count1++
          }
          //-----------------

          if (count1 > 0) {
            target0['a'] = target1
            count0++
          }
        }
      }
    }