class-generator
is a macOS command line tool which generates classes based on JSON files used to describe the classes and their properties. It was created to help reduce the manual process of creating mapper classes used in REST service calls in iOS and macOS apps. Since it is template based, it can be used to generate classes for any language.
It takes as input, schema files written in JSON
. It then uses a specified template, written in Stencil Template Language
, to generate a class file for each of the classes defined in the input schemas. It can also load specified plugin files, written in JavaScript
, to do additional work.
The JSON schema files should consist of an array of class objects at its root. The class objects and its subtypes are defined below:
name
[String] The class' name.type
[String] The type of data type, (class
orenum
). Defaults toclass
.properties
[array of Property objects] The class' properties.
name
[String] The property's name.dataType
[String] The property's data type.description
[String, optional] A description of the property.
name
[String] The enum's name.type
[String] The data type's type, (class
orenum
). Defaults toclass
.dataType
[String] The enum values' data type.values
[array of Value objects] The enum's possible values.
name
[String] The enum's display name.value
[String] The enum's JSON value.
Aside from the raw data type, the type definition in a Property
object can also specify whether the property is a collection and whether it's optional or required.
To specify a collection, surround the type name with square brackets. For example, an array of Strings would be specified as [String]
.
To specify an optional, add a question mark at the end of the type definition. For example, an optional String would be specified as String?
.
{
"version": "2.1",
"dataTypes": [
{
"name": "UsersResponse",
"type": "class",
"properties": [
{ "name": "users", "dataType": "[User]" }
]
},
{
"name": "User",
"properties": [
{ "name": "firstName", "dataType": "String" },
{ "name": "lastName", "dataType": "String" },
{ "name": "age", "dataType": "Int?" },
{ "name": "address", "dataType": "Address?" },
{ "name": "role", "dataType": "Role" }
]
},
{
"name": "Address",
"properties": [
{ "name": "streetAddress1", "dataType": "String" },
{ "name": "streetAddress2", "dataType": "String?" },
{ "name": "city", "dataType": "String" },
{ "name": "state", "dataType": "String" },
{ "name": "zipcode", "dataType": "Int" }
]
},
{
"name": "Role",
"type": "enum",
"dataType": "String",
"values": [
{ "name": "administrator", "value": "10001" },
{ "name": "moderator", "value": "10002" },
{ "name": "user", "value": "10003" }
]
}
]
}
Template parsing is done using the Stencil
library. Which means templates must be written in the Stencil Template Language
.
When writing a template, the template's context represents the current class being generated. The properties available to the template are defined below:
The default properties in the Class Object (see above)
The default properties in the Property Object (see above), plus the following:
isCollection
[Bool] Whether or not the property represents a collection (for example an array).isOptional
[Bool] Whether or not the property represents an optional value.rawDataType
[String] The property's raw data type (the same value astype
, except without the collection and optionality modifiers).
//
// {{ name }}.swift
// Autogenerated by class-generator
//
// swiftlint:disable superfluous_disable_command
// swiftlint:disable type_name
import Foundation
import ObjectMapper
{% if type == "class" %}internal class {{ name }}: ImmutableMappable {
// MARK: - Public Properties
{% for property in properties %}
let {{ property|swiftNameAndTypeDeclaration }}{% endfor %}
// MARK: - Private Enums
fileprivate enum Keys: String { {% for property in properties %}
case {{ property.name }}{% endfor %}
}
// MARK: - Initialization
init({% for property in properties %}{{ property|swiftNameAndTypeDeclaration }}{% if not forloop.last %},
{% endif %}{% endfor %}) { {% for property in properties %}
self.{{ property.name }} = {{ property.name }}{% endfor %}
}
required init(map: Map) throws { {% for property in properties %}{% if property.rawDataType == "Date" %}
{{ property.name }} = try{% if property.isOptional %}?{% endif %} map.value(Keys.{{ property.name }}.rawValue, using: ISO8601DateTransform()){% else %}
{{ property.name }} = try{% if property.isOptional %}?{% endif %} map.value(Keys.{{ property.name }}.rawValue){% endif %}{% endfor %}
}
// MARK: - Mappable
func mapping(map: Map) { {% for property in properties %}{% if property.rawDataType == "Date" %}
{{ property.name }} >>> (map[Keys.{{ property.name }}.rawValue], ISO8601DateTransform()){% else %}
{{ property.name }} >>> map[Keys.{{ property.name }}.rawValue]{% endif %}{% endfor %}
}
}{% elif type == "enum" %}internal enum {{ name }}: {{ dataType }}: { {% for value in values %}
case {{ value.name }} = "{{ value.value }}"{% endfor %}
}{% endif %}
class-generator
supports plugins written in JavaScript
. Plugins can currently do the following:
- Log a message
- Register pre-defined type.
- Register custom template filters.
- Register custom template tags.
A plugin can log messages to the class-generator
log, which can help while debugging the plugin. Logging is done via the exposed classGenLog
function.
classGenLog("Log a message that should appear in the class-generator log")
Pre-defined types are types which will not be represented in the input schema while generating classes. This can be primitive types or other custom types which will not be autogenerated, but will be referenced in the input schemas. class-generator
needs to know about these, in order to avoid throwing an undefined class exception while validating the input schemas.
The pre-defined types can be set in a plugin by calling the exposed registerPreDefinedTypes
function.
registerPreDefinedTypes(["Bool", "Date", "Decimal", "Double", "Float", "Int", "Long", "String"])
Custom Stencil
template filters can be registered using the exposed registerFilter
function. For example, the swiftNameAndTypeDeclaration
filter is used in the example template above. The plugin is shown below:
function swiftNameAndTypeDeclaration(property) {
var declaration = property.name + ": "
if (property.isCollection) {
declaration += "["
}
declaration += property.rawType
if (property.isCollection) {
declaration += "]"
}
if (property.isOptional) {
declaration += "?"
}
return declaration
}
registerFilter("swiftNameAndTypeDeclaration", "swiftNameAndTypeDeclaration", "string")
Custom Stencil
tags can be registered using the exposed registerTag
function.
function myTag(context) {
return Date.now()
}
registerTag("myTag", "myTag")
> class-generator help
Usage: class-generator <command> [options]
class-generator - Generate classes from JSON schemas
Commands:
generate Generates an output file for each class definition found in the specified schemas, using the specified template.
help Prints this help information
version Prints the current version of this app
This command will generate a class file for each class represented in the JSON schemas that are loaded from the input directory.
> class-generator generate --help
Usage: class-generator generate <schemasDirectoryPath> <templateFilePath> [options]
Options:
--alphabetize-properties The generated properties will be listed alphabetical order.
--output-directory-path <value> The output directory where all generated files will be saved to. A temporary directory will be used if none is provided.
--plugins-directory-path <value> A directory containing plugins which will be loaded at runtime.
-h, --help Show help information for this command
> class-generator generate \
~/Desktop/class-gen/schemas \
~/Desktop/class-gen/templates/object-mapper.swift \
--plugins-directory-path ~/Desktop/class-gen/plugins \
--alphabetize-properties
- Download the latest release archive from the releases page.
- Unzip the file.
- Copy the
class-generator
binary to somewhere in yourPATH
(for example/usr/local/bin
). - Run the commands as shown in the examples above.
class-generator uses the Swift Package Manager. After cloning the repository, change directories into the repository's directory and run the swift build
command. You should see something like the following:
> swift build
Fetching https://github.com/kylef/PathKit.git
Fetching https://github.com/kylef/Stencil.git
Fetching https://github.com/jakeheis/SwiftCLI.git
Fetching https://github.com/Hearst-DD/ObjectMapper.git
Fetching https://github.com/IBM-Swift/HeliumLogger.git
Fetching https://github.com/kylef/Spectre.git
Fetching https://github.com/IBM-Swift/LoggerAPI.git
...
Compile Swift Module 'Spectre' (8 sources)
Compile Swift Module 'LoggerAPI' (1 sources)
Compile Swift Module 'SwiftCLI' (25 sources)
Compile Swift Module 'ObjectMapper' (23 sources)
Compile Swift Module 'HeliumLogger' (2 sources)
Compile Swift Module 'class_generator' (6 sources)
Linking ./.build/x86_64-apple-macosx10.10/debug/class-generator
You can then find the binary at .build/debug/class-generator
.
class-generator
uses the following libraries: