{"id":15041019,"url":"https://github.com/fluidsonic/fluid-json","last_synced_at":"2025-04-14T19:34:43.861Z","repository":{"id":57719798,"uuid":"105949984","full_name":"fluidsonic/fluid-json","owner":"fluidsonic","description":"A JSON library written in pure Kotlin","archived":false,"fork":false,"pushed_at":"2023-06-24T03:31:58.000Z","size":1272,"stargazers_count":31,"open_issues_count":9,"forks_count":7,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-03-28T07:51:16.324Z","etag":null,"topics":["annotation-processing","annotations","codec","decoding","encoding","json","json-library","json-types","kotlin","parse"],"latest_commit_sha":null,"homepage":"","language":"Kotlin","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/fluidsonic.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}},"created_at":"2017-10-05T23:21:18.000Z","updated_at":"2024-07-21T19:04:50.000Z","dependencies_parsed_at":"2022-09-13T13:02:09.004Z","dependency_job_id":null,"html_url":"https://github.com/fluidsonic/fluid-json","commit_stats":null,"previous_names":[],"tags_count":37,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fluidsonic%2Ffluid-json","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fluidsonic%2Ffluid-json/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fluidsonic%2Ffluid-json/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fluidsonic%2Ffluid-json/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/fluidsonic","download_url":"https://codeload.github.com/fluidsonic/fluid-json/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248946543,"owners_count":21187517,"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":["annotation-processing","annotations","codec","decoding","encoding","json","json-library","json-types","kotlin","parse"],"created_at":"2024-09-24T20:45:24.631Z","updated_at":"2025-04-14T19:34:43.829Z","avatar_url":"https://github.com/fluidsonic.png","language":"Kotlin","funding_links":[],"categories":[],"sub_categories":[],"readme":"fluid-json\n==========\n\n[![Maven Central](https://img.shields.io/maven-central/v/io.fluidsonic.json/fluid-json-basic?label=Maven%20Central)](https://search.maven.org/search?q=io.fluidsonic.json)\n[![Tests](https://github.com/fluidsonic/fluid-json/workflows/Tests/badge.svg)](https://github.com/fluidsonic/fluid-json/actions?workflow=Tests)\n[![Kotlin](https://img.shields.io/badge/Kotlin-1.8.22-blue.svg)](https://github.com/JetBrains/kotlin/releases/v1.8.22)\n[![#fluid-libraries Slack Channel](https://img.shields.io/badge/slack-%23fluid--libraries-543951.svg)](https://kotlinlang.slack.com/messages/C7UDFSVT2/)\n\nA JSON library written in pure Kotlin.\n\n\n\nTable of Contents\n-----------------\n\n- [Installation](#installation)\n- [Basic Usage](#basic-usage)\n- [Annotation Customization](#annotation-customization)\n- [Examples](#examples)\n- [Manual Coding](#manual-coding)\n- [Error Handling](#error-handling)\n- [Ktor Serialization](#ktor-serialization)\n- [Modules](#modules)\n- [Testing](#testing)\n- [Type Mapping](#type-mapping)\n- [Architecture](#architecture)\n- [Future Planning](#future-planning)\n\nInstallation\n------------\n\n`build.gradle.kts`:\n\n```kotlin\nplugins {\n\tkotlin(\"kapt\")\n}\n\ndependencies {\n\tkapt(\"io.fluidsonic.json:fluid-json-annotation-processor:1.5.0\")\n\timplementation(\"io.fluidsonic.json:fluid-json-coding-jdk8:1.5.0\")\n}\n```\n\nIf you cannot use Java 8, e.g. when supporting Android API 25 or below, replace `fluid-json-coding-jdk8` with `fluid-json-coding`.\n\nIf you're using IntelliJ IDEA (not Android Studio) then you have to manually enable the following project setting in order to use annotation processing directly\nwithin the IDE (this is an [open issue](https://youtrack.jetbrains.com/issue/KT-15040) in IntelliJ IDEA):  \n_Preferences \u003e Build, Execution, Deployment \u003e Build Tools \u003e Gradle \u003e Runner \u003e Delegate IDE build/run actions to gradle_\n\n\n\nBasic Usage\n-----------\n\n`fluid-json` uses `@Json`-annotations for automatically generating codec classes at compile-time which are responsible for decoding and encoding from and to\nJSON.  \nYou can also [create these codecs on your own](#manual-coding) instead of relying on annotation processing.\n\n```kotlin\nimport io.fluidsonic.json.*\n\n@Json\ndata class Event(\n\tval attendees: Collection\u003cAttendee\u003e,\n\tval description: String,\n\tval end: Instant,\n\tval id: Int,\n\tval start: Instant,\n\tval title: String\n)\n\n@Json\ndata class Attendee(\n\tval emailAddress: String,\n\tval firstName: String,\n\tval lastName: String,\n\tval rsvp: RSVP?\n)\n\nenum class RSVP {\n\tnotGoing,\n\tgoing\n}\n```\n\nNext you create a parser and a serializer that make use of the generated codecs:\n\n```kotlin\nimport io.fluidsonic.json.*\n\nfun main() {\n\tval data = Event(\n\t\tattendees = listOf(\n\t\t\tAttendee(emailAddress = \"marc@knaup.io\", firstName = \"Marc\", lastName = \"Knaup\", rsvp = RSVP.going),\n\t\t\tAttendee(emailAddress = \"john@doe.com\", firstName = \"John\", lastName = \"Doe\", rsvp = null)\n\t\t),\n\t\tdescription = \"Discussing the fluid-json library.\",\n\t\tend = Instant.now() + Duration.ofHours(2),\n\t\tid = 1,\n\t\tstart = Instant.now(),\n\t\ttitle = \"fluid-json MeetUp\"\n\t)\n\n\tval serializer = JsonCodingSerializer.builder()\n\t\t.encodingWith(EventJsonCodec, AttendeeJsonCodec)\n\t\t.build()\n\n\tval serialized = serializer.serializeValue(data)\n\tprintln(\"serialized: $serialized\")\n\n\tval parser = JsonCodingParser.builder()\n\t\t.decodingWith(EventJsonCodec, AttendeeJsonCodec)\n\t\t.build()\n\n\tval parsed = parser.parseValueOfType\u003cEvent\u003e(serialized)\n\tprintln(\"parsed: $parsed\")\n}\n```\n\nPrints this:\n\n```\nserialized: {\"attendees\":[{\"emailAddress\":\"marc@knaup.io\",\"firstName\":\"Marc\",\"lastName\":\"Knaup\",\"rsvp\":\"going\"},{\"emailAddress\":\"john@doe.com\",\"firstName\":\"John\",\"lastName\":\"Doe\",\"rsvp\":null}],\"description\":\"Discussing the fluid-json library.\",\"end\":\"2019-03-05T00:45:08.335Z\",\"id\":1,\"start\":\"2019-03-04T22:45:08.339Z\",\"title\":\"fluid-json MeetUp\"}\n\nparsed: Event(attendees=[Attendee(emailAddress=marc@knaup.io, firstName=Marc, lastName=Knaup, rsvp=going), Attendee(emailAddress=john@doe.com, firstName=John, lastName=Doe, rsvp=null)], description=Discussing the fluid-json library., end=2019-03-05T00:45:08.335Z, id=1, start=2019-03-04T22:45:08.339Z, title=fluid-json MeetUp)\n```\n\n(nope, no [pretty serialization](https://github.com/fluidsonic/fluid-json/issues/15) yet)\n\n\n\nAnnotation Customization\n------------------------\n\nIn this section are a few examples on how JSON codec generation can be customized.\n\nThe full documentation on all annotations and properties controlling the JSON codec generation can be found in the\n[KDoc for `@Json`](https://github.com/fluidsonic/fluid-json/blob/master/annotations/sources/Json.kt).\n\n### Collect all generated codecs in one codec provider\n\nAll codecs in your module generated by annotation processing can automatically be added to a single codec provider which makes using these codecs much simpler.\n\n```kotlin\n@Json.CodecProvider\ninterface MyCodecProvider : JsonCodecProvider\u003cJsonCodingContext\u003e\n\nfun main() {\n\tval parser = JsonCodingParser.builder()\n\t\t.decodingWith(JsonCodecProvider.generated(MyCodecProvider::class))\n\t\t.build()\n\t// …\n}\n```\n\n### Customize the generated codec\n\n```kotlin\n@Json(\n\tcodecName = \"MyCoordinateCodec\",            // customize the JsonCodec's name\n\tcodecPackageName = \"some.other.location\",          // customize the JsonCodec's package\n\tcodecVisibility = Json.CodecVisibility.publicRequired  // customize the JsonCodec's visibility\n)\ndata class GeoCoordinate2(\n\tval latitude: Double,\n\tval longitude: Double\n)\n```\n\n### Customize what constructor is used for decoding\n\n```kotlin\n@Json(\n\tdecoding = Json.Decoding.annotatedConstructor  // require one constructor to be annotated explicitly\n)\ndata class GeoCoordinate3(\n\tval altitude: Double,\n\tval latitude: Double,\n\tval longitude: Double\n) {\n\n\t@Json.Constructor\n\tconstructor(latitude: Double, longitude: Double) : this(\n\t\taltitude = -1.0,\n\t\tlatitude = latitude,\n\t\tlongitude = longitude\n\t)\n}\n\n// input:  {\"latitude\":50.051961,\"longitude\":14.431521}\n// output: {\"altitude\":-1.0,\"latitude\":50.051961,\"longitude\":14.431521}\n```\n\n### Customize what properties are used for encoding (opt-in)\n\n```kotlin\n@Json(\n\tencoding = Json.Encoding.annotatedProperties  // only encode properties annotated explicitly\n)\ndata class User(\n\t@Json.Property val id: String,\n\t@Json.Property val name: String,\n\tval passwordHash: String\n)\n\n// input:  {\"id\":1,\"name\":\"Some User\",\"passwordHash\":\"123456\"}\n// output: {\"id\":1,\"name\":\"Some User\"}\n```\n\n### Customize what properties are used for encoding (opt-out)\n\n```kotlin\n@Json\ndata class User(\n\tval id: String,\n\tval name: String,\n\t@Json.Excluded val passwordHash: String\n)\n\n// input:  {\"id\":1,\"name\":\"Some User\",\"passwordHash\":\"123456\"}\n// output: {\"id\":1,\"name\":\"Some User\"}\n```\n\n### Encode extension properties\n\n```kotlin\n@Json\ndata class Person(\n\tval firstName: String,\n\tval lastName: String\n)\n\n@Json.Property\nval Person.name\n\tget() = \"$firstName $lastName\"\n\n// input:  {\"firstName\":\"Marc\",\"lastName\":\"Knaup\"}\n// output: {\"firstName\":\"Marc\",\"lastName\":\"Knaup\",\"name\":\"Marc Knaup\"}\n```\n\n### Customize JSON property names\n\nSome prefer it that way ¯\\\\\\_(ツ)\\_/¯.\n\n```kotlin\n@Json\ndata class Person(\n\t@Json.Property(\"first_name\") val firstName: String,\n\t@Json.Property(\"last_name\") val lastName: String\n)\n\n// input/input: {\"first_name\":\"John\",\"last_name\":\"Doe\"}\n```\n\n### Inline a single value\n\n```kotlin\n@Json(\n\trepresentation = Json.Representation.singleValue  // no need to wrap in a structured JSON object\n)\nclass EmailAddress(val value: String)\n\n// input:  \"e@mail.com\"\n// output: \"e@mail.com\"\n```\n\n### Prevent encoding completely\n\n```kotlin\n@Json(\n\tencoding = Json.Encoding.none,              // prevent encoding altogether\n\trepresentation = Json.Representation.singleValue  // no need to wrap in a structured JSON object\n)\nclass Password(val secret: String)\n\n// input:  \"123456\"\n// output: not possible\n```\n\n### Prevent decoding completely\n\n```kotlin\n@Json(\n\tdecoding = Json.Decoding.none  // prevent decoding altogether\n)\nclass Response\u003cResult\u003e(val result: result)\n\n// input:  not possible\n// output: {\"result\":…}\n```\n\n### Add properties depending on the context\n\n```kotlin\n@Json(\n\tdecoding = Json.Decoding.none,                // prevent decoding altogether\n\tencoding = Json.Encoding.annotatedProperties  // only encode properties annotated explicitly\n)\ndata class User(\n\t@Json.Property val id: String,\n\t@Json.Property val name: String,\n\tval emailAddress: String\n)\n\n\n@Json.CustomProperties  // function will be called during encoding\nfun JsonEncoder\u003cMyContext\u003e.writeCustomProperties(value: User) {\n\tif (context.authenticatedUserId == value.id)\n\t\twriteMapElement(\"emailAddress\", value = value.emailAddress)\n}\n\n\n@Json.CodecProvider\ninterface MyCodecProvider : JsonCodecProvider\u003cMyContext\u003e\n\n\ndata class MyContext(\n\tval authenticatedUserId: String?\n) : JsonCodingContext\n\n\nfun main() {\n\tval serializer = JsonCodingSerializer\n\t\t.builder(MyContext(authenticatedUserId = \"5678\"))\n\t\t.encodingWith(JsonCodecProvider.generated(MyCodecProvider::class))\n\t\t.build()\n\n\tprintln(serializer.serializeValue(listOf(\n\t\tUser(id = \"1234\", name = \"Some Other User\", emailAddress = \"email@hidden.com\"),\n\t\tUser(id = \"5678\", name = \"Authenticated User\", emailAddress = \"own@email.com\")\n\t)))\n}\n\n// input:  not possible\n// output: [{\"id\":\"1234\",\"name\":\"Some Other User\"},{\"id\":\"5678\",\"name\":\"Authenticated User\",\"emailAddress\":\"own@email.com\"}]\n```\n\n### Annotate types without having the source code\n\nIf a type is not part of your module you can still annotate it indirectly in order to automatically generate a codec for it. Note that this currently does not\nwork correctly if the type has internal properties or an internal primary constructor.\n\n```kotlin\n@Json.CodecProvider(\n\texternalTypes = [\n\t\tJson.ExternalType(Triple::class, Json(\n\t\t\tcodecVisibility = Json.CodecVisibility.publicRequired\n\t\t))\n\t]\n)\ninterface MyCodecProvider : JsonCodecProvider\u003cJsonCodingContext\u003e\n```\n\nExamples\n--------\n\nHave a look at the [examples](https://github.com/fluidsonic/fluid-json/tree/master/examples/sources) directory. If you've checked out this project locally then\nyou can run them directly from within [IntelliJ IDEA](https://www.jetbrains.com/idea/).\n\n\n\nManual Coding\n-------------\n\nInstead of using annotations to generate codecs, JSON can be written either directly using low-level APIs or by manually creating codecs to decode and encode\nclasses from and to JSON.\n\n### Simple Parsing\n\n```kotlin\n… = JsonParser.default.parseValue(\"\"\"{ \"hello\": \"world\", \"test\": 123 }\"\"\")\n\n// returns a value like this:\nmapOf(\n\t\"hello\" to \"world\",\n\t\"test\" to 123\n)\n```\n\nYou can also accept a `null` value by using `parseValueOrNull` instead.\n\n[Full example](https://github.com/fluidsonic/fluid-json/blob/master/examples/sources/0010-Parsing.kt)\n\n### Simple Serializing\n\n```kotlin\nJsonSerializer.default.serializeValue(mapOf(\n\t\"hello\" to \"world\",\n\t\"test\" to 123\n))\n\n// returns a string:\n// {\"hello\":\"world\",\"test\":123}\n```\n\n[Full example](https://github.com/fluidsonic/fluid-json/blob/master/examples/sources/0020-Serializing.kt)\n\n### Using Reader and Writer\n\nWhile the examples above parse and return JSON as `String` you can also use `Reader` and `Writer`:\n\n```kotlin\nval reader: Reader = …\n… = JsonParser.default.parseValue(source = reader)\n\nval writer: Writer = …\nJsonSerializer.default.serializeValue(…, destination = writer)\n```\n\nFull example [for Reader](https://github.com/fluidsonic/fluid-json/blob/master/examples/sources/0011-ParsingFromReader.kt)\nand [for Writer](https://github.com/fluidsonic/fluid-json/blob/master/examples/sources/0021-SerializingToWriter.kt)\n\n### Parsing Lists and Maps\n\nYou can also parse lists and maps in a type-safe way directly. Should it not be possible to parse the input as the requested Kotlin type a `JsonException` is\nthrown. Note that this requires the `-coding` library variant.\n\n```kotlin\nval parser = JsonCodingParser.default\n\nparser.parseValueOfType\u003cList\u003c*\u003e\u003e(…)              // returns List\u003c*\u003e\nparser.parseValueOfType\u003cList\u003cString?\u003e\u003e(…)        // returns List\u003cString?\u003e\nparser.parseValueOfType\u003cMap\u003c*, *\u003e\u003e(…)             // returns Map\u003c*,*\u003e\nparser.parseValueOfType\u003cMap\u003cString, String?\u003e\u003e(…)  // returns Map\u003cString,String?\u003e\n```\n\nNote that you can also specify non-nullable `String` instead of nullable `String?`. But due to a limitation of Kotlin and the JVM the resulting list/map can\nalways contain `null` keys and values. This can cause an unexpected `NullPointerException` at runtime if the source data contains `null`s.\n\nFull example [for Lists](https://github.com/fluidsonic/fluid-json/blob/master/examples/sources/0012-ParsingLists.kt)\nand [for Maps](https://github.com/fluidsonic/fluid-json/blob/master/examples/sources/0013-ParsingMaps.kt)\n\n### Streaming Parser\n\n`JsonReader` provides an extensive API for reading JSON values from a `Reader`.\n\n```kotlin\nval input = StringReader(\"\"\"{ \"data\": [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ] }\"\"\")\nJsonReader.build(input).use { reader -\u003e\n\treader.readFromMapByElementValue { key -\u003e\n\t\tprintln(key)\n\n\t\treadFromListByElement {\n\t\t\tprintln(readInt())\n\t\t}\n\t}\n}\n```\n\nFull example\n[using higher-order functions](https://github.com/fluidsonic/fluid-json/blob/master/examples/sources/0014-ParsingAsStream.kt) and\n[using low-level functions](https://github.com/fluidsonic/fluid-json/blob/master/examples/sources/0015-ParsingAsStreamLowLevel.kt)\n\n### Streaming Writer\n\n`JsonWriter` provides an extensive API for writing JSON values to a `Writer`.\n\n```kotlin\nval output = StringWriter()\nJsonWriter.build(output).use { writer -\u003e\n\twriter.writeIntoMap {\n\t\twriteMapElement(\"data\") {\n\t\t\twriteIntoList {\n\t\t\t\tfor (value in 0 .. 10) {\n\t\t\t\t\tjson.writeInt(value)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n```\n\nFull example\n[using higher-order functions](https://github.com/fluidsonic/fluid-json/blob/master/examples/sources/0022-SerializingAsStream.kt) and\n[using low-level functions](https://github.com/fluidsonic/fluid-json/blob/master/examples/sources/0023-SerializingAsStreamLowLevel.kt)\n\n### Type Encoder Codecs\n\nWhile many basic Kotlin types like `String`, `List`, `Map` and `Boolean` are serialized automatically to their respective JSON counterparts you can easily add\nsupport for other types. Just write a codec for the type you'd like to serialize by implementing `JsonEncoderCodec` and pass an instance to the builder of\neither `JsonCodingSerializer` (high-level API) or `JsonEncoder` (streaming API).\n\nCodecs in turn can write other encodable values and `JsonEncoder` will automatically look up the right codec and use it to serialize these values.\n\nIf your codec encounters an inappropriate value which it cannot encode then it will throw a `JsonException` in order to stop the serialization process.\n\nBecause `JsonEncoderCodec` is simply an interface you can use `AbstractJsonEncoderCodec` as base class for your codec which simplifies implementing that\ninterface.\n\n```kotlin\ndata class MyType(…)\n\nobject MyTypeCodec : AbstractJsonEncoderCodec\u003cMyType, JsonCodingContext\u003e() {\n\n\toverride fun JsonEncoder\u003cJsonCodingContext\u003e.encode(value: MyType) {\n\t\t// write JSON for `value` directly using the encoder (the receiver)\n\t}\n}\n```\n\n[Full example](https://github.com/fluidsonic/fluid-json/blob/master/examples/sources/0030-TypeEncoderCodecs.kt)\n\n### Type Decoder Codecs\n\nWhile all JSON types are parsed automatically using appropriate Kotlin counterparts like `String`, `List`, `Map` and `Boolean` you can easily add support for\nother types. Just write a codec for the type you'd like to parse by implementing `JsonDecoderCodec` and pass an instance to the builder of either\n`JsonCodingParser` (high-level API) or `JsonDecoder` (streaming API).\n\nCodecs in turn can read other decodable values and `JsonDecoder` will automatically look up the right codec and use it to parse these values.\n\nIf your codec encounters inappropriate JSON data which it cannot decode then it will throw a `JsonException` in order to stop the parsing process.\n\nBecause `JsonDecoderCodec` is simply an interface you can use `AbstractJsonDecoderCodec` as base class for your codec which simplifies implementing that\ninterface.\n\n```kotlin\ndata class MyType(…)\n\nobject MyTypeCodec : AbstractJsonDecoderCodec\u003cMyType, JsonCodingContext\u003e() {\n\n\toverride fun JsonDecoder\u003cJsonCodingContext\u003e.decode(valueType: JsonCodingType\u003cMyType\u003e): MyType {\n\t\t// read JSON using and create an instance of `MyType` using decoder (the receiver)\n\t}\n}\n```\n\nA `JsonDecoderCodec` can also decode generic types. The instance passed to `JsonCodingType` contains information about generic arguments expected by the call\nwhich caused this codec to be invoked. For `List\u003cSomething\u003e` for example a single generic argument of type `Something` would be reported which allows for\nexample the list codec to serialize the list value's directly as `Something` using the respective codec.\n\n[Full example](https://github.com/fluidsonic/fluid-json/blob/master/examples/sources/0031-TypeDecoderCodecs.kt)\n\n### Type Codecs\n\nIf you want to be able to encode and decode the same type you can implement the interface `JsonCodec` which in turn extends `JsonEncoderCodec` and\n`JsonDecoderCodec`. That way you can reuse the same codec class for both, encoding and decoding.\n\nBecause `JsonCodec` is simply an interface you can use `AbstractJsonCodec` as base class for your codec which simplifies implementing that interface.\n\n[Full example](https://github.com/fluidsonic/fluid-json/blob/master/examples/sources/0032-TypeCodecs.kt)\n\n### Coding and Streaming\n\nYou can use encoding and decoding codecs not just for high-level encoding and decoding using `JsonCodingSerializer` and `JsonCodingParser` but also for\nstreaming-based encoding and decoding using `JsonEncoder` and `JsonDecoder`.\n\n[Full example](https://github.com/fluidsonic/fluid-json/blob/master/examples/sources/0033-CodingAsStream.kt)\n\n### Thread Safety\n\nAll implementations of `JsonParser`, `JsonSerializer`, `JsonCodecProvider` as well as all codecs provided by this library are thread-safe and can be used from\nmultiple threads without synchronization. It's strongly advised, though not required, that custom implementations are also thread-safe by default.\n\nAll other classes and interfaces are not thread-safe and must be used with appropriate synchronization in place. It's recommended however to simply use a\nseparate instance per thread and not share these mutable instances at all.\n\n\n\nError Handling\n--------------\n\nErrors occurring during I/O operations in the underlying `Reader` or `Writer` cause an `IOException`.  \nErrors occurring due to unsupported or mismatching types, malformed JSON or misused API cause a subclass of `JsonException` being thrown.\n\nSince in Kotlin every method can throw any kind of exception it's recommended to simply catch `Exception` when encoding or decoding JSON - unless handling\nerrors explicitly is not needed in your use-case. This is especially important if you parse JSON data from an unsafe source like a public API.\n\n### Default `JsonException` subclasses\n\n| Exception                     | Usage                                                                                                                       \n|-------------------------------|-----------------------------------------------------------------------------------------------------------------------------\n| `JsonException.Parsing`       | Thrown when a `JsonReader` was used improperly, i.e. it's a development error.                                              \n| `JsonException.Serialization` | Thrown when a `JsonWriter` was used improperly, e.g. if it would result in malformed JSON.                                  \n| `JsonException.Schema`        | Thrown when a `JsonReader` or `JsonDecoder` reads data in an unexpected format, i.e. them schema of the JSON data is wrong. \n| `JsonException.Syntax`        | Thrown when a `JsonReader` reads data which is not properly formatted JSON.                                                 \n\nKtor Serialization\n------------------\n\nYou can use this library with [`ContentNegotiation`](https://ktor.io/docs/serialization.html) of Ktor.\n\n`build.gradle.kts`:\n\n```kotlin\ndependencies {\n\timplementation(\"io.fluidsonic.json:fluid-json-ktor-serialization:1.5.0\")\n}\n```\n\nSetting up your `HttpClient`:\n\n```kotlin\nHttpClient {\n\tinstall(ContentNegotiation) {\n\t\tfluidJson(\n\t\t\tparser = JsonCodingParser.builder().decodingWith(…).build(),\n\t\tserializer = JsonCodingSerializer.builder().encodingWith(…).build(),\n\t\t)\n\t}\n}\n```\n\nModules\n-------\n\n| Module                            | Usage                                                                                                                       \n|-----------------------------------|-----------------------------------------------------------------------------------------------------------------------------\n| `fluid-json-annotation-processor` | `@Json`-based `JsonCodec` creation using `kapt`                                                                             \n| `fluid-json-annotations`          | contains `@Json` annotations                                                                                                \n| `fluid-json-basic`                | low-level API with `JsonReader`/`JsonParser` and `JsonWriter`/`JsonSerializer`                                              \n| `fluid-json-coding`               | `JsonCodec`-based parsing and serialization using `JsonDecoder`/`JsonCodingParser` and `JsonEncoder`/`JsonCodingSerializer` \n| `fluid-json-coding-jdk8`          | additional `JsonCodec`s for commonly used Java 8 types on top of `fluid-json-coding`                                        \n| `fluid-json-ktor-serialization`   | plugs in `JsonCodingParser`/`JsonCodingSerializer` to `ktor-serialization` using its `ContentConverter`                     \n\nTesting\n-------\n\nThis library is tested automatically using [extensive](https://github.com/fluidsonic/fluid-json/tree/master/basic/tests/sources)\n[unit](https://github.com/fluidsonic/fluid-json/tree/master/coding/tests/sources)\n[tests](https://github.com/fluidsonic/fluid-json/tree/master/coding-jdk8/tests/sources). Some parser tests are imported directly from\n[JSONTestSuite](https://github.com/nst/JSONTestSuite) (kudos to [Nicolas Seriot](https://github.com/nst) for that suite).\n\nYou can run the tests manually using `Tests` run configuration in IntelliJ IDEA or from the command line by using:\n\n```bash\n./gradlew check\n```\n\nType Mapping\n------------\n\n### Basic Types\n\n#### Encoding\n\nThe default implementations of `JsonWriter` and `JsonSerializer` encode Kotlin types as follows:\n\n| Kotlin          | JSON               | Remarks                                                       \n|-----------------|--------------------|---------------------------------------------------------------\n| `Array\u003c*\u003e`      | `array\u003c*\u003e`         |\n| `Boolean`       | `boolean`          |\n| `BooleanArray`  | `array\u003cboolean\u003e`   |\n| `Byte`          | `number`           |\n| `ByteArray`     | `array\u003cnumber\u003e`    |\n| `Char`          | `string`           |\n| `CharArray`     | `array\u003cstring\u003e`    |\n| `Collection\u003cE\u003e` | `array\u003c*\u003e`         | using decoder/encoder for `E`                                 \n| `Double`        | `number`           | must be finite                                                \n| `DoubleArray`   | `array\u003cnumber\u003e`    |\n| `Float`         | `number`           | must be finite                                                \n| `FloatArray`    | `array\u003cnumber\u003e`    |\n| `Int`           | `number`           |\n| `IntArray`      | `array\u003cnumber\u003e`    |\n| `Iterable\u003cE\u003e`   | `array\u003c*\u003e`         | using decoder/encoder for `E`                                 \n| `List\u003cE\u003e`       | `array\u003c*\u003e`         | using decoder/encoder for `E`                                 \n| `Long`          | `number`           |\n| `LongArray`     | `array\u003cnumber\u003e`    |\n| `Map\u003cK,V\u003e`      | `object\u003cstring,*\u003e` | key must be `String`, using decoders/encoders for `K` and `V` \n| `Number`        | `number`           | unless matched by subclass; encodes as `toDouble()`           \n| `Sequence\u003cE\u003e`   | `array\u003c*\u003e`         | using decoder/encoder for `E`                                 \n| `Set\u003cE\u003e`        | `array\u003c*\u003e`         | using decoder/encoder for `E`                                 \n| `Short`         | `number`           |\n| `ShortArray`    | `array\u003cnumber\u003e`    |\n| `String`        | `string`           |\n| `null`          | `null`             |\n\n#### Decoding\n\nThe default implementations of `JsonReader` and `JsonParser` decode JSON types as follows:\n\n| JSON               | Kotlin          | Remarks                                                                                            \n|--------------------|-----------------|----------------------------------------------------------------------------------------------------\n| `array\u003c*\u003e`         | `List\u003c*\u003e`       |\n| `boolean`          | `Boolean`       |\n| `null`             | `null`          |\n| `number`           | `Int`           | if number doesn't include `.` (decimal separator) or `e` (exponent separator) and fits into `Int`  \n| `number`           | `Long`          | if number doesn't include `.` (decimal separator) or `e` (exponent separator) and fits into `Long` \n| `number`           | `Double`        | otherwise                                                                                          \n| `object\u003cstring,*\u003e` | `Map\u003cString,*\u003e` |\n| `string`           | `String`        |\n\n### Extended Types\n\nThe following classes of the can also be decoded and encoded out of the box.  \nFor types in the `java.time` package the `-coding-jdk8` library variant must be used.\n\n| Kotlin           | JSON                                | Remarks                                                                 \n|------------------|-------------------------------------|-------------------------------------------------------------------------\n| `CharRange`      | `{ \"start\": …, \"endInclusive\": … }` | using `string` value                                                    \n| `ClosedRange\u003cC\u003e` | `{ \"start\": …, \"endInclusive\": … }` | using decoder/encoder for `C`                                           \n| `Enum`           | `string`                            | uses `.toString()` and converts to `lowerCamelCase` (can be configured) \n| `DayOfWeek`      | `string`                            | `\"monday\"`, …, `\"friday\"`                                               \n| `Duration`       | `string`                            | using `.parse()` / `.toString()`                                        \n| `Instant`        | `string`                            | using `.parse()` / `.toString()`                                        \n| `IntRange`       | `{ \"start\": …, \"endInclusive\": … }` | using `number` values                                                   \n| `LocalDate`      | `string`                            | using `.parse()` / `.toString()`                                        \n| `LocalDateTime`  | `string`                            | using `.parse()` / `.toString()`                                        \n| `LocalTime`      | `string`                            | using `.parse()` / `.toString()`                                        \n| `LongRange`      | `{ \"start\": …, \"endInclusive\": … }` | using `number` values                                                   \n| `MonthDay`       | `string`                            | using `.parse()` / `.toString()`                                        \n| `Month`          | `string`                            | `\"january\"`, …, `\"december\"`                                            \n| `OffsetDateTime` | `string`                            | using `.parse()` / `.toString()`                                        \n| `OffsetTime`     | `string`                            | using `.parse()` / `.toString()`                                        \n| `Period`         | `string`                            | using `.parse()` / `.toString()`                                        \n| `Year`           | `int`                               | using `.value`                                                          \n| `YearMonth`      | `string`                            | using `.parse()` / `.toString()`                                        \n| `ZonedDateTime`  | `string`                            | using `.parse()` / `.toString()`                                        \n| `ZoneId`         | `string`                            | using `.of()` / `.id`                                                   \n| `ZoneOffset`     | `string`                            | using `.of()` / `.id`                                                   \n\nArchitecture\n------------\n\n- `JsonReader`/`JsonWriter` are at the lowest level and read/write JSON as a stream of `JsonToken`s:\n    - part of `-basic` library variant\n    - character-level input/output\n    - validation of read/written syntax\n    - one instance per parsing/serializing (maintains state \u0026 holds reference to `Reader`/`Writer`)\n- `JsonParser`/`JsonSerializer` are built on top of `JsonReader`/`JsonWriter` and read/write a complete JSON value at once.\n    - part of `-basic` library variant\n    - completely hides usage of underlying `JsonReader`/`JsonWriter`\n    - encoding is performed using the actual type of values to be encoded\n    - decoding is performed using the type expected by the caller of `JsonParser`'s `parse…` methods and only available for basic types\n    - instance can be reused and creates one `JsonReader`/`JsonWriter` per parsing/serialization invocation\n    - ease of use is important\n- `JsonDecoder`/`JsonEncoder` are built on top of `JsonReader`/`JsonWriter` and decode/encode arbitrary Kotlin types from/to a stream of `JsonToken`s:\n    - part of `-coding` library variant\n    - most read/write operations are forwarded to the underlying `JsonReader`/`JsonWriter`\n    - some read/write operations are intercepted by `JsonEncoder` to encode compatible types using codecs\n    - implementations provided by `JsonDecoderCodec`s and `JsonEncoderCodec`s\n    - inspired by MongoDB's [Codec and CodecRegistry](http://mongodb.github.io/mongo-java-driver/3.9/bson/codecs/)\n    - one instance per parsing/serialization invocation (holds reference to `JsonReader`/`JsonWriter`)\n- `JsonCodingParser`/`JsonCodingSerializer` are built on top of `JsonDecoder`/`JsonEncoder` and read/write a complete JSON value at once.\n    - part of `-coding` library variant\n    - completely hides usage of underlying `JsonDecoder`/`JsonEncoder`\n    - encoding is performed using the actual type of values to be encoded using a matching `JsonEncoderCodec` implementation\n    - decoding is performed using the type expected by the caller of `JsonParser`'s `parse…` methods and a matching `JsonDecoderCodec` implementation\n    - instance can be reused and creates one `JsonDecoder`/`JsonEncoder` per parsing/serialization invocation\n    - ease of use is important\n\nMost public API is provided as `interface`s in order to allow for plugging in custom behavior and to allow easy unit testing of code which produces or consumes\nJSON.\n\nThe default implementations of `JsonDecoder`/`JsonEncoder` use a set of pre-defined codecs in order to support decoding/encoding various basic Kotlin types like\n`String`, `List`, `Map`, `Boolean` and so on. Codecs for classes which are available only since Java 8 are provided by the `-coding-jdk8` library variant.\n\n### Recursive vs. Non-Recursive\n\nWhile codec-based decoding/encoding has to be implemented recursively in order to be efficient and easy to use it's sometimes not desirable to parse/serialize\nJSON recursively. For that reason the default container codecs like `MapJsonCodec` also provide a `nonRecursive` codec. Since they read/write a whole value at\nonce using `JsonReader`'s/`JsonWriter`'s primitive `read*`/`write*` methods they will not use any other codecs and thus don't support encoding or decoding other\nnon-basic types.\n\n`JsonCodingParser.nonRecursive` and `JsonCodingSerializer.nonRecursive` both operate on these codecs and are thus a non-recursive parser/serializer.\n\n### Classes and Interfaces\n\n| Type                       | Description                                                                                                                                                                                                                                                                    \n|----------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------\n| `AbstractJsonCodec`        | Abstract base class which simplifies implementing `JsonCodec`.                                                                                                                                                                                                                 \n| `AbstractJsonDecoderCodec` | Abstract base class which simplifies implementing `JsonDecoderCodec`.                                                                                                                                                                                                          \n| `AbstractJsonEncoderCodec` | Abstract base class which simplifies implementing `JsonEncoderCodec`.                                                                                                                                                                                                          \n| `DefaultJsonCodecs`        | Contains lists of default codecs which can be used when constructing custom `JsonCodecProvider`s.                                                                                                                                                                              \n| `JsonCodec`                | Interface for classes which implement both, `JsonEncoderCodec` and `JsonDecoderCodec`. Also simplifies creating such codecs.                                                                                                                                                   \n| `JsonCodecProvider`        | Interface for classes which when given a `JsonCodingType` (for decoding) or `KClass` (for encoding) return a codec which is able to decode/encode values of that type.                                                                                                         \n| `JsonCodingContext`        | Interface for context types. Instances of context types can be passed to `JsonParser`, `JsonSerializer`, `JsonDecoder` and `JsonEncoder`. They in turn can be used by custom codecs to help decoding/encoding values if needed.                                                \n| `JsonCodingParser`         | Interface for high-level reusable JSON parsers with codec providers and context already configured.                                                                                                                                                                            \n| `JsonCodingSerializer`     | Interface for high-level reusable JSON serializers where codec providers and context are already configured.                                                                                                                                                                   \n| `JsonCodingType`           | Roughly describes a Kotlin type which can be decoded from JSON. It includes relevant generic information which allows decoding for example `List\u003cSomething\u003e` instead of just `List\u003c*\u003e`. Also known as [type token](http://gafter.blogspot.de/2006/12/super-type-tokens.html)). \n| `JsonDecoder`              | Interface which extends `JsonReader` to enable reading values of any Kotlin type from JSON using `JsonCodecProvider`s for type mapping.                                                                                                                                        \n| `JsonDecoderCodec`         | Interface for decoding a value of a specific Kotlin type using a `JsonDecoder`.                                                                                                                                                                                                \n| `JsonEncoder`              | Interface which extends `JsonWriter` to enable writing values of any Kotlin type as JSON using `JsonCodecProvider`s for type mapping.                                                                                                                                          \n| `JsonEncoderCodec`         | Interface for encoding a value of a specific Kotlin type using a `JsonEncoder`.                                                                                                                                                                                                \n| `JsonException`            | Exception base class which is thrown whenever JSON cannot be written or read for non-IO reasons (e.g. malformed JSON, wrong state in reader/writer, missing type mapping).                                                                                                     \n| `JsonParser`               | Interface for high-level reusable JSON parsers which support only basic types.                                                                                                                                                                                                 \n| `JsonReader`               | Interface for low-level JSON parsing on a token-by-token basis.                                                                                                                                                                                                                \n| `JsonSerializer`           | Interface for high-level reusable JSON serializers which support only basic types.                                                                                                                                                                                             \n| `JsonToken`                | Enum containing all types of tokens a `JsonReader` can read.                                                                                                                                                                                                                   \n| `JsonWriter`               | Interface for low-level JSON serialization on a token-by-token basis.                                                                                                                                                                                                          \n| `*Codec`                   | The various codec classes are concrete codecs for common Kotlin types.                                                                                                                                                                                                         \n\nFuture Planning\n---------------\n\nThis is on the backlog for later consideration, in no specific order:\n\n- [Add KDoc to all public API](https://github.com/fluidsonic/fluid-json/issues/28)\n- [Add performance testing](https://github.com/fluidsonic/fluid-json/issues/4)\n- [Add low-level support for `BigDecimal` / `BigInteger`](https://github.com/fluidsonic/fluid-json/issues/18)\n- [Add pretty serialization](https://github.com/fluidsonic/fluid-json/issues/15)\n\nLicense\n-------\n\nApache 2.0\n\n\n--------------------------\n\n[![Awesome Kotlin](https://kotlin.link/awesome-kotlin.svg)](https://github.com/KotlinBy/awesome-kotlin)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffluidsonic%2Ffluid-json","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffluidsonic%2Ffluid-json","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffluidsonic%2Ffluid-json/lists"}