{"id":32149288,"url":"https://github.com/oskarek/swift-json-parsing","last_synced_at":"2025-10-21T09:53:09.680Z","repository":{"id":178746948,"uuid":"662303144","full_name":"oskarek/swift-json-parsing","owner":"oskarek","description":"JSON decoding and encoding, using the swift-parsing library","archived":false,"fork":false,"pushed_at":"2025-03-20T15:25:39.000Z","size":103,"stargazers_count":31,"open_issues_count":1,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-10-21T09:53:05.673Z","etag":null,"topics":["decoding","encoding","json","parsing","swift"],"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/oskarek.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,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2023-07-04T20:31:25.000Z","updated_at":"2025-03-09T15:35:59.000Z","dependencies_parsed_at":null,"dependency_job_id":"8f416030-ec45-49a1-b26f-cdc68194c15b","html_url":"https://github.com/oskarek/swift-json-parsing","commit_stats":null,"previous_names":["oskarek/swift-json-parsing"],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/oskarek/swift-json-parsing","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/oskarek%2Fswift-json-parsing","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/oskarek%2Fswift-json-parsing/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/oskarek%2Fswift-json-parsing/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/oskarek%2Fswift-json-parsing/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/oskarek","download_url":"https://codeload.github.com/oskarek/swift-json-parsing/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/oskarek%2Fswift-json-parsing/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":280240305,"owners_count":26296527,"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","status":"online","status_checked_at":"2025-10-21T02:00:06.614Z","response_time":58,"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":["decoding","encoding","json","parsing","swift"],"created_at":"2025-10-21T09:53:04.331Z","updated_at":"2025-10-21T09:53:09.669Z","avatar_url":"https://github.com/oskarek.png","language":"Swift","funding_links":[],"categories":[],"sub_categories":[],"readme":"# JSONParsing\n\n[![CI](https://github.com/oskarek/swift-json-parsing/actions/workflows/ci.yml/badge.svg)](https://github.com/oskarek/swift-json-parsing/actions/workflows/ci.yml)\n[![](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2Foskarek%2Fswift-json-parsing%2Fbadge%3Ftype%3Dswift-versions)](https://swiftpackageindex.com/oskarek/swift-json-parsing)\n[![](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2Foskarek%2Fswift-json-parsing%2Fbadge%3Ftype%3Dplatforms)](https://swiftpackageindex.com/oskarek/swift-json-parsing)\n\nA library for decoding and encoding json, built on top of @pointfreeco's [Parsing](https://github.com/pointfreeco/swift-parsing) library.\n\n* [Introduction](#introduction)\n* [Quick start](#quick-start)\n* [Motivation - why not use Codable?](#motivation---why-not-use-codable)\n* [The `JSONValue` type](#the-jsonvalue-type)\n* [The JSON parsers](#the-json-parsers)\n    * [Null](#null)\n    * [JSONBoolean](#jsonboolean)\n    * [JSONNumber](#jsonnumber)\n    * [JSONString](#jsonstring)\n    * [JSONArray](#jsonarray)\n    * [JSONObject](#jsonobject)\n    * [Field](#field)\n    * [OptionalField](#optionalfield)\n* [Integration with Codable](#integration-with-codable)\n* [Benchmarks](#benchmarks)\n* [Installation](#installation)\n\n---\n\n## Introduction\n\nAs mentioned above, this library is built using the [Parsing](https://github.com/pointfreeco/swift-parsing) library, which is a library that provides a consistent story for writing _parsing_ code in Swift, that is, code that turns some _unstructured data_ into more _structured data_. You do that by constructing _parsers_ that are generic over both the (unstructured) _input_ and the (structued) _output_. What's really great is the fact the these parsers can be made _invertible_ (or bidirectional), meaning they can also turn structured data _back_ into unstructured data, referred to as _printing_.\n\nThe *JSONParsing* library provides predefined parsers tuned specifically for when the _input is json_, giving you a convenient way of writing parsers capable of parsing (decoding) and printing (encoding) json. This style of dealing with json has a number of benefits compared to the *Codable* abstraction. More about that in the [Motivation](#motivation---why-not-use-codable) section.\n\n## Quick start\n\nLet's see what it looks like to decode and encode json data using this library. Imagine, for example, you have json describing a movie:\n\n```swift\nlet json = \"\"\"\n{\n  \"title\": \"Interstellar\",\n  \"release_year\": 2014,\n  \"director\": \"Christopher Nolan\",\n  \"stars\": [\n    \"Matthew McConaughey\",\n    \"Anne Hathaway\",\n    \"Jessica Chastain\"\n  ],\n  \"poster_url\": \"https://www.themoviedb.org/t/p/w1280/gEU2QniE6E77NI6lCU6MxlNBvIx.jpg\",\n  \"added_to_favorites\": true\n}\n\"\"\".data(using: .utf8)!\n```\n\nFirst, we define a corresponding `Movie` type:\n\n```swift\nstruct Movie {\n  let title: String\n  let releaseYear: Int\n  let director: String\n  let stars: [String]\n  let posterUrl: URL?\n  let addedToFavorites: Bool\n}\n```\n\nThen, we can create a _JSON parser_, to handle the decoding of the json into this new data type:\n\n```swift\nextension Movie {\n  static var jsonParser: some JSONParserPrinter\u003cSelf\u003e {\n    ParsePrint(.memberwise(Self.init)) {\n      Field(\"title\") { String.jsonParser() }\n      Field(\"release_year\") { Int.jsonParser() }\n      Field(\"director\") { String.jsonParser() }\n      Field(\"stars\") {\n        JSONArray { String.jsonParser() }\n      }\n      OptionalField(\"poster_url\") { URL.jsonParser() }\n      Field(\"added_to_favorites\") { Bool.jsonParser() }\n    }\n  }\n}\n```\n\nNow, the `Movie.jsonParser` can be used to decode json data into `Movie` instances:\n\n```swift\nlet decodedMovie = try Movie.jsonParser.decode(json)\nprint(decodedMovie)\n// Movie(title: \"Interstellar\", releaseYear: 2014, director: \"Christopher Nolan\", stars: [\"Matthew McConaughey\", \"Anne Hathaway\", \"Jessica Chastain\"], posterUrl: Optional(https://www.themoviedb.org/t/p/w1280/gEU2QniE6E77NI6lCU6MxlNBvIx.jpg), addedToFavorites: true)\n```\n\nBut what's even cooler is that the very same parser, without any extra work, can also be used to _encode_ movie values into json:\n\n```swift\nlet jokerMovie = Movie(\n  title: \"Joker\",\n  releaseYear: 2019,\n  director: \"Todd Phillips\",\n  stars: [\"Joaquin Phoenix\", \"Robert De Niro\"],\n  posterUrl: URL(string: \"https://www.themoviedb.org/t/p/w1280/udDclJoHjfjb8Ekgsd4FDteOkCU.jpg\")!,\n  addedToFavorites: true\n)\n\nlet jokerJson = try Movie.jsonParser.encode(jokerMovie)\nprint(String(data: jokerJson, encoding: .utf8)!)\n// {\"added_to_favorites\":true,\"director\":\"Todd Phillips\",\"poster_url\":\"https://www.themoviedb.org/t/p/w1280/udDclJoHjfjb8Ekgsd4FDteOkCU.jpg\",\"release_year\":2019,\"stars\":[\"Joaquin Phoenix\",\"Robert De Niro\"],\"title\":\"Joker\"}\n```\n\nMore information about the building blocks for constructing the JSON parsers can be found under the [The JSON parsers](#the-json-parsers) section.\n\n## Motivation - why not use Codable?\n\nThe default way to work with JSON in Swift is with Apple's own Codable framework. While it is a powerful abstraction, it does have some drawbacks and limitations. Let's explore some of them and see how the JSONParsing library addresses these issues.\n\n### More than one JSON representation\n\nOne limitation of the Codable framework is that any given type can only have _one_ way of being represented as JSON. To work around this limitation, one common approach is to introduce wrapper types that wrap a value of the result type and have a custom Decodable implementation. Then, when decoding the type, you first decode to the wrapper type and then extract the underlying value. While this approach works, it's cumbersome to introduce a new type solely for handling JSON decoding. Moreover, the wrapper type needs to be explicitly used whenever you want to decode to the underlying type with that specific decoding strategy.\n\nAs an example, let's consider the following type representing an RGB color:\n\n```swift\nstruct RGBColor {\n  let red: Int\n  let green: Int\n  let blue: Int\n}\n```\n\nWhat would be the corresponding json representation for this type? Would it be something like:\n\n```json\n{\n  \"red\": 205,\n  \"green\": 99,\n  \"blue\": 138\n}\n```\n\nOr perhaps:\n\n```json\n\"205,99,138\"\n```\n\nThe truth is, both representations are reasonable (as well as many other possibilities), and it's possible that you might have one API endpoint returning RGB colors in the first format, and another in the second format. But when using Codable, you would have to choose one of the formats to be the one used for the `RGBColor` type. To handle both variants, you would have to define two separate types, something like `RGBColorWithObjectRepresentation` and `RGBColorWithStringRepresentation`, and conform both of them to `Codable`, with the different decoding/encoding strategies.\n\nUsing the *JSONParsing* library, you can easily just create two separate parsers, one for each alternative:\n\n```swift\nextension RGBColor {\n  static var jsonParserForObjectRepresentation: some JSONParserPrinter\u003cSelf\u003e {\n    ParsePrint(.memberwise(Self.init)) {\n      Field(\"red\") { Int.jsonParser() }\n      Field(\"green\") { Int.jsonParser() }\n      Field(\"blue\") { Int.jsonParser() }\n    }\n  }\n\n  static var jsonParserForStringRepresentation: some JSONParserPrinter\u003cSelf\u003e {\n    ParsePrint(.memberwise(Self.init)) {\n      JSONString {\n        Int.parser()\n        \",\"\n        Int.parser()\n        \",\"\n        Int.parser()\n      }\n    }\n  }\n}\n```\n\nAnd now you can use whichever suits best in the given situation:\n\n```swift\n// in one place in the app\n\nlet colorJson1 = \"\"\"\n{\n  \"red\": 205,\n  \"green\": 99,\n  \"blue\": 138\n}\n\"\"\".data(using: .utf8)!\n// decode\nlet color1 = try RGBColor.jsonParserForObjectRepresentation.decode(colorJson1)\nprint(color1)\n// RGBColor(red: 205, green: 99, blue: 138)\n\n// encode\nlet newColorJson1 = try RGBColor.jsonParserForObjectRepresentation.encode(color1)\nprint(String(data: newColorJson1, encoding: .utf8)!)\n// {\"blue\":138,\"green\":99,\"red\":205}\n\n// in another place in the app\n\nlet colorJson2 = \"\"\"\n\"55,190,25\"\n\"\"\".data(using: .utf8)!\n// decode\nlet color2 = try RGBColor.jsonParserForStringRepresentation.decode(colorJson2)\nprint(color2)\n// RGBColor(red: 205, green: 99, blue: 138)\n\n// encode\nlet newColorJson2 = try RGBColor.jsonParserForStringRepresentation.encode(color2)\nprint(String(data: newColorJson2, encoding: .utf8)!)\n// \"55,190,25\"\n```\n\nIf you want, you could even define a configurable function, dealing with both variants in the same place:\n\n```swift\nextension RGBColor {\n  static func jsonParser(useStringRepresentation: Bool = false) -\u003e some JSONParserPrinter\u003cSelf\u003e {\n    ParsePrint(.memberwise(Self.init)) {\n      if useStringRepresentation {\n        JSONString {\n          Int.parser()\n          \",\"\n          Int.parser()\n          \",\"\n          Int.parser()\n        }\n      } else {\n        Field(\"red\") { Int.jsonParser() }\n        Field(\"green\") { Int.jsonParser() }\n        Field(\"blue\") { Int.jsonParser() }\n      }\n    }\n  }\n}\n\ntry RGBColor.jsonParser(useStringRepresentation: false).decode(colorJson1)\n// RGBColor(red: 205, green: 99, blue: 138)\ntry RGBColor.jsonParser(useStringRepresentation: true).decode(colorJson2)\n// RGBColor(red: 205, green: 99, blue: 138)\n```\n\n#### The `Date` type\n\nPerhaps the most common way to run into the limitation of a type only being able to have one single `Codable` conformance, is when dealing with the `Date` type. In fact, it's so common, that the Codable framework even provides a special way of managing how `Date` types are decoded/encoded, through the `dateDecodingStrategy`/`dateEncodingStrategy` properties available on `JSONDecoder` and `JSONEncoder`, respectively. While this does work, it's a little weird to have special handling for _one_ specific type, that looks nothing like how you deal with all the other types. Also, having the configuration on the Encoder/Decoder types, means you can't have more than one date format in the same json object.\n\nWith *JSONParsing* on the other hand, the `Date` type doesn't have to be handled as an exception. We saw above with the `RGBColor` type, that we can just create a parser that matches the required representation that is used in the JSON API. The library also extends the `Date` type with a static `jsonParser(formatter:)` method, which allows constructing a json parser that decodes/encodes dates according to a given `DateFormatter`:\n\n```swift\nlet json = \"\"\"\n{\n  \"date1\": \"1998-11-20\",\n  \"date2\": \"2021-06-01T13:09:09Z\"\n}\n\"\"\".data(using: .utf8)!\n\nstruct MyType {\n  let date1: Date\n  let date2: Date\n}\n\nlet basicFormatter = DateFormatter()\nbasicFormatter.dateFormat = \"yyyy-MM-dd\"\n\nlet isoFormatter = DateFormatter()\nisoFormatter.dateFormat = \"yyyy-MM-dd'T'HH':'mm':'ss'Z'\"\n\nextension MyType {\n  static var jsonParser: some JSONParserPrinter\u003cSelf\u003e {\n    ParsePrint(.memberwise(Self.init)) {\n      Field(\"date1\") { Date.jsonParser(formatter: basicFormatter) }\n      Field(\"date2\") { Date.jsonParser(formatter: isoFormatter) }\n    }\n  }\n}\n\nlet parsedValue = try MyType.jsonParser.decode(json)\nprint(parsedValue)\n// MyType(date1: 1998-11-20 00:00:00 +0000, date2: 2021-06-01 13:09:09 +0000)\nlet encodedJson = try MyType.jsonParser.encode(parsedValue)\nprint(String(data: encodedJson, encoding: .utf8)!)\n// {\"date1\":\"1998-11-20\",\"date2\":\"2021-06-01T13:09:09Z\"}\n```\n\n### Decoding and encoding logic out of sync\n\nCodable has the really cool feature of being able to automatically synthesize the decoding and encoding implementations for Swift types, thanks to integration with the Swift compiler. Unfortunately, in practice, the automatically synthesized implementations will often not be correct for your use case, because it assumes that your json data and your Swift data types _exactly_ match each other in structure. This will often not be the case, for various reasons. First, you might be dealing with JSON APIs that you don't own yourself and therefore might deliver data in a format that isn't ideal to your use case. But even if you do own the API code, it might be used by multiple platforms, which means you can't tailor it specifically to work perfectly with your Swift code. Also, Swift has some features, such as enums, that simply _can't_ be expressed equivalently in json.\n\nSo in practice, when using Codable, you will often have to implement the decoding and encoding logic manually. And the problem in that situation, is that they have to be implemented _separately_. This means that, whenever the expected json format changes in any way, you have to remember to update both the `init(from:)` (decoding) and the `encode(to:)` (encoding) implementations accordingly.\n\nWith *JSONParsing* on the other hand, you can write a single json parser that can take care of both the decoding and the encoding (as was shown in the [Quick start](#quick-start) section). What this means is that you are guaranteed to always have the two transformations kept in sync as your json API evolves.\n\n### Custom String parsing\n\nRecall how we previously defined a json parser for the `RGBColor` type, where the json representation was a comma separated string. It looked like this:\n\n```swift\nextension RGBColor {\n  static var jsonParserForStringRepresentation: some JSONParserPrinter\u003cSelf\u003e {\n    ParsePrint(.memberwise(Self.init)) {\n      JSONString {\n        Int.parser()\n        \",\"\n        Int.parser()\n        \",\"\n        Int.parser()\n      }\n    }\n  }\n}\n\nlet colorJson = \"\"\"\n\"55,190,25\"\n\"\"\".data(using: .utf8)!\nlet color = try RGBColor.jsonParserForStringRepresentation.decode(colorJson)\nprint(color)\n// RGBColor(red: 55, green: 190, blue: 25)\nlet newColorJson2 = try RGBColor.jsonParserForStringRepresentation.encode(color2)\nprint(String(data: newColorJson2, encoding: .utf8)!)\n// \"55,190,25\"\n```\n\nIn that example, it was used to highlight the fact that we can handle different json representations for the same type. However, it actually also shows off _another_ great thing about the library, which is how its integration with the *Parsing* library makes it very convenient to deal with types whose json representation requires custom String transformations.\n\nLet's try to accomplish the same thing using Codable:\n\n```swift\nextension RGBColor: Decodable {\n  init(from decoder: Decoder) throws {\n    let container = try decoder.singleValueContainer()\n    let stringValue = container.decode(String.self)\n    self.red = ???\n    self.green = ???\n    self.blue = ???\n  }\n}\n```\n\nHow do we get the rgb components from the decoded String? The Codable abstraction doesn't really provide a general answer to this. We could of course use the *Parsing* library here if we want:\n\n```swift\nextension RGBColor: Decodable {\n  init(from decoder: Decoder) throws {\n    let container = try decoder.singleValueContainer()\n    let stringValue = try container.decode(String.self)\n    self = try Parse(Self.init) {\n      Int.parser()\n      \",\"\n      Int.parser()\n      \",\"\n      Int.parser()\n    }\n    .parse(stringValue)\n  }\n}\n```\n\nBut it's not as seamlessly integrated into the rest of the code, as it was in the *JSONParsing* example, forcing us to manually call out to the `parse` method for instance. And also, again, this is only half of the equation, we still have to deal with the encoding, which has to be implemented on its own.\n\n### JSON with alternative representations\n\nImagine that you are working with an api that delivers a list of ingredients in the following format:\n\n```swift\nlet ingredientsJson = \"\"\"\n[\n  {\n    \"name\": \"milk\",\n    \"amount\": {\n      \"value\": 2,\n      \"unit\": \"dl\"\n    }\n  },\n  {\n    \"name\": \"salt\",\n    \"amount\": \"a pinch\"\n  }\n]\n\"\"\".data(using: .utf8)!\n```\n\nAs you can see, the `amount` can _either_ be expressed as a combination of a value and a unit, _or_ a string. In Swift, this is most naturally represented using an enum:\n\n```swift\nstruct Ingredient {\n  enum Amount {\n    case exact(value: Int, unit: String)\n    case freetext(String)\n  }\n\n  let name: String\n  let amount: Amount\n}\n```\n\nIn this situation, we cannot get a suitable synthesized conformance to `Codable` for the `Amount` type, so we have no choice but to implement the methods ourselves. Let's do the `Decodable` conformance:\n\n```swift\nextension Ingredient.Amount: Decodable {\n  enum CodingKeys: CodingKey {\n    case unit\n    case value\n  }\n\n  init(from decoder: Decoder) throws {\n    do {\n      let container = try decoder.singleValueContainer()\n      self = .freetext(try container.decode(String.self))\n    } catch {\n      let container = try decoder.container(keyedBy: CodingKeys.self)\n      let value = try container.decode(Int.self, forKey: .value)\n      let unit = try container.decode(String.self, forKey: .unit)\n      self = .exact(value: value, unit: unit)\n    }\n  }\n}\n```\n\nFor the `Ingredient` type we can just use the automatically synthesized conformance:\n\n```swift\nextension Ingredient: Decodable {}\n```\n\nNow we can use a `JSONDecoder` to decode the `ingredientsJson` into a list of `Ingredient`:\n\n```swift\nlet ingredients = try JSONDecoder().decode([Ingredient].self, from: ingredientsJson)\nprint(ingredients)\n// [Ingredient(name: \"milk\", amount: Ingredient.Amount.exact(value: 2, unit: \"dl\")), Ingredient(name: \"salt\", amount: Ingredient.Amount.freetext(\"a pinch\"))]\n```\n\nSo that works. We did have to create an explicit `CodingKeys` type as well as two separate `containers` for handling the two cases, which is a little bit of extra boilerplate, but it's not too bad. But there is actually a more fundamental problem here. To see that, let's modify the json input like this:\n\n```diff\n[\n  ...\n  {\n    \"name\": \"salt\",\n-   \"amount\": \"a pinch\"\n+   \"amount\": 3\n  }\n]\n\"\"\".data(using: .utf8)!\n```\n\nSo the amount is now just a number, which is not allowed. When we try to decode the list, we get an error:\n\n```swift\ndo {\n  let ingredients = try JSONDecoder().decode([Ingredient].self, from: ingredientsJson)\n} catch {\n  print(error)\n  // typeMismatch(Swift.Dictionary\u003cSwift.String, Any\u003e, Swift.DecodingError.Context(codingPath: [_JSONKey(stringValue: \"Index 1\", intValue: 1), CodingKeys(stringValue: \"amount\", intValue: nil)], debugDescription: \"Expected to decode Dictionary\u003cString, Any\u003e but found a number instead.\", underlyingError: nil))\n}\n```\n\nThe error message isn't very easily readable, but hidden in there is the message: `\"Expected to decode Dictionary\u003cString, Any\u003e but found a number instead.\"`. So judging by this error, it would seem like that the only valid type of value for the `amount` field is a nested json object. But we know that there is actually a second valid option, namely a string. But this information got lost when the error was created, because of our (arbitrary) choice in the `init(from:)` to _first_ try to decode it as a string, and then if that fails, try the other alternative. If we had written it in the other order, our error message would instead have said `\"Expected to decode String but found a number instead.\"`. Either way, we are missing the fact that we have _multiple valid choices_.\n\nSo let's see how the JSONParsing library handles this kind of situation! Instead of conforming the types to `Decodable`, let's write _JSON parsers_ for them.\n\n```swift\nextension Ingredient.Amount {\n  static var jsonParser: some JSONParserPrinter\u003cSelf\u003e {\n    OneOf {\n      ParsePrint(.case(Self.exact)) {\n        Field(\"value\") { Int.jsonParser() }\n        Field(\"unit\") { String.jsonParser() }\n      }\n\n      ParsePrint(.case(Self.freetext)) {\n        String.jsonParser()\n      }\n    }\n  }\n}\n\nextension Ingredient {\n  static var jsonParser: some JSONParserPrinter\u003cSelf\u003e {\n    ParsePrint(.memberwise(Self.init)) {\n      Field(\"name\") { String.jsonParser() }\n      Field(\"amount\") { Amount.jsonParser }\n    }\n  }\n}\n```\n\nWe make use of the `OneOf` parser from the *Parsing* library, which will run a number of parsers until one succeeds, and if no one succeeds their errors are accumulated. Let's try decoding the same json as before, and see what is printed[^1]:\n\n```swift\ndo {\n  let ingredients = try JSONArray { Ingredient.jsonParser }.decode(ingredientsJson)\n} catch {\n  print(error)\n  // At [index 1]/\"amount\":\n  // error: multiple failures occurred\n  //\n  // error: Expected an object (containing the key \"value\"), but found:\n  // 3\n  //\n  // Expected a string, but found:\n  // 3\n}\n```\n\nAs you can see, _both_ possibilities are now mentioned in the printed error message. Also, as a bonus, the error message is _a lot_ easier to read.\n\nThis also serves as a glimpse at what printed errors look like when using this library. They always have basically the same layout as what you see above: a path describing where something went wrong, and then a more detailed description of _what_ went wrong. All in an easily readable format.\n\n[^1]: At the time of writing, this is actually a slight lie. In this exact situation, the first line `At [index 1]/\"amount\":` would in fact be split across two lines reading `At [index 1]:` and `error: At \"amount\":` respectively. This is due to a current limitation preventing the error path to be printed in the ideal way, that will hopefully be fixed in the near future. In many other situations though, the error path will be printed in that nice compact format, so I still wanted to show that version.\n\n### Decoding/encoding logic spread out\n\nAnother thing that I don't think is ideal with the Codable abstraction is that the decoding/encoding logic lives in two separate places. In part, it is implemented in the types when they conform to the two protocols, but then you can _also_ control some of the behavior via properties on the `JSONDecoder`/`JSONEncoder` instance that you use to perform the decoding/encoding. For instance, the `JSONDecoder` type has a `keyDecodingStrategy` property that can be used to control how keys in the json objects are pre-processed during decoding, and a `dateDecodingStrategy` that can be used to control how dates are decoded.\n\nWhat this means is that a type's conformance to `Decodable`/`Encodable` _is not a complete description of how that type is converted to/from json_. To fully control how that happens, you _also_ have to be in control over which `JSONDecoder`/`JSONEncoder` instance that is used.\n\nWhen using *JSONParsing*, on the other hand, any json parser that you create, _exactly_ determines how to transform a type to/from a json representation.\n\n## The `JSONValue` type\n\nSo far we have glossed over a detail of the library, that isn't immediately necessary to know about to start using it, but is useful to know about to understand how things work under the hood. Everywhere when we have created json parsers, we have given it the type of either `some JSONParser\u003cT\u003e` or `some JSONParserPrinter\u003cT\u003e`, and then when using them to decode or encode json data, we have used the `decode(_:)` and `encode(_:)` methods, respectively.\n\nAs it turns out, `JSONParser\u003cT\u003e` and `JSONParserPrinter\u003cT\u003e` are just typealiases for `Parser\u003cJSONValue, T\u003e` and `ParserPrinter\u003cJSONValue, T\u003e`, respectively (`ParserPrinter` means it can both parse (decode) and print (encode), see [the documentation](https://pointfreeco.github.io/swift-parsing/main/documentation/parsing/gettingstarted) for the *Parsing* library for more details).\n\nSo we are actually defining parsers that take as input a type called `JSONValue`. This is a type exposed from this library, and just serves as a very basic typed representation of json, that looks like this:\n\n```swift\npublic enum JSONValue: Equatable {\n  case null\n  case boolean(Bool)\n  case integer(Int)\n  case float(Double)\n  case string(String)\n  case array([JSONValue])\n  case object([String: JSONValue])\n}\n```\n\nSo when we call the `decode(_:)` and `encode(_:)` methods on the parsers, the decoding and encoding happens in two steps: the json data is transformed to/from the `JSONValue` type, and the `JSONValue` type is in turn transformed to/from the result type using the `Parser.parse`/`ParserPrinter.print` methods.\n\nThe primary use case for the `JSONValue` type is just to act as this middle layer, to simplify the implementations of the various json parsers that ship with the library. However, it can actually be useful on its own. For instance, you might have code like this today:\n\n```swift\nlet json: [String: Any] = [\n  \"title\": \"hello\",\n  \"more_info\": [\"a\": 1, \"b\": 2, ...],\n  ...\n]\nlet jsonData = try JSONSerialization.data(withJSONObject: json)\nvar request = URLRequest(url: requestUrl)\nrequest.httpMethod = \"POST\"\nrequest.httpBody = jsonData\n```\n\nWhile that does work, the fact that the `json` has type `[String: Any]` means that it could actually be a dictionary that holds _any_ kind of data. In particular, it could hold data that isn't valid json data, and the compiler won't let you know. For instance, we could add a `Date` in the `title` field, and the compiler will be fine with it, but it will result in a runtime crash:\n\n```swift\nlet json: [String: Any] = [\n  \"title\": Date(),\n  \"more_info\": [\"a\": 1, \"b\": 2, ...],\n  ...\n]\nlet jsonData = try JSONSerialization.data(withJSONObject: json)\n// runtime crash: *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Invalid type in JSON write (__NSTaggedDate)'\n```\n\nBy using the `JSONValue` type instead in this scenario, you can get a compile time guarantee that your json data is valid. And thanks to the fact that `JSONValue` conforms to a number of `ExpressibleBy...` protocols, it can actually be initialized with the _exact_ same syntax as before. So the previous example becomes:\n\n```swift\nlet json: JSONValue = [\n  \"title\": \"hello\",\n  \"more_info\": [\"a\": 1, \"b\": 2, ...],\n  ...\n]\nlet jsonData = try json.toJsonData()\n// ... the rest is the same\n```\n\nIf we now try to replace `\"hello\"` with `Date()` as we did before, this time the compiler won't let us:\n\n```swift\nlet json: [String: Any] = [\n  \"title\": Date(), // compiler error: Cannot convert value of type 'Date' to expected dictionary value type 'JSONValue'\n  \"more_info\": [\"a\": 1, \"b\": 2, ...],\n  ...\n]\n```\n\n## The JSON parsers\n\nThis library ships with a number of json parsers, that can be composed together to deal with more complex json structures. As mentioned in the previous section, they all take values of the custom type `JSONValue` as input, so when using the `parse`/`print` methods, they convert to/from that type.\n\nWhen you want to use them to decode/encode json _data_ (which is likely to be the most common use case) you just use the `decode`/`encode` methods defined on them instead, which does the converting to from data for you.\n\n### Null\n\nThe `Null` parser is used for parsing the special json value `null`. You use it when you need to explicitly make sure that a value is null.\n\n```swift\nlet nullJson: JSONValue = .null\nlet nonNullJson: JSONValue = 5.0\n\ntry Null().parse(nullJson)\n// ()\ntry Null().parse(nonNullJson)\n// throws:\n// Expected a null value, but found:\n// 5.0\n```\n\nWhen used as a printer (encoder), the `Null` parser prints `.null`:\n\n```swift\ntry Null().print(()) // .null\n```\n\n### JSONBoolean\n\nThe `JSONBoolean` parser is used for parsing json booleans. It succeeds only when given either a `false` or `true` json value, and returns the corresponding `Bool` value.\n\n```swift\nlet booleanJson: JSONValue = false\nlet nonBooleanJson: JSONValue = [\n  \"key1\": 1,\n  \"key2\": \"hello\"\n]\n\ntry JSONBoolean().parse(booleanJson)\n// false\ntry JSONBoolean().parse(nonBooleanJson)\n// throws:\n// Expected a boolean, but found:\n// {\n//   \"key1\": 1,\n//   \"key2\": \"hello\"\n// }\n```\n\nAn alternative way of constructing a `JSONBoolean` parser, is via the static `jsonParser()` method on the `Bool` type:\n\n```swift\ntry Bool.jsonParser().parse(booleanJson)\n// false\n```\n\nThe `JSONBoolean` parser can also be used for printing (encoding) back into json:\n\n```swift\ntry Bool.jsonParser().print(true)\n// .boolean(true)\n```\n\n### JSONNumber\n\nThe `JSONNumber` parser is used for parsing json numbers. Notable is the fact that the `JSONValue` type has a separation between _floating point_ numbers, and _integer_ numbers. When using it to parse to a floating point type, the parser takes an optional parameter called `allowInteger`, which controls whether it succeeds on integers as well as floating points. If not specified, that defaults to `true`.\n\n```swift\nlet integerJson: JSONValue = 10 // or .integer(10)\nlet floatJson: JSONValue = 2.4 // or .float(2.4)\nlet nonNumberJson: JSONValue = \"hello\"\n\ntry JSONNumber\u003cInt\u003e().parse(integerJson)\n// 10\ntry JSONNumber\u003cDouble\u003e().parse(floatJson)\n// 2.4\ntry JSONNumber\u003cInt\u003e().parse(floatJson)\n// throws:\n// Expected an integer number, but found:\n// 2.4\ntry JSONNumber\u003cDouble\u003e().parse(integerJson)\n// 10.0\ntry JSONNumber\u003cDouble\u003e(allowInteger: false).parse(integerJson)\n// throws:\n// Expected a floating point number, but found:\n// 10\ntry JSONNumber\u003cDouble\u003e().parse(nonNumberJson)\n// throws:\n// Expected a number, but found:\n// \"hello\"\n```\n\nAlternatively, a `JSONNumber` parser can be constructed via the `jsonParser()` static methods defined on `BinaryInteger` and `BinaryFloatingPoint`:\n\n```swift\ntry Int.jsonParser().parse(integerJson) // 10\ntry Int64.jsonParser().parse(integerJson) // 10\ntry Double.jsonParser().parse(floatJson) // 2.4\ntry CGFloat.jsonParser(allowInteger: false).parse(floatJson) // 2.4\n```\n\nNote: when decoding json _data_, using the `decode` method, a number in the json object is interpreted as a floating point if it has _any_ decimals (including just a `0`).\n\n```swift\nlet json = \"\"\"\n{\n  \"a\": 10,\n  \"b\": 10.5,\n  \"c\": 10.0\n}\n\"\"\".data(using: .utf8)!\n\ntry Field(\"a\") { Int.jsonParser() }.decode(json)\n// 10\ntry Field(\"b\") { Int.jsonParser() }.decode(json)\n// throws:\n// At \"b\":\n// Expected an integer number, but found:\n// 10.5\ntry Field(\"c\") { Int.jsonParser() }.decode(json)\n// throws:\n// At \"c\":\n// Expected an integer number, but found:\n// 10.0\ntry Field(\"b\") { Double.jsonParser() }.decode(json)\n// 10.5\ntry Field(\"c\") { CGFloat.jsonParser() }.decode(json)\n// 10.0\n```\n\nThe `JSONNumber` parser can also be used for printing to json:\n\n```swift\ntry Int.jsonParser().print(25) // .integer(25)\ntry Double.jsonParser().print(1.6) // .float(1.6)\n```\n\n### JSONString\n\nThe `JSONString` parser is used for parsing json strings. And as has been showed in previous sections, it can also be given a string parser, for performing custom parsing of the string value.\n\n```swift\nlet stringJson: JSONValue = \"120,200,43\"\nlet nonStringJson: JSONValue = [1, 2, 3]\n\ntry JSONString().parse(stringJson)\n// \"120,200,43\"\ntry JSONString().parse(nonStringJson)\n// throws:\n// Expected a string, but found:\n// [ 1, 2, 3 ]\ntry JSONString {\n  Int.parser()\n  \",\"\n  Int.parser()\n  \",\"\n  Int.parser()\n}.parse(stringJson)\n// (120, 200, 43)\n\nlet nonMatchingStringJson: JSONValue = \"apple\"\n\ntry JSONString {\n  Int.parser()\n  \",\"\n  Int.parser()\n  \",\"\n  Int.parser()\n}.parse(stringJson)\n// throws:\n// error: unexpected input\n//  --\u003e input:1:1\n// 1 | apple\n//   | ^ expected integer\n```\n\nThere is also a version of the initializer that takes a string _conversion_. A conversion is a concept introduced in the *Parsing* library, and works like a two-way function. The library also exposes a number of predefined conversions, for example the `representing(_:)` conversion, that can be used to convert between `RawRepresentable` types, and their raw values. Using it with the `JSONString` parser looks like this:\n\n```swift\nenum Direction: String {\n  case up, down, left, right\n}\nextension Direction {\n  static let jsonParser = JSONString(.representing(Direction.self))\n}\nlet json: JSONValue = \"left\"\nlet direction = Direction.jsonParser.parse(json)\nprint(direction) // Direction.left\n\ntry Direction.jsonParser.print(direction)\n// .string(\"left\")\n```\n\nWhen you don't need any custom parsing, and just want to parse a json string as it is, you can also choose to define the parser with the static `jsonParser()` method defined on the `String` type:\n\n```swift\nlet json: JSONValue = \"hello\"\ntry String.jsonParser().parse(json)\n// \"hello\"\n```\n\nThe `JSONString` can be used as a printer, to print (decode) to json, as long as the underlying string parser given to it is a printer itself.\n\n```swift\ntry JSONString {\n  Int.parser()\n  \",\"\n  Int.parser()\n  \",\"\n  Int.parser()\n}.print((120, 200, 43))\n// .string(\"120,200,43\")\n```\n\n### JSONArray\n\nThe `JSONArray` parser is used for parsing json arrays. You construct it by providing a parser that should be applied to each element of the array. As a bonus you can also, optionally, specify that the array must be of a certain size, by giving it a range or a single number. It looks like this to use it for parsing json:\n\n```swift\nlet directionArrayJson: JSONValue = [\"left\", \"left\", \"right\", \"up\"]\nlet numberArrayJson: JSONValue = [1, 2, 3]\nlet nonArrayJson: JSONValue = 10.5\n\ntry JSONArray {\n  Direction.jsonParser\n}.parse(directionArrayJson)\n// [Direction.left, Direction.left, Direction.right, Direction.up]\n\ntry JSONArray(1...3) {\n  Direction.jsonParser\n}.parse(directionArrayJson)\n// throws:\n// Expected 1-3 elements in array, but found 4.\n\ntry JSONArray(3) {\n  Direction.jsonParser\n}.parse(directionArrayJson)\n// throws:\n// Expected 3 elements in array, but found 4.\n\ntry JSONArray {\n  Direction.jsonParser\n}.parse(numberArrayJson)\n// throws:\n// At [index 0]:\n// Expected a string, but found:\n// 1\n\ntry JSONArray {\n  Int.jsonParser()\n}.parse(numberArrayJson)\n// [1, 2, 3]\n\ntry JSONArray {\n  Int.jsonParser()\n}.parse(nonArrayJson)\n// throws:\n// Expected an array, but found:\n// 10.5\n```\n\nAnd for printing (which is available whenever the element parser given to it has printing capabilities):\n\n```swift\ntry JSONArray {\n  Direction.jsonParser\n}.print([Direction.right, .left, .down])\n// .array([\"right\", \"left\", \"down\"])\n```\n\n### JSONObject\n\nThe `JSONObject` parser is used to parse a json object into a dictionary. In it's most basic form it takes a single `Value` parser, to be applied to each value in the json object. And the result after parsing will be a `[String: Value.Output]` dictionary, where `Value.Output` is the type returned from the `Value` parser.\n\n```swift\nlet objectJson: JSONValue = .object([\n  \"url1\": \"https://www.example.com/1\",\n  \"url2\": \"https://www.example.com/2\",\n  \"url3\": \"https://www.example.com/3\",\n])\n\nlet dictionary = try JSONObject {\n  URL.jsonParser()\n}.parse(objectJson)\nprint(dictionary)\n// [\"url1\": https://www.example.com/1, \"url3\": https://www.example.com/3, \"url2\": https://www.example.com/2]\n\ntry JSONObject {\n  URL.jsonParser()\n}.print(dictionary)\n// .object([\"url1\": \"https://www.example.com/1\", \"url3\": \"https://www.example.com/3\", \"url2\": \"https://www.example.com/2\"])\n```\n\nBut you can also specify custom parsing of the _keys_ into any `Hashable` type, by adding on a `keys` parser parameter:\n\n```swift\nlet objectJson: JSONValue = [\n  \"key_1\": \"Steve Jobs\",\n  \"key_2\": \"Tim Cook\"\n]\n\nlet dictionary = try JSONObject {\n  String.jsonParser()\n} keys: {\n  \"key_\"\n  Int.parser()\n}.parse(objectJson)\nprint(dictionary)\n// [1: \"Steve Jobs\", 2: \"Tim Cook\"]\n\ntry JSONObject {\n  String.jsonParser()\n} keys: {\n  \"key_\"\n  Int.parser()\n}.print(dictionary)\n// .object([\"key_1\": \"Steve Jobs\", \"key_2\": \"Tim Cook\"])\n```\n\nor by passing a string conversion to the initializer, for example a `representing` conversion to turn the keys into some `RawRepresentable` type:\n\n```swift\nstruct UserID: RawRepresentable, Hashable {\n  var rawValue: String\n}\nlet usersJson: JSONValue = .object([\n  \"abc\": \"user 1\",\n  \"def\": \"user 2\",\n])\nlet dictionary = try JSONObject(keys: .representing(UserID.self)) {\n  String.jsonParser()\n}.parse(usersJson)\nprint(dictionary)\n// [UserID(rawValue: \"abc\"): \"user 1\", UserID(rawValue: \"def\"): \"user 2\"]\n\ntry JSONObject(keys: .representing(UserID.self)) {\n  String.jsonParser()\n}.print(dictionary)\n// .object([\"abc\": \"user 1\", \"def\": \"user 2\"])\n```\n\nAnd just like the `JSONArray` parser, it can be restricted to only accept a certain number of elements (key/value pairs).\n\n```swift\nlet emptyObjectJson: JSONValue = [:]\ntry JSONObject(1...) {\n  URL.jsonParser()\n}.parse(emptyObjectJson)\n// throws: Expected at least 1 key/value pair in object, but found 0.\n\nlet emptyDictionary: [String: URL] = [:]\ntry JSONObject(1...) {\n  URL.jsonParser()\n}.print(emptyDictionary)\n// throws: An JSONObject parser requiring at least 1 key/value pair was given 0 to print.\n```\n\n### Field\n\nThe `Field` parser is used for parsing a single value at a given field. It takes as input a key, as a `String`, and a json parser to be applied to the value found at that key.\n\n```swift\nlet personJson: JSONValue = [\n  \"first_name\": \"Steve\",\n  \"last_name\": \"Jobs\",\n  \"age\": 56,\n]\nlet personJsonWithoutFirstName: JSONValue = [\n  \"last_name\": \"Cook\",\n  \"age\": 62,\n]\n\ntry Field(\"first_name\") {\n  String.jsonParser()\n}.parse(personJson)\n// \"Steve\"\n\ntry Field(\"first_name\") {\n  String.jsonParser()\n}.print(\"Steve\")\n// .object([\"first_name\": \"Steve\"])\n\ntry Field(\"first_name\") {\n  Int.jsonParser()\n}.parse(personJson)\n// throws:\n// At \"first_name\":\n// Expected an integer number, but found:\n// \"Steve\"\n\ntry Field(\"first_name\") {\n  String.jsonParser()\n}.parse(personJsonWithoutFirstName)\n// throws:\n// Key \"first_name\" not present.\n```\n\nMost often, you will probably want to combine multiple `Field` parsers together, to parse to a more complex result type. For the example above, you will likely have a `Person` type that you want to turn the json into. For that, we can make use of the `memberwise` conversion exposed from the *Parsing* library.\n\n```swift\nstruct Person {\n  let firstName: String\n  let lastName: String\n  let age: Int\n}\n\nextension Person {\n  static var jsonParser: some JSONParserPrinter\u003cSelf\u003e {\n    try ParsePrint(.memberwise(Person.init)) {\n      Field(\"first_name\") { String.jsonParser() }\n      Field(\"last_name\") { String.jsonParser() }\n      Field(\"age\") { Int.jsonParser() }\n    }\n  }\n}\n\nlet person = try Person.jsonParser.parse(personJson)\n// Person(firstName: \"Steve\", lastName: \"Jobs\", age: 56)\n\ntry Person.jsonParser.print(person)\n// .object([\"first_name\": \"Steve\", \"last_name\": \"Jobs\", \"age\": 56])\n```\n\n### OptionalField\n\nThe `OptionalField` parser works like the `Field` parser, but it allows for the field to not exist (or be `null`). To see what that is useful for, let's extend the `Person` type with a new field called `salary`:\n\n```diff\nstruct Person {\n  let firstName: String\n  let lastName: String\n  let age: Int\n+ let salary: Double?\n}\n```\n\nThen we can extend the `Person.jsonParser` in the following way:\n\n```diff\ntry ParsePrint(.memberwise(Person.init)) {\n  Field(\"first_name\") { String.jsonParser() }\n  Field(\"last_name\") { String.jsonParser() }\n  Field(\"age\") { Int.jsonParser() }\n+ OptionalField(\"salary\") { Double.jsonParser() }\n}\n```\n\nNow it can handle person json values with or without a salary.\n\n```swift\nlet personJsonWithSalary: JSONValue = [\n  \"first_name\": \"Bob\",\n  \"last_name\": \"Bobson\",\n  \"age\": 50,\n  \"salary\": 12000\n]\nlet personJsonWithoutSalary: JSONValue = [\n  \"first_name\": \"Mark\",\n  \"last_name\": \"Markson\",\n  \"age\": 20\n]\n\nlet person1 = try Person.jsonParser.parse(personJsonWithSalary)\n// Person(firstName: \"Bob\", lastName: \"Bobson\", age: 50, salary: 12000.0)\ntry Person.jsonParser.print(person1)\n// .object([\"first_name\": \"Bob\", \"last_name\": \"Bobson\", \"age\": 50, \"salary\": 12000.0])\n\nlet person2 = try Person.jsonParser.parse(personJsonWithoutSalary)\n// Person(firstName: \"Mark\", lastName: \"Markson\", age: 20, salary: nil)\ntry Person.jsonParser.print(person2)\n// .object([\"first_name\": \"Mark\", \"last_name\": \"Markson\", \"age\": 20])\n```\n\nInstead of treating an absent value as `nil`, you can optionally provide a `default` value, to use as a fallback:\n\n```diff\nstruct Person {\n  let firstName: String\n  let lastName: String\n  let age: Int\n- let salary: Double?\n+ let salary: Double\n}\n\nextension Person {\n  static var jsonParser: some JSONParserPrinter\u003cSelf\u003e {\n    try ParsePrint(.memberwise(Person.init)) {\n      Field(\"first_name\") { String.jsonParser() }\n      Field(\"last_name\") { String.jsonParser() }\n      Field(\"age\") { Int.jsonParser() }\n-     OptionalField(\"salary\") { Double.jsonParser() }\n+     OptionalField(\"salary\", default: 0) { Double.jsonParser() }\n    }\n  }\n}\n```\n\nNow, parsing a person json without a salary, will use the default value of `0`:\n\n```swift\nlet person = try Person.jsonParser.parse(personJsonWithoutSalary)\n// Person(firstName: \"Mark\", lastName: \"Markson\", age: 20, salary: 0)\ntry Person.jsonParser.print(person)\n// .object([\"first_name\": \"Mark\", \"last_name\": \"Markson\", \"age\": 20])\n```\n\n## Integration with Codable\n\nWhile this library is intended to be able to stand on its own as a fully featured alternative to Codable, it does come with tools to help bridge these two worlds, allowing them to be mixed together. This is important partly because you may be working with other libraries that force you to use Codable in some places, and partly because it allows you to transition a code base that uses Codable, one model at a time. Let's take a look at how it works.\n\n### Integrating *Codable* into *JSONParsing* code\n\nImagine that you have the following type:\n\n```swift\nstruct Person {\n  let name: String\n  let age: Int\n  let favoriteMovie: Movie?\n}\n```\n\nwhere the `Movie` type is `Codable`, and you want to create a json parser for `Person`. For situations like this, the library extends all `Decodable` types with a `jsonParser(decoder:)` method, that takes an optional `JSONDecoder` parameter. And if the type also conforms to `Encodable`, the method takes an optional `JSONEncoder` parameter as well. So for our example, we can make use of this in the parse implementation, to deal with the `Movie` type:\n\n```swift\nextension Person {\n  static var jsonParser: some JSONParserPrinter\u003cSelf\u003e {\n    ParsePrint(.memberwise(Self.init)) {\n      Field(\"name\") { String.jsonParser() }\n      Field(\"age\") { Int.jsonParser() }\n      Field(\"favorite_movie\") { Movie.jsonParser() }\n    }\n  }\n}\n```\n\nand if we need to customize the decoding/encoding of the `Movie` type, we can pass a custom decoder and/or encoder like this:\n\n```swift\nlet jsonDecoder: JSONDecoder = ...\nlet jsonEncoder: JSONEncoder = ...\nextension Person {\n  static var jsonParser: some JSONParserPrinter\u003cSelf\u003e {\n    ParsePrint(.memberwise(Self.init)) {\n      ...\n      Field(\"favoriteMovie\") { Movie.jsonParser(decoder: jsonDecoder, encoder: jsonEncoder) }\n    }\n  }\n}\n```\n\n### Integrating *JSONParsing* into *Codable* code\n\nSo that's one part of the equation, when it comes to integration with Codable. But what about the other way around? What if we actually _do_ have a json parser capable of decoding `Movie`s, and we're using Codable for the `Person` type instead. For that use case, the library comes with overloads of the various methods on the decoding/encoding containers, that take a json parser as input. Let's see what it looks like to use this, by conforming the `Person` type to both the `Decodable` and the `Encodable` protocol:\n\n```swift\nextension Person: Decodable {\n  enum CodingKeys: String, CodingKey {\n    case name\n    case age\n    case favoriteMovie = \"favorite_movie\"\n  }\n\n  init(from decoder: Decoder) throws {\n    let container = try decoder.container(keyedBy: CodingKeys.self)\n    self.name = try container.decode(String.self, forKey: .name)\n    self.age = try container.decode(Int.self, forKey: .age)\n    self.favoriteMovie = try container.decodeIfPresent(forKey: .favoriteMovie) {\n      Movie.jsonParser\n    }\n  }\n}\n\nextension Person: Encodable {\n  func encode(to encoder: Encoder) throws {\n    var container = encoder.container(keyedBy: CodingKeys.self)\n    try container.encode(self.name, forKey: .name)\n    try container.encode(self.age, forKey: .age)\n    try container.encodeIfPresent(self.favoriteMovie, forKey: .favoriteMovie) {\n      Movie.jsonParser\n    }\n  }\n}\n```\n\nHere, we make use of the overloads of the `KeyedDecodingContainer.decodeIfPresent`, and `KeyedEncodingContainer.encodeIfPresent` methods, that takes a json parser as input. Apart from taking an extra json parser parameter, the decoding overloads also make the `type` parameter optional, since it can always be inferred anyway. But if you want, you can still explicitly specify them like for the default versions:\n\n```diff\nextension Person: Decodable {\n  ...\n  init(from decoder: Decoder) throws {\n    ...\n-   self.favoriteMovie = try container.decodeIfPresent(forKey: .favoriteMovie) {\n+   self.favoriteMovie = try container.decodeIfPresent(Movie.self, forKey: .favoriteMovie) {\n      Movie.jsonParser\n    }\n  }\n}\n```\n\n## Benchmarks\n\nThis library comes with a few benchmarks, comparing the execution time for decoding and encoding with that of the corresponding Codable implementation.\n\n```text\nMacBook Pro (14-inch, 2021)\nApple M1 Pro (10 cores, 8 performance and 2 efficiency)\n16 GB (LPDDR5)\n\nname                                     time           std        iterations\n-----------------------------------------------------------------------------\nDecoding.JSONDecoder (Codable)            174917.000 ns ±   3.19 %       7610\nDecoding.JSONParser                       169625.000 ns ±   2.20 %       8070\nDecoding.JSONParser (mixed with Codable)  311250.000 ns ±   8.36 %       4467\nDecoding.JSONParser (from JSONValue)       67042.000 ns ±   2.06 %      20820\nEncoding.JSONEncoder (Codable)           1212416.500 ns ±   0.96 %       1144\nEncoding.JSONParser                      2082541.000 ns ±  22.11 %        680\nEncoding.JSONParser (mixed with Codable) 2889500.000 ns ±  23.28 %        465\nEncoding.JSONParser (to JSONValue)        397417.000 ns ±   1.09 %       3499\n```\n\n## Installation\n\nYou can add the library as a dependency using SPM by adding the following to the `Package.swift` file:\n\n```swift\ndependencies: [\n  .package(url: \"https://github.com/oskarek/swift-json-parsing\", from: \"0.2.0\"),\n]\n```\n\nand then in each module that needs access to it:\n\n```swift\n.target(\n  name: \"MyModule\",\n  dependencies: [\n    .product(name: \"JSONParsing\", package: \"swift-json-parsing\"),\n  ]\n),\n```\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%2Foskarek%2Fswift-json-parsing","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Foskarek%2Fswift-json-parsing","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Foskarek%2Fswift-json-parsing/lists"}