{"id":13872157,"url":"https://github.com/pointfreeco/swift-custom-dump","last_synced_at":"2026-01-28T22:01:44.175Z","repository":{"id":37835986,"uuid":"336173611","full_name":"pointfreeco/swift-custom-dump","owner":"pointfreeco","description":"A collection of tools for debugging, diffing, and testing your application's data structures.","archived":false,"fork":false,"pushed_at":"2026-01-13T23:27:29.000Z","size":762,"stargazers_count":859,"open_issues_count":10,"forks_count":100,"subscribers_count":12,"default_branch":"main","last_synced_at":"2026-01-13T23:27:42.533Z","etag":null,"topics":["debugging","diffing","testing"],"latest_commit_sha":null,"homepage":"","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/pointfreeco.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":".github/CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2021-02-05T05:28:45.000Z","updated_at":"2026-01-13T23:27:30.000Z","dependencies_parsed_at":"2023-09-27T06:54:29.817Z","dependency_job_id":"0ad411c7-1ced-4674-8762-165834d19c9b","html_url":"https://github.com/pointfreeco/swift-custom-dump","commit_stats":{"total_commits":126,"total_committers":26,"mean_commits":4.846153846153846,"dds":0.2936507936507936,"last_synced_commit":"5d132f70bc59d9b063111bc0576130064f7f9597"},"previous_names":[],"tags_count":34,"template":false,"template_full_name":null,"purl":"pkg:github/pointfreeco/swift-custom-dump","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pointfreeco%2Fswift-custom-dump","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pointfreeco%2Fswift-custom-dump/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pointfreeco%2Fswift-custom-dump/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pointfreeco%2Fswift-custom-dump/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/pointfreeco","download_url":"https://codeload.github.com/pointfreeco/swift-custom-dump/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pointfreeco%2Fswift-custom-dump/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28853194,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-28T15:15:36.453Z","status":"ssl_error","status_checked_at":"2026-01-28T15:15:13.020Z","response_time":57,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["debugging","diffing","testing"],"created_at":"2024-08-05T23:00:35.361Z","updated_at":"2026-01-28T22:01:44.169Z","avatar_url":"https://github.com/pointfreeco.png","language":"Swift","funding_links":[],"categories":["Swift"],"sub_categories":[],"readme":"# Custom Dump\n\n[![CI](https://github.com/pointfreeco/swift-custom-dump/actions/workflows/ci.yml/badge.svg)](https://github.com/pointfreeco/swift-custom-dump/actions/workflows/ci.yml)\n[![](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2Fpointfreeco%2Fswift-custom-dump%2Fbadge%3Ftype%3Dswift-versions)](https://swiftpackageindex.com/pointfreeco/swift-custom-dump)\n[![](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2Fpointfreeco%2Fswift-custom-dump%2Fbadge%3Ftype%3Dplatforms)](https://swiftpackageindex.com/pointfreeco/swift-custom-dump)\n\nA collection of tools for debugging, diffing, and testing your application's data structures.\n\n  * [Motivation](#motivation)\n      * [`customDump`](#customdump)\n      * [`diff`](#diff)\n      * [`expectNoDifference`](#expectnodifference)\n      * [`expectDifference`](#expectdifference)\n  * [Customization](#customization)\n      * [`CustomDumpStringConvertible`](#customdumpstringconvertible)\n      * [`CustomDumpReflectable`](#customdumpreflectable)\n      * [`CustomDumpRepresentable`](#customdumprepresentable)\n  * [Contributing](#contributing)\n  * [Installation](#installation)\n  * [Documentation](#documentation)\n  * [Other Libraries](#other-libraries)\n  * [License](#license)\n\n## Motivation\n\nSwift comes with a wonderful tool for dumping the contents of any value to a string, and it's called `dump`. It prints all the fields and sub-fields of a value into a tree-like description:\n\n```swift\nstruct User {\n  var favoriteNumbers: [Int]\n  var id: Int\n  var name: String\n}\n\nlet user = User(\n  favoriteNumbers: [42, 1729],\n  id: 2,\n  name: \"Blob\"\n)\n\ndump(user)\n```\n```text\n▿ User\n  ▿ favoriteNumbers: 2 elements\n    - 42\n    - 1729\n  - id: 2\n  - name: \"Blob\"\n```\n\nThis is really useful, and can be great for building debug tools that visualize the data held in runtime values of our applications, but sometimes its output is not ideal.\n\nFor example, dumping dictionaries leads to a verbose output that can be hard to read (also note that the keys are unordered):\n\n```swift\ndump([1: \"one\", 2: \"two\", 3: \"three\"])\n```\n```text\n▿ 3 key/value pairs\n  ▿ (2 elements)\n    - key: 2\n    - value: \"two\"\n  ▿ (2 elements)\n    - key: 3\n    - value: \"three\"\n  ▿ (2 elements)\n    - key: 1\n    - value: \"one\"\n```\n\nSimilarly enums have a very verbose output:\n\n```swift\ndump(Result\u003cInt, Error\u003e.success(42))\n```\n```text\n▿ Swift.Result\u003cSwift.Int, Swift.Error\u003e.success\n  - success: 42\n```\n\nIt gets even harder to read when dealing with deeply nested structures:\n\n```swift\ndump([1: Result\u003cUser, Error\u003e.success(user)])\n```\n```text\n▿ 1 key/value pair\n  ▿ (2 elements)\n    - key: 1\n    ▿ value: Swift.Result\u003cUser, Swift.Error\u003e.success\n      ▿ success: User\n        ▿ favoriteNumbers: 2 elements\n          - 42\n          - 1729\n        - id: 2\n        - name: \"Blob\"\n```\n\nThere are also times that `dump` simply does not print useful information, such as enums imported from Objective-C:\n\n```swift\nimport UserNotifications\n\ndump(UNNotificationSetting.disabled)\n```\n```text\n- __C.UNNotificationSetting\n```\n\nSo, while the `dump` function can be handy, it is often too crude of a tool to use. This is the motivation for the `customDump` function.\n\n### `customDump`\n\nThe `customDump` function emulates the behavior of `dump`, but provides a more refined output of nested structures, optimizing for readability. For example, structs are dumped in a format that more closely mimics the struct syntax in Swift, and arrays are dumped with the indices of each element:\n\n```swift\nimport CustomDump\n\ncustomDump(user)\n```\n```text\nUser(\n  favoriteNumbers: [\n    [0]: 42,\n    [1]: 1729\n  ],\n  id: 2,\n  name: \"Blob\"\n)\n```\n\nDictionaries are dumped in a more compact format that mimics Swift's syntax, and automatically orders the keys:\n\n```swift\ncustomDump([1: \"one\", 2: \"two\", 3: \"three\"])\n```\n```text\n[\n  1: \"one\",\n  2: \"two\",\n  3: \"three\"\n]\n```\n\nSimilarly, enums also dump in a more compact, readable format:\n\n```swift\ncustomDump(Result\u003cInt, Error\u003e.success(42))\n```\n```text\nResult.success(42)\n```\n\nAnd deeply nested structures have a simplified tree-structure:\n\n```swift\ncustomDump([1: Result\u003cUser, Error\u003e.success(user)])\n```\n```text\n[\n  1: Result.success(\n    User(\n      favoriteNumbers: [\n        [0]: 42,\n        [1]: 1729\n      ],\n      id: 2,\n      name: \"Blob\"\n    )\n  )\n]\n```\n\n### `diff`\n\nUsing the output of the `customDump` function we can build a very lightweight way to textually diff any two values in Swift:\n\n```swift\nvar other = user\nother.favoriteNumbers[1] = 91\n\nprint(diff(user, other)!)\n```\n```diff\n  User(\n    favoriteNumbers: [\n      [0]: 42,\n-     [1]: 1729\n+     [1]: 91\n    ],\n    id: 2,\n    name: \"Blob\"\n  )\n```\n\nFurther, extra work is done to minimize the size of the diff when parts of the structure haven't changed, such as a single element changing in a large collection:\n\n```swift\nlet users = (1...5).map {\n  User(\n    favoriteNumbers: [$0],\n    id: $0,\n    name: \"Blob \\($0)\"\n  )\n}\n\nvar other = users\nother.append(\n  .init(\n    favoriteNumbers: [42, 1729],\n    id: 100,\n    name: \"Blob Sr.\"\n  )\n)\n\nprint(diff(users, other)!)\n```\n```diff\n  [\n    … (4 unchanged),\n+   [4]: User(\n+     favoriteNumbers: [\n+       [0]: 42,\n+       [1]: 1729\n+     ],\n+     id: 100,\n+     name: \"Blob Sr.\"\n+   )\n  ]\n```\n\nFor a real world use case we modified Apple's [Landmarks](https://developer.apple.com/tutorials/swiftui/working-with-ui-controls) tutorial application to print the before and after state when favoriting a landmark:\n\n```diff\n  [\n    [0]: Landmark(\n      id: 1001,\n      name: \"Turtle Rock\",\n      park: \"Joshua Tree National Park\",\n      state: \"California\",\n      description: \"This very large formation lies south of the large Real Hidden Valley parking lot and immediately adjacent to (south of) the picnic areas.\",\n-     isFavorite: true,\n+     isFavorite: false,\n      isFeatured: true,\n      category: Category.rivers,\n      imageName: \"turtlerock\",\n      coordinates: Coordinates(…)\n    ),\n    … (11 unchanged)\n  ]\n```\n\n### `expectNoDifference`\n\n\u003ctable\u003e\n\u003ctr\u003e\n\u003cth\u003eexpectNoDifference\u003c/th\u003e\n\u003cth\u003e#expect\u003c/th\u003e\n\u003c/tr\u003e\n\u003ctr valign=top\u003e\n\u003ctd width=50%\u003e\n\n![](.github/assert-no-difference.png)\n\n\u003c/td\u003e\n\u003ctd width=50%\u003e\n\n![](.github/expect.png)\n\n\u003c/td\u003e\n\u003c/tr\u003e\n\u003c/table\u003e\n\nXCTest's `XCTAssertEqual` and Swift Testing's `#expect(_ == _)` both allow you to assert that two values are equal, and if they are not the test suite will fail with a message:\n\n```swift\nvar other = user\nother.name += \"!\"\n\nXCTAssertEqual(user, other)\n#expect(user == other)\n```\n```text\nXCTAssertEqual failed: (\"User(favoriteNumbers: [42, 1729], id: 2, name: \"Blob\")\") is not equal to (\"User(favoriteNumbers: [42, 1729], id: 2, name: \"Blob!\")\")\nExpectation failed: (user → User(favoriteNumbers: [42, 1729], id: 2, name: \"Blob\")) == (other → User(favoriteNumbers: [42, 1729], id: 2, name: \"Blob!\"))\n```\n\nUnfortunately these failure messages are quite difficult to visually parse and understand. It takes a few moments of hunting through the message to see that the only difference is the exclamation mark at the end of the name. The problem gets worse if the type is more complex, consisting of nested structures and large collections.\n\nThis library also ships with an `expectNoDifference` function to mitigate these problems. It works like `XCTAssertEqual` and `#expect(_ == _)` except the failure message uses a nicely formatted diff to show exactly what is different between the two values:\n\n```swift\nexpectNoDifference(user, other)\n```\n```diff\nexpectNoDifference failed: …\n\n  User(\n    favoriteNumbers: […],\n    id: 2,\n-   name: \"Blob\"\n+   name: \"Blob!\"\n  )\n\n(First: −, Second: +)\n```\n\n### `expectDifference`\n\nThis function provides the inverse of `expectNoDifference`: it asserts that a value has a set of changes by evaluating a given expression before and after a given operation and then comparing the results.\n\nFor example, given a very simple counter structure, we can write a test against its incrementing functionality:\n\n```swift\nstruct Counter {\n  var count = 0\n  var isOdd = false\n  mutating func increment() {\n    self.count += 1\n    self.isOdd.toggle()\n  }\n}\n\nvar counter = Counter()\nexpectDifference(counter) {\n  counter.increment()\n} changes: {\n  $0.count = 1\n  $0.isOdd = true\n}\n```\n\nIf the `changes` does not exhaustively describe all changed fields, the assertion will fail.\n\nBy omitting the operation you can write a \"non-exhaustive\" assertion against a value by describing just the fields you want to assert against in the `changes` closure:\n\n```swift\ncounter.increment()\nexpectDifference(counter) {\n  $0.count = 1\n  // Don't need to further describe how `isOdd` has changed\n}\n```\n\n## Customization\n\nCustom Dump provides a few important ways to customize how a data type is dumped: `CustomDumpStringConvertible`, `CustomDumpReflectable`, and `CustomDumpRepresentable`.\n\n### `CustomDumpStringConvertible`\n\nThe `CustomDumpStringConvertible` protocol provides a simple way of converting a type to a raw string for the purpose of dumping. It is most appropriate for types that have a simple, un-nested internal representation, and typically its output fits on a single line, for example dates, UUIDs, URLs, etc:\n\n```swift\nextension URL: CustomDumpStringConvertible {\n  public var customDumpDescription: String {\n    \"URL(\\(self.absoluteString))\"\n  }\n}\n\ncustomDump(URL(string: \"https://www.pointfree.co/\")!)\n```\n```text\nURL(https://www.pointfree.co/)\n```\n\nCustom Dump also uses this protocol internally to provide more useful output for enums imported from Objective-C:\n\n```swift\nimport UserNotifications\n\nprint(\"dump:\")\ndump(UNNotificationSetting.disabled)\nprint(\"customDump:\")\ncustomDump(UNNotificationSetting.disabled)\n```\n```text\ndump:\n- __C.UNNotificationSetting\ncustomDump:\nUNNotificationSettings.disabled\n```\n\nEncounter an Objective-C enum that doesn't print nicely? See the [contributing](#contributing) section of this README to help submit a fix.\n\n### `CustomDumpReflectable`\n\nThe `CustomDumpReflectable` protocol provides a more comprehensive way of dumping a type into a more structured output. It allows you to construct a custom mirror that describes the structure that should be dumped. You can omit, add, and replace fields, or even change the \"display style\" of how the structure is dumped.\n\nFor example, let's say you have a struct representing state that holds a secure token in memory that should never be written to your logs. You can omit the token from `customDump` by providing a mirror that omits this field:\n\n```swift\nstruct LoginState: CustomDumpReflectable {\n  var username: String\n  var token: String\n\n  var customDumpMirror: Mirror {\n    .init(\n      self,\n      children: [\n        \"username\": self.username,\n        // omit token from logs\n      ],\n      displayStyle: .struct\n    )\n  }\n}\n\ncustomDump(\n  LoginState(\n    username: \"blob\",\n    token: \"secret\"\n  )\n)\n```\n```text\nLoginState(username: \"blob\")\n```\n\nAnd just like that, no token data will be written to the dump.\n\n### `CustomDumpRepresentable`\n\nThe `CustomDumpRepresentable` protocol allows you to return _any_ value for the purpose of dumping. This can be useful to flatten the dump representation of wrapper types. For example, a type-safe identifier may want to dump its raw value directly:\n\n```swift\nstruct ID: RawRepresentable {\n  var rawValue: String\n}\n\nextension ID: CustomDumpRepresentable {\n  var customDumpValue: Any {\n    self.rawValue\n  }\n}\n\ncustomDump(ID(rawValue: \"deadbeef\")\n```\n```text\n\"deadbeef\"\n```\n\n## Contributing\n\nThere are many types in Apple's ecosystem that do not dump to a nicely formatted string. In particular, all enums that are imported from Objective-C dump to strings that are not very helpful:\n\n```swift\nimport UserNotifications\n\ndump(UNNotificationSetting.disabled)\n```\n```text\n- __C.UNNotificationSetting\n```\n\nFor this reason we have conformed a [bunch](Sources/CustomDump/Conformances) of Apple's types to the `CustomDumpStringConvertible` protocol so that they print out more reasonable descriptions. If you come across types that do not print useful information then we would happily accept a PR to conform those types to `CustomDumpStringConvertible`.\n\n## Installation\n\nYou can add Custom Dump to an Xcode project by adding it as a package dependency.\n\n\u003e https://github.com/pointfreeco/swift-custom-dump\n\nIf you want to use Custom Dump in a [SwiftPM](https://swift.org/package-manager/) project, it's as simple as adding it to a `dependencies` clause in your `Package.swift`:\n\n``` swift\ndependencies: [\n  .package(url: \"https://github.com/pointfreeco/swift-custom-dump\", from: \"1.0.0\")\n]\n```\n\n## Documentation\n\nThe latest documentation for the Custom Dump APIs is available [here](https://swiftpackageindex.com/pointfreeco/swift-custom-dump/documentation).\n\n## Other libraries\n\n* [Difference](https://github.com/krzysztofzablocki/Difference)\n* [MirrorDiffKit](https://github.com/Kuniwak/MirrorDiffKit)\n\n## License\n\nThis library is released under the MIT license. See [LICENSE](LICENSE) for details.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpointfreeco%2Fswift-custom-dump","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpointfreeco%2Fswift-custom-dump","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpointfreeco%2Fswift-custom-dump/lists"}