function parseObject ({ banner, api, itemName, masterType, verifyCategory, verifySerializable }) {
  let obj = api[ itemName ]

  const printErrorAndExit = msg => {
    logError(`${ banner } ${ msg }`)
    console.error(obj)
    console.log()
    process.exit(1)
  }

  if (obj.hasOwnProperty('addedIn') === true) {
    const result = parseAddedIn(obj.addedIn)
    if (result !== true) {
      printErrorAndExit(result)
    }
  }

  if (obj.extends !== void 0 && extendApi[ masterType ] !== void 0) {
    if (extendApi[ masterType ][ obj.extends ] === void 0) {
      printErrorAndExit(`extends "${ obj.extends }" which does not exists`)
    }

    api[ itemName ] = merge(
      {},
      extendApi[ masterType ][ obj.extends ],
      api[ itemName ]
    )
    delete api[ itemName ].extends

    obj = api[ itemName ]
  }

  // there are cases where you extend something but you
  // need to remove some props from the extended object
  if (obj.__delete !== void 0) {
    if (Array.isArray(obj.__delete) === false) {
      printErrorAndExit('"__delete" prop must be an Array')
    }

    if (obj.__delete.some(prop => typeof prop !== 'string')) {
      printErrorAndExit('"__delete" prop must be an Array of Strings')
    }

    obj.__delete.forEach(prop => {
      delete obj[ prop ]
    })

    // now delete the __delete prop itself (we don't need it in the final API)
    delete obj.__delete
  }

  let type

  if ([ 'props', 'modifiers' ].includes(masterType)) {
    if (obj.type === void 0) {
      printErrorAndExit('missing "type" prop')
    }

    type = Array.isArray(obj.type) || obj.type === 'Any'
      ? 'MultipleTypes'
      : obj.type
  }
  else {
    type = masterType
  }

  type = type.startsWith('Promise') ? 'Promise' : type
  const def = objectTypes[ type ]

  if (def === void 0) {
    printErrorAndExit(`object has unrecognized API type prop value: "${ type }"`)
  }

  if (obj.internal !== true) {
    const regexList = Array.isArray(obj.type)
      ? (obj.type.includes('Any') ? [] : obj.type.map(t => apiValueRegex[ t ]).filter(v => v))
      : (obj.type === 'Any' ? [] : [ apiValueRegex[ obj.type ] ].filter(v => v))

    for (const prop in obj) {
      // These props are always valid and doesn't need to be specified in 'props' of 'objectTypes' entries
      if ([ 'type', '__exemption' ].includes(prop) === true) {
        continue
      }

      if (prop === '__runtimeDefault') {
        if (obj.__runtimeDefault !== true) {
          printErrorAndExit(
            'props > "__runtimeDefault" should only be set to true; Solutions:'
            + '\n  1. delete it as it is indeed an error'
            + '\n  2. it is being inherited, so add "delete": [ "__runtimeDefault" ]'
          )
        }

        continue
      }

      // 'configFileType' is always valid in any level of 'quasarConfOptions' and nothing else
      if (prop === 'configFileType' && banner.includes('"quasarConfOptions"')) {
        continue
      }

      if (!def.props.includes(prop)) {
        printErrorAndExit(`object has unrecognized API prop "${ prop }" for its type (${ type })`)
      }
    }

    ;[ ...def.required, ...(verifyCategory ? [ 'category' ] : []) ].forEach(prop => {
      if (obj.__exemption !== void 0 && obj.__exemption.includes(prop)) {
        return
      }

      // 'examples' property is not required if 'definition' or 'values' properties are specified
      if (prop === 'examples' && (obj.definition !== void 0 || obj.values !== void 0)) {
        const matchedProp = obj.definition !== void 0
          ? 'definition'
          : 'values'

        printErrorAndExit(`"examples" is not needed because there is "${ matchedProp }"; remove it`)
        return
      }

      if (obj[ prop ] === void 0) {
        printErrorAndExit(`missing required API prop "${ prop }" for its type (${ type })`)
      }
    })

    // Since we processed '__exemption', we can strip it
    if (obj.__exemption !== void 0) {
      const { __exemption, ...p } = obj
      api[ itemName ] = p
    }

    def.isBoolean && def.isBoolean.forEach(prop => {
      if (obj.hasOwnProperty(prop) && obj[ prop ] !== true && obj[ prop ] !== false) {
        printErrorAndExit(`"${ prop }" is not a Boolean`)
      }
    })
    def.isObject && def.isObject.forEach(prop => {
      if (obj[ prop ] && Object(obj[ prop ]) !== obj[ prop ]) {
        printErrorAndExit(`"${ prop }" is not an Object`)
      }
    })
    def.isArray && def.isArray.forEach(prop => {
      if (obj[ prop ] && !Array.isArray(obj[ prop ])) {
        printErrorAndExit(`"${ prop }" is not an Array`)
      }
    })
    def.isString && def.isString.forEach(prop => {
      if (obj[ prop ] && typeof obj[ prop ] !== 'string') {
        printErrorAndExit(`"${ prop }" is not a String`)
      }
    })

    if (obj.type) {
      const list = Array.isArray(obj.type) ? obj.type : [ obj.type ]
      list.forEach(t => {
        if (typeList.includes(t) === false) {
          printErrorAndExit(
            `object has unrecognized type "${ t }"; if this is a new type, then `
            + 'add it to the "typeList" array in build.api.js'
          )
        }
      })
    }

    if (obj.values) {
      if (obj.values.some(val => typeof val !== 'string')) {
        printErrorAndExit('object: stringify each of "values" entries')
      }

      if (regexList.length !== 0) {
        obj.values.forEach(val => {
          if (
            apiIgnoreValueRegex.test(val) === false
            && regexList.every(regex => regex.test(val) === false)
          ) {
            printErrorAndExit(
              `object: "values" -> "${ val }" value must satisfy regex: `
              + `${ regexList.map(r => r.toString()).join(' or ') }`
            )
          }
        })
      }
    }

    if (obj.hasOwnProperty('default')) {
      if (typeof obj.default !== 'string') {
        printErrorAndExit('object: stringify "default" value')
      }

      if (apiIgnoreValueRegex.test(obj.default) === false) {
        if (regexList.length !== 0 && regexList.every(regex => regex.test(obj.default) === false)) {
          printErrorAndExit(
            `object: "default" value must satisfy regex: ${ regexList.map(r => r.toString()).join(' or ') }`
          )
        }

        if (obj.values && obj.values.includes(obj.default) === false) {
          printErrorAndExit('object: "default" value must be one of the "values"')
        }
      }
    }

    if (obj.examples !== void 0) {
      if (obj.examples.some(val => typeof val !== 'string')) {
        printErrorAndExit('object: stringify each of "examples" entries')
      }

      if (regexList.length !== 0) {
        obj.examples.forEach(val => {
          if (
            apiIgnoreValueRegex.test(val) === false
            && regexList.every(regex => regex.test(val) === false)
          ) {
            printErrorAndExit(
              `object: "examples" -> "${ val }" value must satisfy regex: ${ regexList.map(r => r.toString()).join(' or ') }`
            )
          }
        })
      }

      if ((new Set(obj.examples)).size !== obj.examples.length) {
        printErrorAndExit('object has "examples" Array with duplicates')
      }
    }

    if (
      obj.hasOwnProperty('passthrough') === true
      && passthroughValues.includes(obj.passthrough) === false
    ) {
      printErrorAndExit(`"passthrough" should be one of: ${ passthroughValues.join('|') }`)
    }

    if (obj.default !== void 0 && obj.required === true) {
      if (
        Array.isArray(obj.type) === true
          ? (obj.type.includes('Any') !== true && obj.type.includes('undefined') !== true)
          : [ 'Any', 'undefined' ].includes(obj.type) !== true
      ) {
        printErrorAndExit(
          'cannot have "required" as true since it is optional because it has "default" '
          + '(if default is still required as it handles the "undefined" value, then '
          + 'add "__requireWithDefault": true)'
        )
      }
    }

    // If required is specified, use it, if not and it has a default value, then it's optional,
    // otherwise use undefined so it can get overridden later
    api[ itemName ].required = obj.required !== void 0
      ? obj.required
      : (obj.default !== void 0 ? false : undefined)
  }

  if (obj.tsType && obj.autoDefineTsType === true && !obj.definition) {
    printErrorAndExit(
      `object is auto defining "${ obj.tsType }" TS type but it is missing "definition" prop`
    )
  }

  if (masterType === 'props') {
    if (Array.isArray(obj.type) === true && (new Set(obj.type)).size !== obj.type.length) {
      printErrorAndExit(
        'object has "type" defined as Array, but the Array contains duplicates'
      )
    }

    if (itemName.indexOf('class') !== -1) {
      if (obj.type === 'Object' && obj.tsType !== 'VueClassObjectProp') {
        printErrorAndExit(
          'object is class-type (Object form) but "tsType" prop is set to '
          + `"${ obj.tsType }" instead of "VueClassObjectProp":`
        )
      }
      else if (obj.tsType !== 'VueClassProp' && isClassStyleType(obj.type) === true) {
        printErrorAndExit(
          'object is class-type (String/Array/Object form) but "tsType" prop '
          + `is set to "${ obj.tsType }" instead of "VueClassProp":`
        )
      }
    }
    else if (itemName.indexOf('style') !== -1) {
      if (obj.type === 'Object' && obj.tsType !== 'VueStyleObjectProp') {
        printErrorAndExit(
          'object is style-type (Object form) but "tsType" prop is '
          + `set to "${ obj.tsType }" instead of "VueStyleObjectProp":`
        )
      }
      else if (obj.tsType !== 'VueStyleProp' && isClassStyleType(obj.type) === true) {
        printErrorAndExit(
          'object is style-type (String/Array/Object form) but "tsType" prop '
          + `is set to "${ obj.tsType }" instead of "VueStyleProp":`
        )
      }
    }

    if (verifySerializable && obj.configFileType === undefined && isSerializable(obj) === false) {
      printErrorAndExit(
        'object\'s type is non-serializable but props in "quasarConfOptions" can only consist of '
        + `${ serializableTypes.join('/') } to be used in quasar.config file. Use "configFileType" `
        + 'prop to specify a serializable type for quasar.config file, or set to null if there is no suitable type:'
      )
    }
  }

  // If it must be synced, then it is syncable too (v-model:xyz)
  if (obj.sync === true) {
    obj.syncable = true
  }

  if (obj.returns) {
    parseObject({
      banner: `${ banner }/"returns"`,
      api: api[ itemName ],
      itemName: 'returns',
      masterType: 'props'
    })
  }

  ;[ 'params', 'definition', 'scope', 'props' ].forEach(prop => {
    if (!obj[ prop ]) return

    for (const item in obj[ prop ]) {
      parseObject({
        banner: `${ banner }/"${ prop }"/"${ item }"`,
        api: api[ itemName ][ prop ],
        itemName: item,
        masterType: 'props',
        verifySerializable
      })
    }
  })
}
Example (we want Number AND null to be valid):

modelValue: {
  default: null,
  validator: v => typeof v === 'number' || v === null
}