{"id":18603022,"url":"https://github.com/devlooped/json","last_synced_at":"2025-04-10T19:31:24.251Z","repository":{"id":40300148,"uuid":"406080311","full_name":"devlooped/json","owner":"devlooped","description":"JsonPeek and JsonPoke tasks implementations","archived":false,"fork":false,"pushed_at":"2025-03-18T00:12:44.000Z","size":201,"stargazers_count":8,"open_issues_count":8,"forks_count":1,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-03-25T03:34:06.912Z","etag":null,"topics":["dotnet","json","msbuild"],"latest_commit_sha":null,"homepage":"https://clarius.org/json","language":"C#","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/devlooped.png","metadata":{"files":{"readme":"readme.md","changelog":"changelog.md","contributing":null,"funding":".github/FUNDING.yml","license":"license.txt","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},"funding":{"github":"devlooped"}},"created_at":"2021-09-13T18:10:00.000Z","updated_at":"2025-03-17T22:46:38.000Z","dependencies_parsed_at":"2025-02-18T08:25:28.425Z","dependency_job_id":"2543aef6-fcac-4285-bc04-29b403676288","html_url":"https://github.com/devlooped/json","commit_stats":null,"previous_names":[],"tags_count":15,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/devlooped%2Fjson","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/devlooped%2Fjson/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/devlooped%2Fjson/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/devlooped%2Fjson/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/devlooped","download_url":"https://codeload.github.com/devlooped/json/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248281400,"owners_count":21077423,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","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":["dotnet","json","msbuild"],"created_at":"2024-11-07T02:13:16.381Z","updated_at":"2025-04-10T19:31:19.236Z","avatar_url":"https://github.com/devlooped.png","language":"C#","funding_links":["https://github.com/sponsors/devlooped","https://github.com/sponsors"],"categories":[],"sub_categories":[],"readme":"![JSON Icon](assets/img/json.png) JsonPeek and JsonPoke MSBuild Tasks\n============\n\n[![License](https://img.shields.io/github/license/devlooped/json.svg?color=blue)](https://github.com/devlooped/json/blob/main/license.txt)\n[![Build](https://github.com/devlooped/json/workflows/build/badge.svg?branch=main)](https://github.com/devlooped/json/actions)\n\n![JsonPeek Icon](assets/img/jsonpeek.png) JsonPeek\n============\n\n[![Version](https://img.shields.io/nuget/vpre/JsonPeek.svg?color=royalblue)](https://www.nuget.org/packages/JsonPeek)\n[![Downloads](https://img.shields.io/nuget/dt/JsonPeek.svg?color=green)](https://www.nuget.org/packages/JsonPeek)\n\n\u003c!-- #JsonPeek --\u003e\nRead values from JSON using JSONPath.\n\nUsage:\n\n```xml\n  \u003cJsonPeek ContentPath=\"[JSON_FILE]\" Query=\"[JSONPath]\"\u003e\n    \u003cOutput TaskParameter=\"Result\" PropertyName=\"Value\" /\u003e\n  \u003c/JsonPeek\u003e\n  \u003cJsonPeek Content=\"[JSON]\" Query=\"[JSONPath]\"\u003e\n    \u003cOutput TaskParameter=\"Result\" ItemName=\"Values\" /\u003e\n  \u003c/JsonPeek\u003e\n```\n\nParameters:\n\n| Parameter   | Description                                                                                                    |\n| ----------- | -------------------------------------------------------------------------------------------------------------- |\n| Content     | Optional `string` parameter.\u003cbr/\u003eSpecifies the JSON input as a string.                                         |\n| ContentPath | Optional `ITaskItem` parameter.\u003cbr/\u003eSpecifies the JSON input as a file path.                                   |\n| Empty       | Optional `string` parameter.\u003cbr/\u003eValue to use as a replacement for empty values matched in JSON.               |\n| Query       | Required `string` parameter.\u003cbr/\u003eSpecifies the [JSONPath](https://goessner.net/articles/JsonPath/) expression. |\n| Result      | Output `ITaskItem[]` parameter.\u003cbr/\u003eContains the results that are returned by the task.                        |\n\nYou can either provide the path to a JSON file via `ContentPath` or \nprovide the straight JSON content to `Content`. The `Query` is a \n[JSONPath](https://goessner.net/articles/JsonPath/) expression that is evaluated \nand returned via the `Result` task parameter. You can assign the resulting \nvalue to either a property (i.e. for a single value) or an item name (i.e. \nfor multiple results).\n\nJSON object properties are automatically projected as item metadata when \nassigning the resulting value to an item. For example, given the following JSON:\n\n```JSON\n{\n    \"http\": {\n        \"host\": \"localhost\",\n        \"port\": 80,\n        \"ssl\": true\n    }\n}\n```\n\nYou can read the entire `http` value as an item with each property as a metadata \nvalue with:\n\n```xml\n\u003cJsonPeek ContentPath=\"host.json\" Query=\"$.http\"\u003e\n    \u003cOutput TaskParameter=\"Result\" ItemName=\"Http\" /\u003e\n\u003c/JsonPeek\u003e\n```\n\nThe `Http` item will have the following values (if it were declared in MSBuild):\n\n```xml\n\u003cItemGroup\u003e\n    \u003cHttp Include=\"[item raw json]\"\u003e\n        \u003chost\u003elocalhost\u003c/host\u003e\n        \u003cport\u003e80\u003c/port\u003e\n        \u003cssl\u003etrue\u003c/ssl\u003e\n    \u003c/Http\u003e\n\u003c/ItemGroup\u003e\n```\n\nThese item metadata values could be read as MSBuild properties as follows, for example:\n\n```xml\n\u003cPropertyGroup\u003e\n    \u003cHost\u003e@(Http -\u003e '%(host)')\u003c/Host\u003e\n    \u003cPort\u003e@(Http -\u003e '%(port)')\u003c/Port\u003e\n    \u003cSsl\u003e@(Http -\u003e '%(ssl)')\u003c/Ssl\u003e\n\u003c/PropertyGroup\u003e\n```\n\nIn addition to the explicitly opted in object properties, the entire node is available \nas raw JSON via the special `_` (single underscore) metadata item.\n\nIf the matched value is empty, no items (because items cannot be constructed with empty \nidentity) or property value will be returned. This makes it difficult to distinguish a \nsuccessfully matched empty value from no value matched at all. For these cases, it's \npossible to specify an `Empty` value to stand-in for an empty (but successful) matched \nresult instead, which allow to distinguish both scenarios:\n\n```xml\n\u003cJsonPeek Content=\"$(Json)\" Empty=\"$empty\" Query=\"$(Query)\"\u003e\n  \u003cOutput TaskParameter=\"Result\" PropertyName=\"Value\" /\u003e\n\u003c/JsonPeek\u003e\n\n\u003cError Condition=\"'$(Value)' == '$empty'\" Text=\"The element $(Query) cannot have an empty value.\" /\u003e\n```\n\n\u003c!-- #JsonPeek --\u003e\n\n![JsonPoke Icon](assets/img/jsonpoke.png) JsonPoke\n============\n\n[![Version](https://img.shields.io/nuget/vpre/JsonPoke.svg?color=royalblue)](https://www.nuget.org/packages/JsonPoke)\n[![Downloads](https://img.shields.io/nuget/dt/JsonPoke.svg?color=green)](https://www.nuget.org/packages/JsonPoke)\n\n\u003c!-- #JsonPoke --\u003e\nWrite values to JSON nodes selected with JSONPath\n\nUsage:\n\n```xml\n  \u003cJsonPoke ContentPath=\"[JSON_FILE]\" Query=\"[JSONPath]\" Value=\"[VALUE]\" /\u003e\n  \u003cJsonPoke ContentPath=\"[JSON_FILE]\" Query=\"[JSONPath]\" RawValue=\"[JSON]\" /\u003e\n  \u003cJsonPoke Content=\"[JSON]\" Query=\"[JSONPath]\" Value=\"[VALUE]\" /\u003e\n```\n\nParameters:\n\n| Parameter   | Description                                                                                                                                           |\n| ----------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |\n| Content     | Optional `string` input/output parameter.\u003cbr/\u003eSpecifies the JSON input as a string and contains the updated\u003cbr/\u003eJSON after successful task execution. |\n| ContentPath | Optional `ITaskItem` parameter.\u003cbr/\u003eSpecifies the JSON input as a file path.                                                                          |\n| Query       | Required `string` parameter.\u003cbr/\u003eSpecifies the [JSONPath](https://goessner.net/articles/JsonPath/) expression.                                        |\n| Value       | Optional `ITaskItem[]` parameter.\u003cbr/\u003eSpecifies the value(s) to be inserted into the specified path.                                                  |\n| RawValue    | Optional `string` parameter.\u003cbr/\u003eSpecifies the raw (JSON) value to be inserted into the specified path.                                               |\n\nYou must either provide the path to a JSON file via `ContentPath` or \nraw JSON content via `Content`.\n\nThe `Value` can be an item group, and in that case, it will be inserted into the \nJSON node matching the [JSONPath](https://goessner.net/articles/JsonPath/) expression \n`Query` as an array. `RawValue` can be used to provide \nan entire JSON fragment as a string, with no conversion to an MSBuild item at all.\n\nThe existing JSON node will determine the data type of the value being written, \nso as to preserve the original document. Numbers, booleans and DateTimes are \nproperly parsed before serializing to the node. \n\n```xml\n    \u003cPropertyGroup\u003e\n      \u003cJson\u003e\n{\n  \"http\": {\n    \"host\": \"localhost\",\n    \"port\": 80,\n    \"ssl\": true\n  }\n}\n      \u003c/Json\u003e\n    \u003c/PropertyGroup\u003e\n\n    \u003cJsonPoke Content=\"$(Json)\" Query=\"$.http.host\" Value=\"example.com\"\u003e\n      \u003cOutput TaskParameter=\"Content\" PropertyName=\"Json\" /\u003e\n    \u003c/JsonPoke\u003e\n\n    \u003cJsonPoke Content=\"$(Json)\" Query=\"$.http.port\" Value=\"80\"\u003e\n      \u003cOutput TaskParameter=\"Content\" PropertyName=\"Json\" /\u003e\n    \u003c/JsonPoke\u003e\n\n    \u003cJsonPoke Content=\"$(Json)\" Query=\"$.http.ssl\" Value=\"true\"\u003e\n      \u003cOutput TaskParameter=\"Content\" PropertyName=\"Json\" /\u003e\n    \u003c/JsonPoke\u003e\n\n    \u003cMessage Importance=\"high\" Text=\"$(Json)\" /\u003e\n```\n\nNote how we update multiple values and assign the updated content to the \nsame `$(Json)` property so it can be used in subsequent updates. The last \n`Message` task will render the following JSON:\n\n```JSON\n{\n  \"http\": {\n    \"host\": \"example.com\",\n    \"port\": 80,\n    \"ssl\": true\n  }\n}\n```\n\n\u003e NOTE: The port number was preserved as a number, as is the `ssl` boolean.\n\nTo force a value to be interpreted as a string, you can surround it with double or single quotes.\nFor example, given the following JSON file:\n\n```JSON\n{\n    \"http\": {\n        \"ports\": [\n            \"80\"\n        ]\n    }\n}\n```\n\nWe can replace the `ports` array with string values as follows (without the \nexplicit quotes, the values would be interpreted as numbers otherwise):\n\n```xml\n  \u003cItemGroup\u003e\n    \u003cHttpPort Include=\"'8080'\" /\u003e\n    \u003cHttpPort Include=\"'1080'\" /\u003e\n  \u003c/ItemGroup\u003e\n\n  \u003cJsonPoke ContentPath=\"http.json\" Query=\"$.http.ports\" Value=\"@(HttpPort)\" /\u003e\n```\n\nResult:\n\n```JSON\n{\n    \"http\": {\n        \"ports\": [\n            \"8080\", \n            \"1080\"\n        ]\n    }\n}\n```\n\nIt's also possible to write a complex object based on MSBuild item metadata: \n\n```xml\n   \u003cItemGroup\u003e\n     \u003cHttp Include=\"Value\"\u003e\n       \u003chost\u003elocalhost\u003c/host\u003e\n       \u003cport\u003e80\u003c/port\u003e\n       \u003cssl\u003etrue\u003c/ssl\u003e\n     \u003c/Value\u003e\n   \u003c/ItemGroup\u003e\n\n   \u003cJsonPoke ContentPath=\"http.json\" Query=\"$.http\" Value=\"@(Http)\" Properties=\"host;port;ssl\" /\u003e\n```\n\nResult:\n\n```JSON\n{\n    \"http\": {\n        \"host\": \"localhost\",\n        \"port\": 80,\n        \"ssl\": true\n    }\n}\n```\n\nNote how the native JSON type was automatically inferred, even though everything is \nbasically a string in MSBuild. As noted above, you can surround any of the item metadata \nvalues in double or single quotes to force them to be written as strings instead.\n\nThe task can create entire object hierarchies if any segment of the path expression is \nnot found, which makes it very easy to create complex structures by assigning a single \nvalue. For example, if the `http` section in the examples above didn't exist at all, \nthe following task would add it automatically, prior to assigning the `ssl` property to `true`:\n\n```xml\n\u003cJsonPoke ContentPath=\"http.json\" Query=\"$.http.ssl\" Value=\"true\" /\u003e\n```\n\nThis also works for indexed queries, such as adding launch profile to \n[launchSettings.json](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/environments?view=aspnetcore-6.0#lsj) \nby simply assigning a value:\n\n```xml\n\u003cJsonPoke ContentPath=\"Properties\\launchSettings.json\" Query=\"$.profiles['IIS Express'].commandName\" Value=\"IISExpress\" /\u003e\n```\n\nwhich would create the following entry:\n\n```json\n{\n  \"profiles\": {\n    \"IIS Express\": {\n      \"commandName\": \"IISExpress\",\n    }\n  }\n}\n```\n\nArray index is also supported as part of the query, to modify existing values. If the array is empty \nor non-existent, it's also possible to just use the index `[0]` to denote the new node should be the \nsole element in the new array, like for adding a new watch file value to \n[host.json](https://docs.microsoft.com/en-us/azure/azure-functions/functions-host-json):\n\n```xml\n\u003cJsonPoke ContentPath=\"host.json\" Query=\"$.watchFiles[0]\" Value=\"myFile.txt\" /\u003e\n```\n\nWhich results in:\n\n```json\n{\n  ...\n  \"watchFiles\": [ \"myFile.txt\" ]\n}\n```\n\nIt's quite common to want to add entries to an existing array, usually at the end of the array. The \nJSONPath syntax supports indexes that start from the end of the array (such as `[-1:]`), but if the \narray had any values already, that would match whichever is the last element, meaning in an *update* \nto that element's value. Since we need a different syntax for *inserting* a new node, starting from \nthe end of the list, we leverage the C# syntax `^n` where `n` is the position starting from the end. \nTo add a new element at the end of the list, the index `[^1]` can be used. `^2` means prior to last\nand so on.\n\nFor example, to *add* a new watched file to the array in the example above, we could use:\n\n```xml\n\u003cJsonPoke ContentPath=\"host.json\" Query=\"$.watchFiles[^1]\" Value=\"myOtherFile.txt\" /\u003e\n```\n\nGiven an existing `host.json` file like the one above, we would get a new file added like so:\n\n```json\n{\n  ...\n  \"watchFiles\": [ \"myFile.txt\", \"myOtherFile.txt\" ]\n}\n```\n\nIf the `watchFiles` property didn't exit at all or had no elements, the result would be \nthe same as if we used `[0]`, but this makes the code more flexible if needed.\n\n\nThe modified JSON nodes can be assigned to an item name using the `Result` task property, \nand will contain the item path (matching the `Query` plus the index if multiple nodes were modified) \nas well as the `Value` item metadata containing the raw JSON that was written.\n\n\u003c!-- #JsonPoke --\u003e\n\n# Dogfooding\n\n[![CI Version](https://img.shields.io/endpoint?url=https://shields.kzu.io/vpre/JsonPeek/main\u0026label=nuget.ci\u0026color=brightgreen)](https://pkg.kzu.io/index.json)\n[![Build](https://github.com/devlooped/json/workflows/build/badge.svg?branch=main)](https://github.com/devlooped/json/actions)\n\nWe also produce CI packages from branches and pull requests so you can dogfood builds as quickly as they are produced. \n\nThe CI feed is `https://pkg.kzu.io/index.json`. \n\nThe versioning scheme for packages is:\n\n- PR builds: *42.42.42-pr*`[NUMBER]`\n- Branch builds: *42.42.42-*`[BRANCH]`.`[COMMITS]`\n\n\u003c!-- include https://github.com/devlooped/sponsors/raw/main/footer.md --\u003e\n# Sponsors \n\n\u003c!-- sponsors.md --\u003e\n[![Clarius Org](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/clarius.png \"Clarius Org\")](https://github.com/clarius)\n[![Kirill Osenkov](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/KirillOsenkov.png \"Kirill Osenkov\")](https://github.com/KirillOsenkov)\n[![MFB Technologies, Inc.](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/MFB-Technologies-Inc.png \"MFB Technologies, Inc.\")](https://github.com/MFB-Technologies-Inc)\n[![Stephen Shaw](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/decriptor.png \"Stephen Shaw\")](https://github.com/decriptor)\n[![Torutek](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/torutek-gh.png \"Torutek\")](https://github.com/torutek-gh)\n[![DRIVE.NET, Inc.](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/drivenet.png \"DRIVE.NET, Inc.\")](https://github.com/drivenet)\n[![Ashley Medway](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/AshleyMedway.png \"Ashley Medway\")](https://github.com/AshleyMedway)\n[![Keith Pickford](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/Keflon.png \"Keith Pickford\")](https://github.com/Keflon)\n[![Thomas Bolon](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/tbolon.png \"Thomas Bolon\")](https://github.com/tbolon)\n[![Kori Francis](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/kfrancis.png \"Kori Francis\")](https://github.com/kfrancis)\n[![Toni Wenzel](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/twenzel.png \"Toni Wenzel\")](https://github.com/twenzel)\n[![Giorgi Dalakishvili](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/Giorgi.png \"Giorgi Dalakishvili\")](https://github.com/Giorgi)\n[![Uno Platform](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/unoplatform.png \"Uno Platform\")](https://github.com/unoplatform)\n[![Dan Siegel](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/dansiegel.png \"Dan Siegel\")](https://github.com/dansiegel)\n[![Reuben Swartz](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/rbnswartz.png \"Reuben Swartz\")](https://github.com/rbnswartz)\n[![Jacob Foshee](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/jfoshee.png \"Jacob Foshee\")](https://github.com/jfoshee)\n[![](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/Mrxx99.png \"\")](https://github.com/Mrxx99)\n[![Eric Johnson](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/eajhnsn1.png \"Eric Johnson\")](https://github.com/eajhnsn1)\n[![Ix Technologies B.V.](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/IxTechnologies.png \"Ix Technologies B.V.\")](https://github.com/IxTechnologies)\n[![David JENNI](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/davidjenni.png \"David JENNI\")](https://github.com/davidjenni)\n[![Jonathan ](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/Jonathan-Hickey.png \"Jonathan \")](https://github.com/Jonathan-Hickey)\n[![Oleg Kyrylchuk](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/okyrylchuk.png \"Oleg Kyrylchuk\")](https://github.com/okyrylchuk)\n[![Charley Wu](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/akunzai.png \"Charley Wu\")](https://github.com/akunzai)\n[![Jakob Tikjøb Andersen](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/jakobt.png \"Jakob Tikjøb Andersen\")](https://github.com/jakobt)\n[![Seann Alexander](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/seanalexander.png \"Seann Alexander\")](https://github.com/seanalexander)\n[![Tino Hager](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/tinohager.png \"Tino Hager\")](https://github.com/tinohager)\n[![Mark Seemann](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/ploeh.png \"Mark Seemann\")](https://github.com/ploeh)\n[![Ken Bonny](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/KenBonny.png \"Ken Bonny\")](https://github.com/KenBonny)\n[![Simon Cropp](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/SimonCropp.png \"Simon Cropp\")](https://github.com/SimonCropp)\n[![agileworks-eu](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/agileworks-eu.png \"agileworks-eu\")](https://github.com/agileworks-eu)\n[![sorahex](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/sorahex.png \"sorahex\")](https://github.com/sorahex)\n[![Zheyu Shen](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/arsdragonfly.png \"Zheyu Shen\")](https://github.com/arsdragonfly)\n[![Vezel](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/vezel-dev.png \"Vezel\")](https://github.com/vezel-dev)\n[![ChilliCream](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/ChilliCream.png \"ChilliCream\")](https://github.com/ChilliCream)\n[![4OTC](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/4OTC.png \"4OTC\")](https://github.com/4OTC)\n\n\n\u003c!-- sponsors.md --\u003e\n\n[![Sponsor this project](https://raw.githubusercontent.com/devlooped/sponsors/main/sponsor.png \"Sponsor this project\")](https://github.com/sponsors/devlooped)\n\u0026nbsp;\n\n[Learn more about GitHub Sponsors](https://github.com/sponsors)\n\n\u003c!-- https://github.com/devlooped/sponsors/raw/main/footer.md --\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdevlooped%2Fjson","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdevlooped%2Fjson","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdevlooped%2Fjson/lists"}