{"id":32148926,"url":"https://github.com/alexhunsley/patchouli-jsonpatch","last_synced_at":"2026-02-19T02:02:14.941Z","repository":{"id":240087076,"uuid":"801632577","full_name":"alexhunsley/patchouli-jsonpatch","owner":"alexhunsley","description":"Ergonomic JSON Patch in Swift with a handy DSL.","archived":false,"fork":false,"pushed_at":"2024-06-26T20:28:23.000Z","size":1050,"stargazers_count":3,"open_issues_count":4,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-11-18T09:23:09.523Z","etag":null,"topics":["dsl","json","jsonpatch","patcher","patching","patchouli-patcher","resultbuilder","swift"],"latest_commit_sha":null,"homepage":"","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/alexhunsley.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"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}},"created_at":"2024-05-16T15:55:32.000Z","updated_at":"2024-09-05T17:09:51.000Z","dependencies_parsed_at":"2024-06-26T17:28:51.406Z","dependency_job_id":"9a955247-3802-46ec-86a1-9856d4c9c9a5","html_url":"https://github.com/alexhunsley/patchouli-jsonpatch","commit_stats":null,"previous_names":["alexhunsley/patchouli-jsonpatch"],"tags_count":7,"template":false,"template_full_name":null,"purl":"pkg:github/alexhunsley/patchouli-jsonpatch","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alexhunsley%2Fpatchouli-jsonpatch","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alexhunsley%2Fpatchouli-jsonpatch/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alexhunsley%2Fpatchouli-jsonpatch/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alexhunsley%2Fpatchouli-jsonpatch/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/alexhunsley","download_url":"https://codeload.github.com/alexhunsley/patchouli-jsonpatch/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alexhunsley%2Fpatchouli-jsonpatch/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29600845,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-19T00:59:38.239Z","status":"online","status_checked_at":"2026-02-19T02:00:07.702Z","response_time":117,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"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":["dsl","json","jsonpatch","patcher","patching","patchouli-patcher","resultbuilder","swift"],"created_at":"2025-10-21T09:24:12.760Z","updated_at":"2026-02-19T02:02:14.932Z","avatar_url":"https://github.com/alexhunsley.png","language":"Swift","readme":"\u003cdiv align=\"center\"\u003e\n\u003cimg src=\"https://github.com/alexhunsley/patchouli-jsonpatch/blob/main/Tests/Resources/patchouli-json-logo-1-scaled-378x375.png\"\u003e\n\u003c/div\u003e\n\n# Patchouli JSON Patcher\n\n[![Apache 2 License](https://img.shields.io/badge/license-Apache%202-blue.svg)](https://opensource.org/licenses/Apache-2.0)\n[![](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2Falexhunsley%2Fpatchouli-core%2Fbadge%3Ftype%3Dswift-versions)](https://swiftpackageindex.com/alexhunsley/patchouli-core)\n[![](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2Falexhunsley%2Fpatchouli-core%2Fbadge%3Ftype%3Dplatforms)](https://swiftpackageindex.com/alexhunsley/patchouli-core)\n[![Build System](https://img.shields.io/badge/dependency%20management-spm-yellow.svg)](https://swift.org/package-manager/)\n\n\n\nThis is a [JSON Patch](https://jsonpatch.com) library for Swift featuring an ergonomic DSL. It is built with the [Patchouli Core](https://github.com/alexhunsley/patchouli-core) engine and uses [JSONPatch](https://github.com/raymccrae/swift-jsonpatch) lib for the patch execution.\n\n## Installation\n\nTo install via SPM, add a dependency for `https://github.com/alexhunsley/patchouli-jsonpatch`.\n\nOr to take PatchouliJSON for a spin in a playground, visit https://swiftpackageindex.com/alexhunsley/patchouli-jsonpatch and select \"Try in a playground\".\n\n## Usage\n\n```swift\n    import PatchouliJSON\n\n    // Use the DSL to construct a patch on a file's JSON content.\n    // This step doesn't do the actual patching!\n\n    let patchedJSONContent: PatchedJSON = Content(fileURL: someJSONFileURL) {\n\n        Add(address: \"/users/-\", jsonContent: \"alex\")\n\n        Add(address: \"/users/-\", jsonContent: [1, \"hi\", 5.0])\n\n        // note: please use `null` (and never `nil`) to represent JSON's `null`\n        Add(address: \"/users/-\", jsonContent: null)\n\n        Remove(address: \"/temp/log\")\n\n        Replace(address: \"/user_type\", jsonContent: \"admin\")\n\n        Move(fromAddress: \"/v1/last_login\", toAddress: \"/v2/last_login\")\n\n        Copy(fromAddress: \"/v1/login_count\", toAddress: \"/v2/login_count\")\n\n        Test(address: \"/magic_string\", jsonContent: \"0xdeadbeef\")\n    }\n\n    // execute the patch\n    let resultJSONContent: JSONContent = try content.reduced()\n\n    // Get the result in various forms\n    let jsonData = try dataResult.data()\n    let jsonString = try dataResult.string() // defaults to UTF8\n    let jsonStringUTF16 = try dataResult.string(encoding: utf16)\n```\n\nYou can also fetch JSON content from bundles:\n\n```swift\n    // loads User.json from the main bundle\n    let patchedJSONContent: PatchedJSON = Content(resource: \"User\", bundle: Bundle.main) {\n        Add(address: \"/users/-\", jsonContent: \"alex\")\n        ...\n```\n\nAnd you can use string literals for your source json (utf8 is assumed):\n\n```swift\n    let patchedJSONContent: PatchedJSON = Content(string:\n        \"\"\"\n        { \"greet\": \"Hello\", \"bye\": \"auf wiedersehen\", \"users\": [] }\n        \"\"\"\n    ) {\n        Add(address: \"/users/-\", jsonContent: \"alex\")\n        ...\n```\n\nYou can also use an empty JSON object or array as your starting point:\n\n```swift\n        let patchedEmptyObject = EmptyJSONObject {\n            Add(address: \"/asdf\", jsonContent: \"Hello\")\n        }\n\n        let patchedEmptyArray1 = EmptyJSONArray {\n            Add(address: \"/-\", jsonContent: \"xyz\")\n            Add(address: \"/-\", jsonContent: \"abc\")\n        }\n```\n\nYou can use `@JSONPatchListBuilder` in the same way as SwiftUI's `@ViewBuilder` to break up your patch declarations:\n\n```swift\n    @JSONPatchListBuilder\n    func builderFunc(name: String) -\u003e JSONPatchList {\n        Add(address: \"/asdf\", jsonContent: \"Hello \\(name)\")\n        Add(address: \"/qwer\", jsonContent: \"Bye \\(name)\")\n    }\n\n    @JSONPatchListBuilder\n    var builderVar: JSONPatchList {\n        Add(address: \"/addr1\", jsonContent: \"Hello\")\n        Add(address: \"/addr2\", jsonContent: \"Bye\")\n        builderFunc(name: \"boo\")\n    }\n\n    func useBuilderHelpers() throws {\n        let patchedJSONContent = Content(JSONPatchType.emptyObjectContent) {\n            builderFunc(name: \"fred\")\n            builderVar\n        }\n```\n\nThis is particularly useful if you want to declare a patch list just once for use on multiple different bits of JSON.\n\n## DSL features\n\nThe DSL can handle `if`, `if-else`, `for`, `for-in`, and optionals. For example:\n\n```swift\n    let patchedJSONContent: PatchedJSON = Content(fileURL: someJSONFileURL) {\n        for index in 0...5 {\n            if someCheck(index) {\n                Add(address: \"/abc\", jsonContent: \"\\(index)\")\n            }\n\n            if someCondition {\n                Add(address: \"/cde\", jsonContent: \"\\(index)\")\n            } else {\n                Remove(address: \"/cde\")\n            } \n        }\n\n        for user in users {\n            Add(address: \"/usernames/-\", jsonContent: \"\\(user.username)\")\n        }\n    }\n\n```\n\n## Nested patches\n\nThe DSL can handle nesting, which means you can have patches-within-patches:\n\n```swift\n    let patchedJSONContent: PatchedJSON = Content(fileURL: jsonFile1URL) {\n\n        Add(address: \"/some_key\", content: Content(fileURL: jsonFile2URL) {\n            Replace(address: \"hello\", jsonContent: \"friend\")\n        })\n        \n        Remove(address: \"/remove_me\")\n    }\n```\n\nNote that with nested patching, the deepest operations are resolved first: in the above, the `Replace` patch is applied to the contents of JSON file 2. The result of that is then added at `/some_key` in the content from JSON file 1. And finally the `Remove` is peformed on what we have.\n\n# Built on top of Patchouli, a generic patching engine\n\nPatchouli JSON is built on top of [Patchouli Core](https://github.com/alexhunsley/patchouli-core), a generic patching engine. You can use Pathcouli Core to make patchers for other kinds of data.\n\n# Project Keywords\n\n```\nresultbuilder, protocol witness, generics, json, jsonpatch\n```\n\n# License\n\n```\nCopyright 2024 Alex Hunsley\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n   http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n```\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Falexhunsley%2Fpatchouli-jsonpatch","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Falexhunsley%2Fpatchouli-jsonpatch","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Falexhunsley%2Fpatchouli-jsonpatch/lists"}