[
  {
    "path": ".gitignore",
    "content": "# Xcode\n#\n# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore\n\n## Build generated\nbuild/\nDerivedData/\n\n## Various settings\n*.pbxuser\n!default.pbxuser\n*.mode1v3\n!default.mode1v3\n*.mode2v3\n!default.mode2v3\n*.perspectivev3\n!default.perspectivev3\nxcuserdata/\n\n## Other\n*.moved-aside\n*.xccheckout\n*.xcscmblueprint\n.DS_Store\n\n## Obj-C/Swift specific\n*.hmap\n*.ipa\n*.dSYM.zip\n*.dSYM\n\n## Playgrounds\ntimeline.xctimeline\nplayground.xcworkspace\n\n# Swift Package Manager\n#\n# Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.\n# Packages/\n# Package.pins\n.build/\n\n# CocoaPods\n#\n# We recommend against adding the Pods directory to your .gitignore. However\n# you should judge for yourself, the pros and cons are mentioned at:\n# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control\n#\n# Pods/\n\n# Carthage\n#\n# Add this line if you want to avoid checking in source code from Carthage dependencies.\n# Carthage/Checkouts\n\nCarthage/Build\n\n# fastlane\n#\n# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the\n# screenshots whenever they are needed.\n# For more information about the recommended setup visit:\n# https://docs.fastlane.tools/best-practices/source-control/#source-control\n\nfastlane/report.xml\nfastlane/Preview.html\nfastlane/screenshots\nfastlane/test_output\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2018 Cultured Code GmbH & Co. KG\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# Things JSON Coder\n\nThis repo contains a Swift file that allows the creation of the JSON required to be passed to the `json` command of [Things’ URL scheme](https://culturedcode.com/things/support/articles/2803573/).\n\n## Installation\n\nTo install, download the [`ThingsJSON.swift`](https://github.com/culturedcode/ThingsJSONCoder/blob/master/ThingsJSON.swift) file and add it as a source file to your own project. Alternatively, this repo can be cloned as either a real or fake submodule inside your project.\n\n## Requirements\n\nThis code is written with Swift 4.\n\n## Getting Started\n\n#### The Things JSON Container\n\nThe top level object that will be encoded into the JSON array is the `TJSContainer`. This object contains an array of the items to be included in the JSON.\n\n#### Model Classes\n\nThe following Things model classes can be encoded into JSON:\n\n* `Todo`\n* `Project`\n* `Heading`\n* `ChecklistItem`\n\n#### Container Enums\n\nThere are two wrapper enums used to package objects into arrays. Associated values are used to hold the above model objects inside. These enums exist to allow more than one type of object inside an array while retaining type safety. They also handle the encoding and decoding of heterogeneous types within an array to and from JSON.\n\n* `TJSContainer.Item` – This enum has cases for todo and project objects. Only todo and project items can exist at the top level array in the JSON.\n\n* `TJSProject.Item` – This enum has cases for todo and heading objects. Only todo and heading objects can be items inside a project.\n\n#### Dates\nDates should be formatted according to ISO8601. Setting the JSON encoder’s `dateEncodingStrategy` to `ThingsJSONDateEncodingStrategy()` is the easiest way to do this (see example below).\n\n## Example\n\nCreate two todos and a project, encode them into JSON and send to Things’ add command.\n\n```Swift\nlet todo1 = TJSTodo(title: \"Pick up dry cleaning\", when: \"today\")\nlet todo2 = TJSTodo(title: \"Pack for vacation\",\n                    checklistItems: [TJSChecklistItem(title: \"Camera\"),\n                                     TJSChecklistItem(title: \"Passport\")])\n\nlet project = TJSProject(title: \"Go Shopping\",\n                         items: [.heading(TJSHeading(title: \"Dairy\")),\n                                 .todo(TJSTodo(title: \"Milk\"))])\n\nlet container = TJSContainer(items: [.todo(todo1),\n                                     .todo(todo2),\n                                     .project(project)])\ndo {\n    let encoder = JSONEncoder()\n    encoder.dateEncodingStrategy = ThingsJSONDateEncodingStrategy()\n    let data = try encoder.encode(container)\n    let json = String(data: data, encoding: .utf8)!\n    var components = URLComponents(string: \"things:///add-json\")!\n    let queryItem = URLQueryItem(name: \"data\", value: json)\n    components.queryItems = [queryItem]\n    let url = components.url!\n    UIApplication.shared.open(url, options: [:], completionHandler: nil)\n}\ncatch {\n    // Handle error\n}\n```\n\n## License\n\nThis code is released under the MIT license. See [LICENSE](https://github.com/culturedcode/ThingsJSONCoder/blob/master/LICENSE) for details.\n"
  },
  {
    "path": "ThingsJSON.swift",
    "content": "//\n//  ThingsJSON.swift\n//\n//  Copyright © 2018 Cultured Code GmbH & Co. KG\n//\n//  Permission is hereby granted, free of charge, to any person obtaining a copy\n//  of this software and associated documentation files (the \"Software\"), to deal\n//  in the Software without restriction, including without limitation the rights\n//  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n//  copies of the Software, and to permit persons to whom the Software is\n//  furnished to do so, subject to the following conditions:\n//\n//  The above copyright notice and this permission notice shall be included in all\n//  copies or substantial portions of the Software.\n//\n//  THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n//  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n//  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n//  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n//  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n//  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n//  SOFTWARE.\n//\n\nimport Foundation\n\n// MARK: Container\n\n/// The container holding the array of items to be encoded to JSON.\npublic class TJSContainer : Codable {\n\n    /// The array of items that will be encoded or decoded from the JSON.\n    public var items = [Item]()\n\n    /// Create and return a new ThingsJSON object configured with the provided items.\n    public init(items: [Item]) {\n        self.items = items\n    }\n\n    /// Creates a new instance by decoding from the given decoder.\n    public required init(from decoder: Decoder) throws {\n        let container = try decoder.singleValueContainer()\n        self.items = try container.decode([Item].self)\n    }\n\n    /// Encodes this value into the given encoder.\n    public func encode(to encoder: Encoder) throws {\n        try self.items.encode(to: encoder)\n    }\n\n    /// An item that can exist inside the top level JSON array.\n    ///\n    /// This is an enum that wraps a TJSTodo or TJSProject object and handles its encoding\n    /// and decoding to JSON. This is required because there is no way of specifiying a\n    /// strongly typed array that contains more than one type.\n    public enum Item : Codable {\n        case todo(TJSTodo)\n        case project(TJSProject)\n\n        /// Creates a new instance by decoding from the given decoder.\n        public init(from decoder: Decoder) throws {\n            let container = try decoder.singleValueContainer()\n\n            do {\n                // Try to decode a to-do\n                let todo = try container.decode(TJSTodo.self)\n                self = .todo(todo)\n            }\n            catch TJSError.invalidType(expectedType: _, errorContext: _) {\n                // If it's the wrong type, try a project\n                let project = try container.decode(TJSProject.self)\n                self = .project(project)\n            }\n        }\n\n        /// Encodes this value into the given encoder.\n        public func encode(to encoder: Encoder) throws {\n            switch self {\n            case .todo(let todo):\n                try todo.encode(to: encoder)\n            case .project(let project):\n                try project.encode(to: encoder)\n            }\n        }\n    }\n}\n\n\n// MARK: - Model Items\n\n/// The superclass of all the Things JSON model items.\n///\n/// Do not instantiate this class itself. Instead use one of the subclasses.\npublic class TJSModelItem {\n    fileprivate var type: String = \"\"\n\n    /// The operation to perform on the object.\n    public var operation: Operation\n\n    /// The ID of the item to update.\n    public var id: String?\n\n    private enum CodingKeys: String, CodingKey {\n        case type\n        case operation\n        case id\n        case attributes\n    }\n\n    public enum Operation: String, Codable {\n        /// Create a new item.\n        case create = \"create\"\n        /// Update an existing item.\n        ///\n        /// Requires id to be set.\n        case update = \"update\"\n    }\n\n    public init(operation: Operation, id: String? = nil) {\n        self.operation = operation\n        self.id = id\n    }\n\n    fileprivate func attributes<T>(_ type: T.Type, from decoder: Decoder) throws -> KeyedDecodingContainer<T> {\n        let container = try decoder.container(keyedBy: CodingKeys.self)\n        let decodedType = try container.decode(String.self, forKey: .type)\n        self.operation = try container.decodeIfPresent(Operation.self, forKey: .operation) ?? .create\n        self.id = try container.decodeIfPresent(String.self, forKey: .id)\n        guard decodedType == self.type else {\n            let description = String(format: \"Expected to decode a %@ but found a %@ instead.\", self.type, decodedType)\n            let errorContext = DecodingError.Context(codingPath: [CodingKeys.type], debugDescription: description)\n            let expectedType = Swift.type(of: self)\n            throw TJSError.invalidType(expectedType: expectedType, errorContext: errorContext)\n        }\n        return try container.nestedContainer(keyedBy: T.self, forKey: .attributes)\n    }\n\n    fileprivate func attributes<T>(_ type: T.Type, for encoder: Encoder) throws -> KeyedEncodingContainer<T> {\n        var container = encoder.container(keyedBy: CodingKeys.self)\n        try container.encode(self.type, forKey: .type)\n        try container.encode(self.operation, forKey: .operation)\n        try container.encodeIfPresent(self.id, forKey: .id)\n        return container.nestedContainer(keyedBy: T.self, forKey: .attributes)\n    }\n}\n\n\n// MARK: -\n\n/// Represents a to-do in Things.\npublic class TJSTodo : TJSModelItem, Codable {\n    public var title: String?\n    public var notes: String?\n    public var prependNotes: String?\n    public var appendNotes: String?\n    public var when: String?\n    public var deadline: String?\n    public var tagIDs: [String]?\n    public var tags: [String]?\n    public var addTags: [String]?\n    public var checklistItems: [TJSChecklistItem]?\n    public var prependChecklistItems: [TJSChecklistItem]?\n    public var appendChecklistItems: [TJSChecklistItem]?\n    public var listID: String?\n    public var list: String?\n    public var headingID: String?\n    public var heading: String?\n    public var completed: Bool?\n    public var canceled: Bool?\n    public var creationDate: Date?\n    public var completionDate: Date?\n\n    private enum CodingKeys: String, CodingKey {\n        case title\n        case notes\n        case prependNotes = \"prepend-notes\"\n        case appendNotes = \"append-notes\"\n        case when\n        case deadline\n        case tagIDs = \"tag-ids\"\n        case tags\n        case addTags = \"add-tags\"\n        case checklistItems = \"checklist-items\"\n        case prependChecklistItems = \"prepend-checklist-items\"\n        case appendChecklistItems = \"append-checklist-items\"\n        case listID = \"list-id\"\n        case list\n        case headingID = \"heading-id\"\n        case heading\n        case completed\n        case canceled\n        case creationDate = \"creation-date\"\n        case completionDate = \"completion-date\"\n    }\n\n    /// Create and return a new todo configured with the provided values.\n    public init(operation: Operation = .create,\n         id: String? = nil,\n         title: String? = nil,\n         notes: String? = nil,\n         prependNotes: String? = nil,\n         appendNotes: String? = nil,\n         when: String? = nil,\n         deadline: String? = nil,\n         tagIDs: [String]? = nil,\n         tags: [String]? = nil,\n         addTags: [String]? = nil,\n         checklistItems: [TJSChecklistItem]? = nil,\n         prependChecklistItems: [TJSChecklistItem]? = nil,\n         appendChecklistItems: [TJSChecklistItem]? = nil,\n         listID: String? = nil,\n         list: String? = nil,\n         headingID: String? = nil,\n         heading: String? = nil,\n         completed: Bool? = nil,\n         canceled: Bool? = nil,\n         creationDate: Date? = nil,\n         completionDate: Date? = nil) {\n\n        super.init(operation: operation, id: id)\n        self.type = \"to-do\"\n\n        self.title = title\n        self.notes = notes\n        self.prependNotes = prependNotes\n        self.appendNotes = appendNotes\n        self.when = when\n        self.deadline = deadline\n        self.tagIDs = tagIDs\n        self.tags = tags\n        self.addTags = addTags\n        self.checklistItems = checklistItems\n        self.prependChecklistItems = prependChecklistItems\n        self.appendChecklistItems = appendChecklistItems\n        self.listID = listID\n        self.list = list\n        self.heading = heading\n        self.headingID = headingID\n        self.completed = completed\n        self.canceled = canceled\n        self.creationDate = creationDate\n        self.completionDate = completionDate\n    }\n\n    /// Create and return a new todo configured with same values as the provided todo.\n    public convenience init(_ todo: TJSTodo) {\n        self.init(id: todo.id,\n                  title: todo.title,\n                  notes: todo.notes,\n                  prependNotes: todo.prependNotes,\n                  appendNotes: todo.appendNotes,\n                  when: todo.when,\n                  deadline: todo.deadline,\n                  tagIDs: todo.tagIDs,\n                  tags: todo.tags,\n                  addTags: todo.addTags,\n                  checklistItems: todo.checklistItems,\n                  prependChecklistItems: todo.prependChecklistItems,\n                  appendChecklistItems: todo.appendChecklistItems,\n                  listID: todo.listID,\n                  list: todo.list,\n                  headingID: todo.headingID,\n                  heading: todo.heading,\n                  completed: todo.completed,\n                  canceled: todo.canceled,\n                  creationDate: todo.creationDate,\n                  completionDate: todo.completionDate)\n    }\n\n    /// Creates a new instance by decoding from the given decoder.\n    public required convenience init(from decoder: Decoder) throws {\n        self.init()\n        let attributes = try self.attributes(CodingKeys.self, from: decoder)\n        do {\n            title = try attributes.decodeIfPresent(String.self, forKey: .title)\n            notes = try attributes.decodeIfPresent(String.self, forKey: .notes)\n            prependNotes = try attributes.decodeIfPresent(String.self, forKey: .prependNotes)\n            appendNotes = try attributes.decodeIfPresent(String.self, forKey: .appendNotes)\n            when = try attributes.decodeIfPresent(String.self, forKey: .when)\n            deadline = try attributes.decodeIfPresent(String.self, forKey: .deadline)\n            tagIDs = try attributes.decodeIfPresent([String].self, forKey: .tagIDs)\n            tags = try attributes.decodeIfPresent([String].self, forKey: .tags)\n            addTags = try attributes.decodeIfPresent([String].self, forKey: .addTags)\n            checklistItems = try attributes.decodeIfPresent([TJSChecklistItem].self, forKey: .checklistItems)\n            prependChecklistItems = try attributes.decodeIfPresent([TJSChecklistItem].self, forKey: .prependChecklistItems)\n            appendChecklistItems = try attributes.decodeIfPresent([TJSChecklistItem].self, forKey: .appendChecklistItems)\n            listID = try attributes.decodeIfPresent(String.self, forKey: .listID)\n            list = try attributes.decodeIfPresent(String.self, forKey: .list)\n            headingID = try attributes.decodeIfPresent(String.self, forKey: .headingID)\n            heading = try attributes.decodeIfPresent(String.self, forKey: .heading)\n            completed = try attributes.decodeIfPresent(Bool.self, forKey: .completed)\n            canceled = try attributes.decodeIfPresent(Bool.self, forKey: .canceled)\n            creationDate = try attributes.decodeIfPresent(Date.self, forKey: .creationDate)\n            completionDate = try attributes.decodeIfPresent(Date.self, forKey: .completionDate)\n        }\n        catch TJSError.invalidType(let expectedType, let errorContext) {\n            throw DecodingError.typeMismatch(expectedType, errorContext)\n        }\n    }\n\n    /// Encodes this value into the given encoder.\n    public func encode(to encoder: Encoder) throws {\n        var attributes = try self.attributes(CodingKeys.self, for: encoder)\n        try attributes.encodeIfPresent(title, forKey: .title)\n        try attributes.encodeIfPresent(notes, forKey: .notes)\n        try attributes.encodeIfPresent(prependNotes, forKey: .prependNotes)\n        try attributes.encodeIfPresent(appendNotes, forKey: .appendNotes)\n        try attributes.encodeIfPresent(when, forKey: .when)\n        try attributes.encodeIfPresent(deadline, forKey: .deadline)\n        try attributes.encodeIfPresent(tagIDs, forKey: .tagIDs)\n        try attributes.encodeIfPresent(tags, forKey: .tags)\n        try attributes.encodeIfPresent(addTags, forKey: .addTags)\n        try attributes.encodeIfPresent(checklistItems, forKey: .checklistItems)\n        try attributes.encodeIfPresent(prependChecklistItems, forKey: .prependChecklistItems)\n        try attributes.encodeIfPresent(appendChecklistItems, forKey: .appendChecklistItems)\n        try attributes.encodeIfPresent(listID, forKey: .listID)\n        try attributes.encodeIfPresent(list, forKey: .list)\n        try attributes.encodeIfPresent(headingID, forKey: .headingID)\n        try attributes.encodeIfPresent(heading, forKey: .heading)\n        try attributes.encodeIfPresent(completed, forKey: .completed)\n        try attributes.encodeIfPresent(canceled, forKey: .canceled)\n        try attributes.encodeIfPresent(creationDate, forKey: .creationDate)\n        try attributes.encodeIfPresent(completionDate, forKey: .completionDate)\n    }\n}\n\n\n// MARK: -\n\n/// Represents a project in Things.\npublic class TJSProject : TJSModelItem, Codable {\n    var title: String?\n    var notes: String?\n    var prependNotes: String?\n    var appendNotes: String?\n    var when: String?\n    var deadline: String?\n    var tagIDs: [String]?\n    var tags: [String]?\n    var addTags: [String]?\n    var areaID: String?\n    var area: String?\n    var items: [Item]?\n    var completed: Bool?\n    var canceled: Bool?\n    var creationDate: Date?\n    var completionDate: Date?\n\n    private enum CodingKeys: String, CodingKey {\n        case title\n        case notes\n        case prependNotes = \"prepend-notes\"\n        case appendNotes = \"append-notes\"\n        case when\n        case deadline\n        case tagIDs = \"tag-ids\"\n        case tags\n        case addTags = \"add-tags\"\n        case areaID = \"area-id\"\n        case area\n        case items\n        case completed\n        case canceled\n        case creationDate = \"creation-date\"\n        case completionDate = \"completion-date\"\n    }\n\n    /// Create and return a new project configured with the provided values.\n    init(operation: Operation = .create,\n         id: String? = nil,\n         title: String? = nil,\n         notes: String? = nil,\n         prependNotes: String? = nil,\n         appendNotes: String? = nil,\n         when: String? = nil,\n         deadline: String? = nil,\n         tagIDs: [String]? = nil,\n         tags: [String]? = nil,\n         addTags: [String]? = nil,\n         areaID: String? = nil,\n         area: String? = nil,\n         items: [Item]? = nil,\n         completed: Bool? = nil,\n         canceled: Bool? = nil,\n         creationDate: Date? = nil,\n         completionDate: Date? = nil) {\n\n        super.init(operation: operation, id: id)\n        self.type = \"project\"\n\n        self.title = title\n        self.notes = notes\n        self.prependNotes = prependNotes\n        self.appendNotes = appendNotes\n        self.when = when\n        self.deadline = deadline\n        self.tagIDs = tagIDs\n        self.tags = tags\n        self.addTags = addTags\n        self.areaID = areaID\n        self.area = area\n        self.items = items\n        self.completed = completed\n        self.canceled = canceled\n        self.creationDate = creationDate\n        self.completionDate = completionDate\n    }\n\n    /// Create and return a new project configured with same values as the provided project.\n    convenience init(_ project: TJSProject) {\n        self.init(id: project.id,\n                  title: project.title,\n                  notes: project.notes,\n                  prependNotes: project.prependNotes,\n                  appendNotes: project.appendNotes,\n                  when: project.when,\n                  deadline: project.deadline,\n                  tagIDs: project.tagIDs,\n                  tags: project.tags,\n                  addTags: project.addTags,\n                  areaID: project.areaID,\n                  area: project.area,\n                  items: project.items,\n                  completed: project.completed,\n                  canceled: project.canceled,\n                  creationDate: project.creationDate,\n                  completionDate: project.completionDate)\n    }\n\n    /// Creates a new instance by decoding from the given decoder.\n    public required convenience init(from decoder: Decoder) throws {\n        self.init()\n        let attributes = try self.attributes(CodingKeys.self, from: decoder)\n        do {\n            title = try attributes.decodeIfPresent(String.self, forKey: .title)\n            notes = try attributes.decodeIfPresent(String.self, forKey: .notes)\n            prependNotes = try attributes.decodeIfPresent(String.self, forKey: .prependNotes)\n            appendNotes = try attributes.decodeIfPresent(String.self, forKey: .appendNotes)\n            when = try attributes.decodeIfPresent(String.self, forKey: .when)\n            deadline = try attributes.decodeIfPresent(String.self, forKey: .deadline)\n            tagIDs = try attributes.decodeIfPresent([String].self, forKey: .tagIDs)\n            tags = try attributes.decodeIfPresent([String].self, forKey: .tags)\n            addTags = try attributes.decodeIfPresent([String].self, forKey: .addTags)\n            areaID = try attributes.decodeIfPresent(String.self, forKey: .areaID)\n            area = try attributes.decodeIfPresent(String.self, forKey: .area)\n            completed = try attributes.decodeIfPresent(Bool.self, forKey: .completed)\n            canceled = try attributes.decodeIfPresent(Bool.self, forKey: .canceled)\n            items = try attributes.decodeIfPresent([Item].self, forKey: .items)\n            creationDate = try attributes.decodeIfPresent(Date.self, forKey: .creationDate)\n            completionDate = try attributes.decodeIfPresent(Date.self, forKey: .completionDate)\n        }\n        catch TJSError.invalidType(let expectedType, let errorContext) {\n            throw DecodingError.typeMismatch(expectedType, errorContext)\n        }\n    }\n\n    /// Encodes this value into the given encoder.\n    public func encode(to encoder: Encoder) throws {\n        var attributes = try self.attributes(CodingKeys.self, for: encoder)\n        try attributes.encodeIfPresent(title, forKey: .title)\n        try attributes.encodeIfPresent(notes, forKey: .notes)\n        try attributes.encodeIfPresent(prependNotes, forKey: .prependNotes)\n        try attributes.encodeIfPresent(appendNotes, forKey: .appendNotes)\n        try attributes.encodeIfPresent(when, forKey: .when)\n        try attributes.encodeIfPresent(deadline, forKey: .deadline)\n        try attributes.encodeIfPresent(tagIDs, forKey: .tagIDs)\n        try attributes.encodeIfPresent(tags, forKey: .tags)\n        try attributes.encodeIfPresent(addTags, forKey: .addTags)\n        try attributes.encodeIfPresent(areaID, forKey: .areaID)\n        try attributes.encodeIfPresent(area, forKey: .area)\n        try attributes.encodeIfPresent(items, forKey: .items)\n        try attributes.encodeIfPresent(completed, forKey: .completed)\n        try attributes.encodeIfPresent(canceled, forKey: .canceled)\n        try attributes.encodeIfPresent(creationDate, forKey: .creationDate)\n        try attributes.encodeIfPresent(completionDate, forKey: .completionDate)\n    }\n\n    /// A child item of a project.\n    ///\n    /// This is an enum that wraps a TJSTodo or TJSHeading object and handles its encoding\n    /// and decoding to JSON. This is required because there is no way of specifiying a\n    /// strongly typed array that contains more than one type.\n    public enum Item : Codable {\n        case todo(TJSTodo)\n        case heading(TJSHeading)\n\n        /// Creates a new instance by decoding from the given decoder.\n        public init(from decoder: Decoder) throws {\n            let container = try decoder.singleValueContainer()\n\n            do {\n                // Try to decode a to-do\n                let todo = try container.decode(TJSTodo.self)\n                self = .todo(todo)\n            }\n            catch TJSError.invalidType(expectedType: _, errorContext: _) {\n                // If it's the wrong type, try a heading\n                let heading = try container.decode(TJSHeading.self)\n                self = .heading(heading)\n            }\n        }\n\n        /// Encodes this value into the given encoder.\n        public func encode(to encoder: Encoder) throws {\n            switch self {\n            case .todo(let todo):\n                try todo.encode(to: encoder)\n            case .heading(let heading):\n                try heading.encode(to: encoder)\n            }\n        }\n    }\n}\n\n\n// MARK: -\n\n/// Represents a heading in Things.\npublic class TJSHeading : TJSModelItem, Codable {\n    public var title: String?\n    public var archived: Bool?\n    public var creationDate: Date?\n    public var completionDate: Date?\n\n    private enum CodingKeys: String, CodingKey {\n        case title\n        case archived\n        case creationDate = \"creation-date\"\n        case completionDate = \"completion-date\"\n    }\n\n    /// Create and return a new heading configured with the provided values.\n    public init(operation: Operation = .create,\n         title: String? = nil,\n         archived: Bool? = nil,\n         creationDate: Date? = nil,\n         completionDate: Date? = nil) {\n\n        super.init(operation: operation)\n        self.type = \"heading\"\n\n        self.title = title\n        self.archived = archived\n        self.creationDate = creationDate\n        self.completionDate = completionDate\n    }\n\n    /// Create and return a new heading configured with same values as the provided heading.\n    public convenience init(_ heading: TJSHeading) {\n        self.init(title: heading.title,\n                  archived: heading.archived,\n                  creationDate: heading.creationDate,\n                  completionDate: heading.completionDate)\n    }\n\n    /// Creates a new instance by decoding from the given decoder.\n    public required convenience init(from decoder: Decoder) throws {\n        self.init()\n        let attributes = try self.attributes(CodingKeys.self, from: decoder)\n        title = try attributes.decodeIfPresent(String.self, forKey: .title)\n        archived = try attributes.decodeIfPresent(Bool.self, forKey: .archived)\n        creationDate = try attributes.decodeIfPresent(Date.self, forKey: .creationDate)\n        completionDate = try attributes.decodeIfPresent(Date.self, forKey: .completionDate)\n    }\n\n    /// Encodes this value into the given encoder.\n    public func encode(to encoder: Encoder) throws {\n        var attributes = try self.attributes(CodingKeys.self, for: encoder)\n        try attributes.encodeIfPresent(title, forKey: .title)\n        try attributes.encodeIfPresent(archived, forKey: .archived)\n        try attributes.encodeIfPresent(creationDate, forKey: .creationDate)\n        try attributes.encodeIfPresent(completionDate, forKey: .completionDate)\n    }\n}\n\n\n// MARK: -\n\n/// Represents a checklist item in Things.\npublic class TJSChecklistItem : TJSModelItem, Codable {\n    public var title: String?\n    public var completed: Bool?\n    public var canceled: Bool?\n    public var creationDate: Date?\n    public var completionDate: Date?\n\n    private enum CodingKeys: String, CodingKey {\n        case title\n        case completed\n        case canceled\n        case creationDate = \"creation-date\"\n        case completionDate = \"completion-date\"\n    }\n\n    /// Create and return a new checklist item configured with the provided values.\n    public init(operation: Operation = .create,\n         title: String? = nil,\n         completed: Bool? = nil,\n         canceled: Bool? = nil,\n         creationDate: Date? = nil,\n         completionDate: Date? = nil) {\n\n        super.init(operation: operation)\n        self.type = \"checklist-item\"\n\n        self.title = title\n        self.completed = completed\n        self.canceled = canceled\n        self.creationDate = creationDate\n        self.completionDate = completionDate\n    }\n\n    /// Create and return a new checklist item configured with same values as the provided checklist item.\n    public convenience init (_ checklistItem: TJSChecklistItem) {\n        self.init(title: checklistItem.title,\n                  completed: checklistItem.completed,\n                  canceled: checklistItem.canceled,\n                  creationDate: checklistItem.creationDate,\n                  completionDate: checklistItem.completionDate)\n    }\n\n    /// Creates a new instance by decoding from the given decoder.\n    public required convenience init(from decoder: Decoder) throws {\n        self.init()\n        let attributes = try self.attributes(CodingKeys.self, from: decoder)\n        title = try attributes.decodeIfPresent(String.self, forKey: .title)\n        completed = try attributes.decodeIfPresent(Bool.self, forKey: .completed)\n        canceled = try attributes.decodeIfPresent(Bool.self, forKey: .canceled)\n        creationDate = try attributes.decodeIfPresent(Date.self, forKey: .creationDate)\n        completionDate = try attributes.decodeIfPresent(Date.self, forKey: .completionDate)\n    }\n\n    /// Encodes this value into the given encoder.\n    public func encode(to encoder: Encoder) throws {\n        var attributes = try self.attributes(CodingKeys.self, for: encoder)\n        try attributes.encodeIfPresent(title, forKey: .title)\n        try attributes.encodeIfPresent(completed, forKey: .completed)\n        try attributes.encodeIfPresent(canceled, forKey: .canceled)\n        try attributes.encodeIfPresent(creationDate, forKey: .creationDate)\n        try attributes.encodeIfPresent(completionDate, forKey: .completionDate)\n    }\n}\n\n\n// MARK: - Internal Error\n\nprivate enum TJSError : Error {\n    case invalidType(expectedType: Any.Type, errorContext: DecodingError.Context)\n}\n\n\n// Mark: - Date Formatting\n\n/// A date encoding strategy to format a date according to ISO8601.\n///\n/// Use to with a JSONEncoder to correctly format dates.\npublic func ThingsJSONDateEncodingStrategy() -> JSONEncoder.DateEncodingStrategy {\n    return .iso8601\n}\n\n/// A date decoding strategy to format a date according to ISO8601.\n///\n/// Use to with a JSONDecoder to correctly format dates.\npublic func ThingsJSONDateDecodingStrategy() -> JSONDecoder.DateDecodingStrategy {\n    return .iso8601\n}\n"
  }
]