Full Code of culturedcode/ThingsJSONCoder for AI

master d2b2986a1acc cached
4 files
31.9 KB
7.2k tokens
1 requests
Download .txt
Repository: culturedcode/ThingsJSONCoder
Branch: master
Commit: d2b2986a1acc
Files: 4
Total size: 31.9 KB

Directory structure:
gitextract_mit5d7mk/

├── .gitignore
├── LICENSE
├── README.md
└── ThingsJSON.swift

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitignore
================================================
# Xcode
#
# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore

## Build generated
build/
DerivedData/

## Various settings
*.pbxuser
!default.pbxuser
*.mode1v3
!default.mode1v3
*.mode2v3
!default.mode2v3
*.perspectivev3
!default.perspectivev3
xcuserdata/

## Other
*.moved-aside
*.xccheckout
*.xcscmblueprint
.DS_Store

## Obj-C/Swift specific
*.hmap
*.ipa
*.dSYM.zip
*.dSYM

## Playgrounds
timeline.xctimeline
playground.xcworkspace

# Swift Package Manager
#
# Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
# Packages/
# Package.pins
.build/

# CocoaPods
#
# We recommend against adding the Pods directory to your .gitignore. However
# you should judge for yourself, the pros and cons are mentioned at:
# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
#
# Pods/

# Carthage
#
# Add this line if you want to avoid checking in source code from Carthage dependencies.
# Carthage/Checkouts

Carthage/Build

# fastlane
#
# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
# screenshots whenever they are needed.
# For more information about the recommended setup visit:
# https://docs.fastlane.tools/best-practices/source-control/#source-control

fastlane/report.xml
fastlane/Preview.html
fastlane/screenshots
fastlane/test_output


================================================
FILE: LICENSE
================================================
MIT License

Copyright (c) 2018 Cultured Code GmbH & Co. KG

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: README.md
================================================
# Things JSON Coder

This 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/).

## Installation

To 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.

## Requirements

This code is written with Swift 4.

## Getting Started

#### The Things JSON Container

The 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.

#### Model Classes

The following Things model classes can be encoded into JSON:

* `Todo`
* `Project`
* `Heading`
* `ChecklistItem`

#### Container Enums

There 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.

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

* `TJSProject.Item` – This enum has cases for todo and heading objects. Only todo and heading objects can be items inside a project.

#### Dates
Dates should be formatted according to ISO8601. Setting the JSON encoder’s `dateEncodingStrategy` to `ThingsJSONDateEncodingStrategy()` is the easiest way to do this (see example below).

## Example

Create two todos and a project, encode them into JSON and send to Things’ add command.

```Swift
let todo1 = TJSTodo(title: "Pick up dry cleaning", when: "today")
let todo2 = TJSTodo(title: "Pack for vacation",
                    checklistItems: [TJSChecklistItem(title: "Camera"),
                                     TJSChecklistItem(title: "Passport")])

let project = TJSProject(title: "Go Shopping",
                         items: [.heading(TJSHeading(title: "Dairy")),
                                 .todo(TJSTodo(title: "Milk"))])

let container = TJSContainer(items: [.todo(todo1),
                                     .todo(todo2),
                                     .project(project)])
do {
    let encoder = JSONEncoder()
    encoder.dateEncodingStrategy = ThingsJSONDateEncodingStrategy()
    let data = try encoder.encode(container)
    let json = String(data: data, encoding: .utf8)!
    var components = URLComponents(string: "things:///add-json")!
    let queryItem = URLQueryItem(name: "data", value: json)
    components.queryItems = [queryItem]
    let url = components.url!
    UIApplication.shared.open(url, options: [:], completionHandler: nil)
}
catch {
    // Handle error
}
```

## License

This code is released under the MIT license. See [LICENSE](https://github.com/culturedcode/ThingsJSONCoder/blob/master/LICENSE) for details.


================================================
FILE: ThingsJSON.swift
================================================
//
//  ThingsJSON.swift
//
//  Copyright © 2018 Cultured Code GmbH & Co. KG
//
//  Permission is hereby granted, free of charge, to any person obtaining a copy
//  of this software and associated documentation files (the "Software"), to deal
//  in the Software without restriction, including without limitation the rights
//  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//  copies of the Software, and to permit persons to whom the Software is
//  furnished to do so, subject to the following conditions:
//
//  The above copyright notice and this permission notice shall be included in all
//  copies or substantial portions of the Software.
//
//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
//  SOFTWARE.
//

import Foundation

// MARK: Container

/// The container holding the array of items to be encoded to JSON.
public class TJSContainer : Codable {

    /// The array of items that will be encoded or decoded from the JSON.
    public var items = [Item]()

    /// Create and return a new ThingsJSON object configured with the provided items.
    public init(items: [Item]) {
        self.items = items
    }

    /// Creates a new instance by decoding from the given decoder.
    public required init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        self.items = try container.decode([Item].self)
    }

    /// Encodes this value into the given encoder.
    public func encode(to encoder: Encoder) throws {
        try self.items.encode(to: encoder)
    }

    /// An item that can exist inside the top level JSON array.
    ///
    /// This is an enum that wraps a TJSTodo or TJSProject object and handles its encoding
    /// and decoding to JSON. This is required because there is no way of specifiying a
    /// strongly typed array that contains more than one type.
    public enum Item : Codable {
        case todo(TJSTodo)
        case project(TJSProject)

        /// Creates a new instance by decoding from the given decoder.
        public init(from decoder: Decoder) throws {
            let container = try decoder.singleValueContainer()

            do {
                // Try to decode a to-do
                let todo = try container.decode(TJSTodo.self)
                self = .todo(todo)
            }
            catch TJSError.invalidType(expectedType: _, errorContext: _) {
                // If it's the wrong type, try a project
                let project = try container.decode(TJSProject.self)
                self = .project(project)
            }
        }

        /// Encodes this value into the given encoder.
        public func encode(to encoder: Encoder) throws {
            switch self {
            case .todo(let todo):
                try todo.encode(to: encoder)
            case .project(let project):
                try project.encode(to: encoder)
            }
        }
    }
}


// MARK: - Model Items

/// The superclass of all the Things JSON model items.
///
/// Do not instantiate this class itself. Instead use one of the subclasses.
public class TJSModelItem {
    fileprivate var type: String = ""

    /// The operation to perform on the object.
    public var operation: Operation

    /// The ID of the item to update.
    public var id: String?

    private enum CodingKeys: String, CodingKey {
        case type
        case operation
        case id
        case attributes
    }

    public enum Operation: String, Codable {
        /// Create a new item.
        case create = "create"
        /// Update an existing item.
        ///
        /// Requires id to be set.
        case update = "update"
    }

    public init(operation: Operation, id: String? = nil) {
        self.operation = operation
        self.id = id
    }

    fileprivate func attributes<T>(_ type: T.Type, from decoder: Decoder) throws -> KeyedDecodingContainer<T> {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        let decodedType = try container.decode(String.self, forKey: .type)
        self.operation = try container.decodeIfPresent(Operation.self, forKey: .operation) ?? .create
        self.id = try container.decodeIfPresent(String.self, forKey: .id)
        guard decodedType == self.type else {
            let description = String(format: "Expected to decode a %@ but found a %@ instead.", self.type, decodedType)
            let errorContext = DecodingError.Context(codingPath: [CodingKeys.type], debugDescription: description)
            let expectedType = Swift.type(of: self)
            throw TJSError.invalidType(expectedType: expectedType, errorContext: errorContext)
        }
        return try container.nestedContainer(keyedBy: T.self, forKey: .attributes)
    }

    fileprivate func attributes<T>(_ type: T.Type, for encoder: Encoder) throws -> KeyedEncodingContainer<T> {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(self.type, forKey: .type)
        try container.encode(self.operation, forKey: .operation)
        try container.encodeIfPresent(self.id, forKey: .id)
        return container.nestedContainer(keyedBy: T.self, forKey: .attributes)
    }
}


// MARK: -

/// Represents a to-do in Things.
public class TJSTodo : TJSModelItem, Codable {
    public var title: String?
    public var notes: String?
    public var prependNotes: String?
    public var appendNotes: String?
    public var when: String?
    public var deadline: String?
    public var tagIDs: [String]?
    public var tags: [String]?
    public var addTags: [String]?
    public var checklistItems: [TJSChecklistItem]?
    public var prependChecklistItems: [TJSChecklistItem]?
    public var appendChecklistItems: [TJSChecklistItem]?
    public var listID: String?
    public var list: String?
    public var headingID: String?
    public var heading: String?
    public var completed: Bool?
    public var canceled: Bool?
    public var creationDate: Date?
    public var completionDate: Date?

    private enum CodingKeys: String, CodingKey {
        case title
        case notes
        case prependNotes = "prepend-notes"
        case appendNotes = "append-notes"
        case when
        case deadline
        case tagIDs = "tag-ids"
        case tags
        case addTags = "add-tags"
        case checklistItems = "checklist-items"
        case prependChecklistItems = "prepend-checklist-items"
        case appendChecklistItems = "append-checklist-items"
        case listID = "list-id"
        case list
        case headingID = "heading-id"
        case heading
        case completed
        case canceled
        case creationDate = "creation-date"
        case completionDate = "completion-date"
    }

    /// Create and return a new todo configured with the provided values.
    public init(operation: Operation = .create,
         id: String? = nil,
         title: String? = nil,
         notes: String? = nil,
         prependNotes: String? = nil,
         appendNotes: String? = nil,
         when: String? = nil,
         deadline: String? = nil,
         tagIDs: [String]? = nil,
         tags: [String]? = nil,
         addTags: [String]? = nil,
         checklistItems: [TJSChecklistItem]? = nil,
         prependChecklistItems: [TJSChecklistItem]? = nil,
         appendChecklistItems: [TJSChecklistItem]? = nil,
         listID: String? = nil,
         list: String? = nil,
         headingID: String? = nil,
         heading: String? = nil,
         completed: Bool? = nil,
         canceled: Bool? = nil,
         creationDate: Date? = nil,
         completionDate: Date? = nil) {

        super.init(operation: operation, id: id)
        self.type = "to-do"

        self.title = title
        self.notes = notes
        self.prependNotes = prependNotes
        self.appendNotes = appendNotes
        self.when = when
        self.deadline = deadline
        self.tagIDs = tagIDs
        self.tags = tags
        self.addTags = addTags
        self.checklistItems = checklistItems
        self.prependChecklistItems = prependChecklistItems
        self.appendChecklistItems = appendChecklistItems
        self.listID = listID
        self.list = list
        self.heading = heading
        self.headingID = headingID
        self.completed = completed
        self.canceled = canceled
        self.creationDate = creationDate
        self.completionDate = completionDate
    }

    /// Create and return a new todo configured with same values as the provided todo.
    public convenience init(_ todo: TJSTodo) {
        self.init(id: todo.id,
                  title: todo.title,
                  notes: todo.notes,
                  prependNotes: todo.prependNotes,
                  appendNotes: todo.appendNotes,
                  when: todo.when,
                  deadline: todo.deadline,
                  tagIDs: todo.tagIDs,
                  tags: todo.tags,
                  addTags: todo.addTags,
                  checklistItems: todo.checklistItems,
                  prependChecklistItems: todo.prependChecklistItems,
                  appendChecklistItems: todo.appendChecklistItems,
                  listID: todo.listID,
                  list: todo.list,
                  headingID: todo.headingID,
                  heading: todo.heading,
                  completed: todo.completed,
                  canceled: todo.canceled,
                  creationDate: todo.creationDate,
                  completionDate: todo.completionDate)
    }

    /// Creates a new instance by decoding from the given decoder.
    public required convenience init(from decoder: Decoder) throws {
        self.init()
        let attributes = try self.attributes(CodingKeys.self, from: decoder)
        do {
            title = try attributes.decodeIfPresent(String.self, forKey: .title)
            notes = try attributes.decodeIfPresent(String.self, forKey: .notes)
            prependNotes = try attributes.decodeIfPresent(String.self, forKey: .prependNotes)
            appendNotes = try attributes.decodeIfPresent(String.self, forKey: .appendNotes)
            when = try attributes.decodeIfPresent(String.self, forKey: .when)
            deadline = try attributes.decodeIfPresent(String.self, forKey: .deadline)
            tagIDs = try attributes.decodeIfPresent([String].self, forKey: .tagIDs)
            tags = try attributes.decodeIfPresent([String].self, forKey: .tags)
            addTags = try attributes.decodeIfPresent([String].self, forKey: .addTags)
            checklistItems = try attributes.decodeIfPresent([TJSChecklistItem].self, forKey: .checklistItems)
            prependChecklistItems = try attributes.decodeIfPresent([TJSChecklistItem].self, forKey: .prependChecklistItems)
            appendChecklistItems = try attributes.decodeIfPresent([TJSChecklistItem].self, forKey: .appendChecklistItems)
            listID = try attributes.decodeIfPresent(String.self, forKey: .listID)
            list = try attributes.decodeIfPresent(String.self, forKey: .list)
            headingID = try attributes.decodeIfPresent(String.self, forKey: .headingID)
            heading = try attributes.decodeIfPresent(String.self, forKey: .heading)
            completed = try attributes.decodeIfPresent(Bool.self, forKey: .completed)
            canceled = try attributes.decodeIfPresent(Bool.self, forKey: .canceled)
            creationDate = try attributes.decodeIfPresent(Date.self, forKey: .creationDate)
            completionDate = try attributes.decodeIfPresent(Date.self, forKey: .completionDate)
        }
        catch TJSError.invalidType(let expectedType, let errorContext) {
            throw DecodingError.typeMismatch(expectedType, errorContext)
        }
    }

    /// Encodes this value into the given encoder.
    public func encode(to encoder: Encoder) throws {
        var attributes = try self.attributes(CodingKeys.self, for: encoder)
        try attributes.encodeIfPresent(title, forKey: .title)
        try attributes.encodeIfPresent(notes, forKey: .notes)
        try attributes.encodeIfPresent(prependNotes, forKey: .prependNotes)
        try attributes.encodeIfPresent(appendNotes, forKey: .appendNotes)
        try attributes.encodeIfPresent(when, forKey: .when)
        try attributes.encodeIfPresent(deadline, forKey: .deadline)
        try attributes.encodeIfPresent(tagIDs, forKey: .tagIDs)
        try attributes.encodeIfPresent(tags, forKey: .tags)
        try attributes.encodeIfPresent(addTags, forKey: .addTags)
        try attributes.encodeIfPresent(checklistItems, forKey: .checklistItems)
        try attributes.encodeIfPresent(prependChecklistItems, forKey: .prependChecklistItems)
        try attributes.encodeIfPresent(appendChecklistItems, forKey: .appendChecklistItems)
        try attributes.encodeIfPresent(listID, forKey: .listID)
        try attributes.encodeIfPresent(list, forKey: .list)
        try attributes.encodeIfPresent(headingID, forKey: .headingID)
        try attributes.encodeIfPresent(heading, forKey: .heading)
        try attributes.encodeIfPresent(completed, forKey: .completed)
        try attributes.encodeIfPresent(canceled, forKey: .canceled)
        try attributes.encodeIfPresent(creationDate, forKey: .creationDate)
        try attributes.encodeIfPresent(completionDate, forKey: .completionDate)
    }
}


// MARK: -

/// Represents a project in Things.
public class TJSProject : TJSModelItem, Codable {
    var title: String?
    var notes: String?
    var prependNotes: String?
    var appendNotes: String?
    var when: String?
    var deadline: String?
    var tagIDs: [String]?
    var tags: [String]?
    var addTags: [String]?
    var areaID: String?
    var area: String?
    var items: [Item]?
    var completed: Bool?
    var canceled: Bool?
    var creationDate: Date?
    var completionDate: Date?

    private enum CodingKeys: String, CodingKey {
        case title
        case notes
        case prependNotes = "prepend-notes"
        case appendNotes = "append-notes"
        case when
        case deadline
        case tagIDs = "tag-ids"
        case tags
        case addTags = "add-tags"
        case areaID = "area-id"
        case area
        case items
        case completed
        case canceled
        case creationDate = "creation-date"
        case completionDate = "completion-date"
    }

    /// Create and return a new project configured with the provided values.
    init(operation: Operation = .create,
         id: String? = nil,
         title: String? = nil,
         notes: String? = nil,
         prependNotes: String? = nil,
         appendNotes: String? = nil,
         when: String? = nil,
         deadline: String? = nil,
         tagIDs: [String]? = nil,
         tags: [String]? = nil,
         addTags: [String]? = nil,
         areaID: String? = nil,
         area: String? = nil,
         items: [Item]? = nil,
         completed: Bool? = nil,
         canceled: Bool? = nil,
         creationDate: Date? = nil,
         completionDate: Date? = nil) {

        super.init(operation: operation, id: id)
        self.type = "project"

        self.title = title
        self.notes = notes
        self.prependNotes = prependNotes
        self.appendNotes = appendNotes
        self.when = when
        self.deadline = deadline
        self.tagIDs = tagIDs
        self.tags = tags
        self.addTags = addTags
        self.areaID = areaID
        self.area = area
        self.items = items
        self.completed = completed
        self.canceled = canceled
        self.creationDate = creationDate
        self.completionDate = completionDate
    }

    /// Create and return a new project configured with same values as the provided project.
    convenience init(_ project: TJSProject) {
        self.init(id: project.id,
                  title: project.title,
                  notes: project.notes,
                  prependNotes: project.prependNotes,
                  appendNotes: project.appendNotes,
                  when: project.when,
                  deadline: project.deadline,
                  tagIDs: project.tagIDs,
                  tags: project.tags,
                  addTags: project.addTags,
                  areaID: project.areaID,
                  area: project.area,
                  items: project.items,
                  completed: project.completed,
                  canceled: project.canceled,
                  creationDate: project.creationDate,
                  completionDate: project.completionDate)
    }

    /// Creates a new instance by decoding from the given decoder.
    public required convenience init(from decoder: Decoder) throws {
        self.init()
        let attributes = try self.attributes(CodingKeys.self, from: decoder)
        do {
            title = try attributes.decodeIfPresent(String.self, forKey: .title)
            notes = try attributes.decodeIfPresent(String.self, forKey: .notes)
            prependNotes = try attributes.decodeIfPresent(String.self, forKey: .prependNotes)
            appendNotes = try attributes.decodeIfPresent(String.self, forKey: .appendNotes)
            when = try attributes.decodeIfPresent(String.self, forKey: .when)
            deadline = try attributes.decodeIfPresent(String.self, forKey: .deadline)
            tagIDs = try attributes.decodeIfPresent([String].self, forKey: .tagIDs)
            tags = try attributes.decodeIfPresent([String].self, forKey: .tags)
            addTags = try attributes.decodeIfPresent([String].self, forKey: .addTags)
            areaID = try attributes.decodeIfPresent(String.self, forKey: .areaID)
            area = try attributes.decodeIfPresent(String.self, forKey: .area)
            completed = try attributes.decodeIfPresent(Bool.self, forKey: .completed)
            canceled = try attributes.decodeIfPresent(Bool.self, forKey: .canceled)
            items = try attributes.decodeIfPresent([Item].self, forKey: .items)
            creationDate = try attributes.decodeIfPresent(Date.self, forKey: .creationDate)
            completionDate = try attributes.decodeIfPresent(Date.self, forKey: .completionDate)
        }
        catch TJSError.invalidType(let expectedType, let errorContext) {
            throw DecodingError.typeMismatch(expectedType, errorContext)
        }
    }

    /// Encodes this value into the given encoder.
    public func encode(to encoder: Encoder) throws {
        var attributes = try self.attributes(CodingKeys.self, for: encoder)
        try attributes.encodeIfPresent(title, forKey: .title)
        try attributes.encodeIfPresent(notes, forKey: .notes)
        try attributes.encodeIfPresent(prependNotes, forKey: .prependNotes)
        try attributes.encodeIfPresent(appendNotes, forKey: .appendNotes)
        try attributes.encodeIfPresent(when, forKey: .when)
        try attributes.encodeIfPresent(deadline, forKey: .deadline)
        try attributes.encodeIfPresent(tagIDs, forKey: .tagIDs)
        try attributes.encodeIfPresent(tags, forKey: .tags)
        try attributes.encodeIfPresent(addTags, forKey: .addTags)
        try attributes.encodeIfPresent(areaID, forKey: .areaID)
        try attributes.encodeIfPresent(area, forKey: .area)
        try attributes.encodeIfPresent(items, forKey: .items)
        try attributes.encodeIfPresent(completed, forKey: .completed)
        try attributes.encodeIfPresent(canceled, forKey: .canceled)
        try attributes.encodeIfPresent(creationDate, forKey: .creationDate)
        try attributes.encodeIfPresent(completionDate, forKey: .completionDate)
    }

    /// A child item of a project.
    ///
    /// This is an enum that wraps a TJSTodo or TJSHeading object and handles its encoding
    /// and decoding to JSON. This is required because there is no way of specifiying a
    /// strongly typed array that contains more than one type.
    public enum Item : Codable {
        case todo(TJSTodo)
        case heading(TJSHeading)

        /// Creates a new instance by decoding from the given decoder.
        public init(from decoder: Decoder) throws {
            let container = try decoder.singleValueContainer()

            do {
                // Try to decode a to-do
                let todo = try container.decode(TJSTodo.self)
                self = .todo(todo)
            }
            catch TJSError.invalidType(expectedType: _, errorContext: _) {
                // If it's the wrong type, try a heading
                let heading = try container.decode(TJSHeading.self)
                self = .heading(heading)
            }
        }

        /// Encodes this value into the given encoder.
        public func encode(to encoder: Encoder) throws {
            switch self {
            case .todo(let todo):
                try todo.encode(to: encoder)
            case .heading(let heading):
                try heading.encode(to: encoder)
            }
        }
    }
}


// MARK: -

/// Represents a heading in Things.
public class TJSHeading : TJSModelItem, Codable {
    public var title: String?
    public var archived: Bool?
    public var creationDate: Date?
    public var completionDate: Date?

    private enum CodingKeys: String, CodingKey {
        case title
        case archived
        case creationDate = "creation-date"
        case completionDate = "completion-date"
    }

    /// Create and return a new heading configured with the provided values.
    public init(operation: Operation = .create,
         title: String? = nil,
         archived: Bool? = nil,
         creationDate: Date? = nil,
         completionDate: Date? = nil) {

        super.init(operation: operation)
        self.type = "heading"

        self.title = title
        self.archived = archived
        self.creationDate = creationDate
        self.completionDate = completionDate
    }

    /// Create and return a new heading configured with same values as the provided heading.
    public convenience init(_ heading: TJSHeading) {
        self.init(title: heading.title,
                  archived: heading.archived,
                  creationDate: heading.creationDate,
                  completionDate: heading.completionDate)
    }

    /// Creates a new instance by decoding from the given decoder.
    public required convenience init(from decoder: Decoder) throws {
        self.init()
        let attributes = try self.attributes(CodingKeys.self, from: decoder)
        title = try attributes.decodeIfPresent(String.self, forKey: .title)
        archived = try attributes.decodeIfPresent(Bool.self, forKey: .archived)
        creationDate = try attributes.decodeIfPresent(Date.self, forKey: .creationDate)
        completionDate = try attributes.decodeIfPresent(Date.self, forKey: .completionDate)
    }

    /// Encodes this value into the given encoder.
    public func encode(to encoder: Encoder) throws {
        var attributes = try self.attributes(CodingKeys.self, for: encoder)
        try attributes.encodeIfPresent(title, forKey: .title)
        try attributes.encodeIfPresent(archived, forKey: .archived)
        try attributes.encodeIfPresent(creationDate, forKey: .creationDate)
        try attributes.encodeIfPresent(completionDate, forKey: .completionDate)
    }
}


// MARK: -

/// Represents a checklist item in Things.
public class TJSChecklistItem : TJSModelItem, Codable {
    public var title: String?
    public var completed: Bool?
    public var canceled: Bool?
    public var creationDate: Date?
    public var completionDate: Date?

    private enum CodingKeys: String, CodingKey {
        case title
        case completed
        case canceled
        case creationDate = "creation-date"
        case completionDate = "completion-date"
    }

    /// Create and return a new checklist item configured with the provided values.
    public init(operation: Operation = .create,
         title: String? = nil,
         completed: Bool? = nil,
         canceled: Bool? = nil,
         creationDate: Date? = nil,
         completionDate: Date? = nil) {

        super.init(operation: operation)
        self.type = "checklist-item"

        self.title = title
        self.completed = completed
        self.canceled = canceled
        self.creationDate = creationDate
        self.completionDate = completionDate
    }

    /// Create and return a new checklist item configured with same values as the provided checklist item.
    public convenience init (_ checklistItem: TJSChecklistItem) {
        self.init(title: checklistItem.title,
                  completed: checklistItem.completed,
                  canceled: checklistItem.canceled,
                  creationDate: checklistItem.creationDate,
                  completionDate: checklistItem.completionDate)
    }

    /// Creates a new instance by decoding from the given decoder.
    public required convenience init(from decoder: Decoder) throws {
        self.init()
        let attributes = try self.attributes(CodingKeys.self, from: decoder)
        title = try attributes.decodeIfPresent(String.self, forKey: .title)
        completed = try attributes.decodeIfPresent(Bool.self, forKey: .completed)
        canceled = try attributes.decodeIfPresent(Bool.self, forKey: .canceled)
        creationDate = try attributes.decodeIfPresent(Date.self, forKey: .creationDate)
        completionDate = try attributes.decodeIfPresent(Date.self, forKey: .completionDate)
    }

    /// Encodes this value into the given encoder.
    public func encode(to encoder: Encoder) throws {
        var attributes = try self.attributes(CodingKeys.self, for: encoder)
        try attributes.encodeIfPresent(title, forKey: .title)
        try attributes.encodeIfPresent(completed, forKey: .completed)
        try attributes.encodeIfPresent(canceled, forKey: .canceled)
        try attributes.encodeIfPresent(creationDate, forKey: .creationDate)
        try attributes.encodeIfPresent(completionDate, forKey: .completionDate)
    }
}


// MARK: - Internal Error

private enum TJSError : Error {
    case invalidType(expectedType: Any.Type, errorContext: DecodingError.Context)
}


// Mark: - Date Formatting

/// A date encoding strategy to format a date according to ISO8601.
///
/// Use to with a JSONEncoder to correctly format dates.
public func ThingsJSONDateEncodingStrategy() -> JSONEncoder.DateEncodingStrategy {
    return .iso8601
}

/// A date decoding strategy to format a date according to ISO8601.
///
/// Use to with a JSONDecoder to correctly format dates.
public func ThingsJSONDateDecodingStrategy() -> JSONDecoder.DateDecodingStrategy {
    return .iso8601
}
Download .txt
gitextract_mit5d7mk/

├── .gitignore
├── LICENSE
├── README.md
└── ThingsJSON.swift
Condensed preview — 4 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (34K chars).
[
  {
    "path": ".gitignore",
    "chars": 1458,
    "preview": "# Xcode\n#\n# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore\n\n"
  },
  {
    "path": "LICENSE",
    "chars": 1084,
    "preview": "MIT License\n\nCopyright (c) 2018 Cultured Code GmbH & Co. KG\n\nPermission is hereby granted, free of charge, to any person"
  },
  {
    "path": "README.md",
    "chars": 3119,
    "preview": "# Things JSON Coder\n\nThis repo contains a Swift file that allows the creation of the JSON required to be passed to the `"
  },
  {
    "path": "ThingsJSON.swift",
    "chars": 27008,
    "preview": "//\n//  ThingsJSON.swift\n//\n//  Copyright © 2018 Cultured Code GmbH & Co. KG\n//\n//  Permission is hereby granted, free of"
  }
]

About this extraction

This page contains the full source code of the culturedcode/ThingsJSONCoder GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 4 files (31.9 KB), approximately 7.2k tokens. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!