## 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:

```javascript
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.

```javascript
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.

```javascript
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.


```javascript
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.

```javascript
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.

```javascript
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.

```javascript
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:

```javascript
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

```javascript
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`.


```javascript
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.

```javascript
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.

```javascript
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.

```javascript
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.

```javascript
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.

```javascript
  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.

```javascript
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.

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


### Initialize with defaults and filtering additional properties



Simplest possible case.

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

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

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










```javascript
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

```javascript
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

```javascript
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' },
      ]
    }
  }
}
```



```javascript
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"

```javascript
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

```javascript
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++
      }
    }
  }
}
```
