docs: function propertyType(property) {
    var type;
    if (property === null || property.type === null) {
      return 'null';
    } else if (property.$ref != null) {
      // Type is a reference
      return simpleRef(property.$ref);
    } else if (property['x-type']) {
      // Type is read from the x-type vendor extension
      type = (property['x-type'] || '').toString().replace('List<', 'Array<');
      return type.length == 0 ? 'null' : type;
    } else if (property['x-nullable']) {
      return 'null | ' + propertyType(
        Object.assign(property, {'x-nullable': undefined}));
    } else if (!property.type && (property.anyOf || property.oneOf)) {
      let variants = (property.anyOf || property.oneOf).map(propertyType);
      return {
        allTypes: mergeTypes(...variants),
        toString: () => variants.join(' | ')
      };
    } else if (Array.isArray(property.type)) {
      let variants = property.type.map(type => propertyType(Object.assign({}, property, {type})));
      return {
        allTypes: mergeTypes(...variants),
        toString: () => variants.join(' | ')
      };
    }
    switch (property.type) {
      case 'null':
        return 'null';
      case 'string':
        if (property.enum && property.enum.length > 0) {
          return '\'' + property.enum.join('\' | \'') + '\'';
        }
        else if (property.const) {
          return '\'' + property.const + '\'';
        }
        return 'string';
      case 'array':
        if (Array.isArray(property.items)) { // support for tuples
          if (!property.maxItems) return 'Array<any>'; // there is unable to define unlimited tuple in TypeScript
          let minItems = property.minItems || 0,
              maxItems = property.maxItems,
              types = property.items.map(propertyType);
          types.push(property.additionalItems ? propertyType(property.additionalItems) : 'any');
          let variants = [];
          for (let i = minItems; i <= maxItems; i++) variants.push(types.slice(0, i));
          return {
            allTypes: mergeTypes(...types.slice(0, maxItems)),
            toString: () => variants.map(types => `[${types.join(', ')}]`).join(' | ')
          };
        }
        else {
          let itemType = propertyType(property.items);
          return {
            allTypes: mergeTypes(itemType),
            toString: () => 'Array<' + itemType + '>'
          };
        }
      case 'integer':
      case 'number':
        if (property.enum && property.enum.length > 0) return property.enum.join(' | ');
        if (property.const) return property.const;
        return 'number';
      case 'boolean':
        return 'boolean';
      case 'file':
        return 'Blob';
      case 'object':
        var def = '{';
        let memberCount = 0;
        var allTypes = [];
        if (property.properties) {
          for (var name in property.properties) {
            var prop = property.properties[name];
            if (memberCount++) def += ', ';
            type = propertyType(prop);
            allTypes.push(type);
  	        let required = property.required && property.required.indexOf(name) >= 0;
  	        def += name + (required ? ': ' : '?: ') + type;
          }
        }
        if (property.additionalProperties) {
          if (memberCount++) def += ', ';
          type = typeof property.additionalProperties === 'object' ?
              propertyType(property.additionalProperties) : 'any';
  	      allTypes.push(type);
          def += '[key: string]: ' + type;
        }
        def += '}';
  
        return {
          allTypes: mergeTypes(...allTypes),
          toString: () => def,
        };
      default:
        return 'any';
    }
  }