├── .editorconfig
├── .github
└── workflows
│ ├── ci.yaml
│ ├── gradle_task.yml
│ └── release.yaml
├── .gitignore
├── CNAME
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── build-logic
├── build.gradle.kts
├── repositories.gradle.kts
├── settings.gradle.kts
└── src
│ ├── main
│ └── kotlin
│ │ └── ks3
│ │ └── conventions
│ │ ├── Ks3BuildLogicSettings.kt
│ │ ├── api-validation.gradle.kts
│ │ ├── base.gradle.kts
│ │ ├── lang
│ │ ├── accessors.kt
│ │ ├── kotlin-multiplatform-base.gradle.kts
│ │ ├── kotlin-multiplatform-js.gradle.kts
│ │ ├── kotlin-multiplatform-jvm.gradle.kts
│ │ ├── kotlin-multiplatform-native.gradle.kts
│ │ └── kotlinJsExtensions.kt
│ │ └── publishing
│ │ ├── accessors.kt
│ │ └── maven-publish.gradle.kts
│ └── test
│ └── kotlin
│ └── IsReleaseVersionTest.kt
├── build.gradle.kts
├── doc
├── README.md
├── builders.md
├── jdk.md
└── standard.md
├── dokka
└── custom-styles.css
├── gradle.properties
├── gradle
├── kotlin-js-store
│ └── yarn.lock
├── libs.versions.toml
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── ks3-core
├── api
│ ├── ks3-core.api
│ └── ks3-core.klib.api
├── build.gradle.kts
└── src
│ └── commonMain
│ └── kotlin
│ └── io
│ └── ks3
│ └── core
│ ├── ExperimentalKs3.kt
│ └── Ks3Internal.kt
├── ks3-jdk
├── Module.md
├── README.md
├── api
│ ├── ks3-jdk.api
│ └── ks3-jdk.klib.api
├── build.gradle.kts
└── src
│ ├── jvmMain
│ └── kotlin
│ │ └── io
│ │ └── ks3
│ │ └── java
│ │ ├── io
│ │ └── FilePathSerializer.kt
│ │ ├── math
│ │ ├── BigDecimalSerializers.kt
│ │ └── BigIntegerSerializers.kt
│ │ ├── net
│ │ ├── UriSerializer.kt
│ │ └── UrlSerializer.kt
│ │ ├── time
│ │ ├── InstantAsStringSerializer.kt
│ │ ├── LocalDateAsStringSerializer.kt
│ │ ├── LocalDateTimeAsStringSerializer.kt
│ │ ├── LocalTimeAsStringSerializer.kt
│ │ ├── OffsetDateTimeAsStringSerializer.kt
│ │ ├── YearMonthAsStringSerializer.kt
│ │ └── YearSerializers.kt
│ │ ├── typealiases
│ │ └── Typealiases.kt
│ │ └── util
│ │ ├── AtomicSerializers.kt
│ │ ├── CurrencySerializer.kt
│ │ ├── LocaleSerializer.kt
│ │ └── UuidSerializer.kt
│ └── jvmTest
│ └── kotlin
│ └── io
│ └── ks3
│ └── java
│ ├── io
│ └── FileSerializerTests.kt
│ ├── math
│ ├── BigDecimalSerializerTests.kt
│ └── BigIntegerSerializerTests.kt
│ ├── net
│ ├── UriSerializerTests.kt
│ └── UrlSerializerTests.kt
│ ├── time
│ ├── InstantSerializersTests.kt
│ ├── LocalDateAsStringSerializerTests.kt
│ ├── LocalDateTimeAsStringSerializerTests.kt
│ ├── LocalTimeAsStringSerializerTests.kt
│ ├── OffsetDateTimeAsStringSerializerTests.kt
│ ├── YearMonthAsStringSerializerTests.kt
│ └── YearSerializersTests.kt
│ └── util
│ ├── AtomicSerializersTests.kt
│ ├── CurrencySerializerTest.kt
│ ├── LocaleSerializerTests.kt
│ └── UuidSerializerTests.kt
├── ks3-standard
├── Module.md
├── README.md
├── api
│ ├── ks3-standard.api
│ └── ks3-standard.klib.api
├── build.gradle.kts
└── src
│ ├── commonMain
│ └── kotlin
│ │ └── io
│ │ └── ks3
│ │ └── standard
│ │ ├── ByteArrayAsBase64StringSerializer.kt
│ │ ├── DoubleSerializer.kt
│ │ ├── EnumAsOrdinalSerializer.kt
│ │ ├── EnumDecodingException.kt
│ │ ├── IntSerializer.kt
│ │ ├── LenientJsonArraySerializer.kt
│ │ ├── LongSerializer.kt
│ │ ├── ObjectWrappingSerializer.kt
│ │ ├── SortedCollectionSerializer.kt
│ │ └── StringSerializer.kt
│ ├── commonTest
│ └── kotlin
│ │ └── io
│ │ └── ks3
│ │ └── standard
│ │ ├── ByteArrayAsBase64StringSerializerTest.kt
│ │ ├── EnumAsOrdinalSerializerTest.kt
│ │ ├── LenientJsonArraySerializerTest.kt
│ │ └── SortedCollectionSerializerTest.kt
│ ├── jvmMain
│ └── kotlin
│ │ └── io
│ │ └── ks3
│ │ └── standard
│ │ ├── TupleDescriptor.kt
│ │ └── TupleSerializer.kt
│ └── jvmTest
│ └── kotlin
│ └── io
│ └── ks3
│ └── standard
│ └── TupleSerializerTest.kt
├── ks3-test
├── Module.md
├── api
│ ├── ks3-test.api
│ └── ks3-test.klib.api
├── build.gradle.kts
└── src
│ └── commonMain
│ ├── kotlin
│ └── io.ks3.test
│ │ ├── SerializerTestFactory.kt
│ │ └── TestHelpers.kt
│ └── resources
│ └── kotest.properties
├── renovate.json
└── settings.gradle.kts
/.editorconfig:
--------------------------------------------------------------------------------
1 | root=true
2 |
3 | [*]
4 | indent_style=space
5 | indent_size=3
6 | max_line_length=140
7 |
8 | TRIM_TRAILING_WHITESPACE=true
9 | insert_final_newline=true
10 |
11 | end_of_line=lf
12 |
13 | [*.md]
14 | indent_size=2
15 |
16 | [*.{kt,kts}]
17 | ij_kotlin_import_nested_classes=false
18 | ij_kotlin_imports_layout=*,java.**,javax.**,kotlin.**,^
19 | ij_kotlin_name_count_to_use_star_import=2147483647
20 | ij_kotlin_name_count_to_use_star_import_for_members=2147483647
21 | ij_kotlin_allow_trailing_comma = true
22 | ij_kotlin_allow_trailing_comma_on_call_site = true
23 |
24 | # Disabled for now. io.ks3.java.typealias -> io.ks3.java.typealiases slated for 1.0.0
25 | ktlint_standard_package-name = disabled
26 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yaml:
--------------------------------------------------------------------------------
1 | name: build
2 |
3 | on:
4 | pull_request:
5 |
6 | push:
7 | paths-ignore:
8 | - "doc/**"
9 | - "documentation/**"
10 | - "*.yml"
11 | branches:
12 | - "main"
13 | - "release/*"
14 | tags:
15 | - "v**"
16 |
17 | workflow_dispatch:
18 |
19 | release:
20 | types: [ published ]
21 |
22 |
23 | concurrency:
24 | group: "${{ github.workflow }} @ ${{ github.event.pull_request.head.label || github.head_ref || github.ref }}"
25 | cancel-in-progress: true
26 |
27 |
28 | jobs:
29 |
30 | linux-tests:
31 | name: "./gradlew ${{ matrix.target }} @ ${{ matrix.os }}"
32 | strategy:
33 | matrix:
34 | os: [ ubuntu-latest ]
35 | target:
36 | - jvmTest
37 | - jsTest
38 | - linuxX64Test
39 | # - jsLegacyBrowserTest
40 | # - jsLegacyNodeTest
41 | # - jsLegacyTest
42 | # - jsIrBrowserTest
43 | # - jsIrNodeTest
44 | uses: ./.github/workflows/gradle_task.yml
45 | with:
46 | runs-on: ${{ matrix.os }}
47 | gradle-task: ${{ matrix.target }} --stacktrace
48 |
49 |
50 | macos-tests:
51 | name: "./gradlew ${{ matrix.target }} @ ${{ matrix.os }}"
52 | strategy:
53 | matrix:
54 | os: [ macos-latest ]
55 | target:
56 | - apiCheck -Pks3_enableKotlinNative=true -Pks3_enableKotlinJs=true -DenableKlibValidation
57 | - iosSimulatorArm64Test
58 | - iosX64Test
59 | - macosArm64Test
60 | - macosX64Test
61 | - tvosSimulatorArm64Test
62 | - tvosX64Test
63 | - watchosSimulatorArm64Test
64 | - watchosX64Test
65 | uses: ./.github/workflows/gradle_task.yml
66 | with:
67 | runs-on: ${{ matrix.os }}
68 | gradle-task: ${{ matrix.target }} --stacktrace
69 |
70 |
71 | windows-tests:
72 | name: "./gradlew ${{ matrix.target }} @ ${{ matrix.os }}"
73 | strategy:
74 | matrix:
75 | os: [ windows-latest ]
76 | target:
77 | - mingwX64Test
78 | uses: ./.github/workflows/gradle_task.yml
79 | with:
80 | runs-on: ${{ matrix.os }}
81 | gradle-task: ${{ matrix.target }} --stacktrace
82 |
83 |
84 | gradle-check:
85 | name: "./gradlew check @ ${{ matrix.os }}"
86 | needs: [ linux-tests, macos-tests, windows-tests ]
87 | # Run 'check' task for each OS.
88 | # To improve speed, only enable Kotlin/JVM (Kotlin/Native is particularly slow).
89 | # The other targets are tested individually above.
90 | strategy:
91 | matrix:
92 | os: [ ubuntu-latest, macos-latest, windows-latest ]
93 | uses: ./.github/workflows/gradle_task.yml
94 | with:
95 | runs-on: ${{ matrix.os }}
96 | gradle-task: check --stacktrace -Pks3_enableKotlinNative=false -Pks3_enableKotlinJs=false
97 |
98 |
99 | docs:
100 | runs-on: ubuntu-latest
101 | steps:
102 | - name: "Checkout code"
103 | uses: actions/checkout@v4
104 | with:
105 | fetch-depth: 1 # Checkout the code. No need of tags and whole history.
106 |
107 | - name: Setup JDK
108 | uses: actions/setup-java@v4
109 | with:
110 | distribution: 'temurin'
111 | java-version: '11'
112 |
113 | - name: Setup Gradle
114 | uses: gradle/gradle-build-action@v3
115 |
116 | - name: Build docs
117 | run: ./gradlew dokkatooGeneratePublicationHtml --no-configuration-cache --no-build-cache --rerun-tasks
118 |
119 | - name: Deploy docs
120 | uses: peaceiris/actions-gh-pages@v4
121 | if: github.ref == 'refs/heads/main'
122 | with:
123 | github_token: ${{ secrets.GITHUB_TOKEN }}
124 | publish_dir: ./build/dokka/html/
125 |
--------------------------------------------------------------------------------
/.github/workflows/gradle_task.yml:
--------------------------------------------------------------------------------
1 | name: Gradle Task
2 | run-name: "Gradle Task ${{ inputs.gradle-task }} @ ${{ inputs.runs-on }}"
3 |
4 | # Reusable Workflow for running a Gradle task
5 |
6 | on:
7 | workflow_dispatch:
8 |
9 | workflow_call:
10 | inputs:
11 | ref:
12 | description: "The git branch, tag or SHA to checkout"
13 | required: false
14 | type: string
15 | gradle-task:
16 | description: "The Gradle task to run, including any flags"
17 | required: true
18 | type: string
19 | runs-on:
20 | description: "OSes to run the task on"
21 | required: true
22 | type: string
23 |
24 |
25 | concurrency:
26 | # note: the Workflow inputs are also included in the concurrency group
27 | group: "${{ github.workflow }} ${{ join(inputs.*) }} @ ${{ github.event.pull_request.head.label || github.head_ref || github.ref }}"
28 | cancel-in-progress: true
29 |
30 |
31 | permissions:
32 | contents: read
33 |
34 |
35 | jobs:
36 |
37 | run-task:
38 | runs-on: ${{ inputs.runs-on }}
39 | name: "./gradlew ${{ inputs.gradle-task}} @ ${{ inputs.runs-on }}"
40 | timeout-minutes: 60
41 | steps:
42 | - name: Checkout the repo
43 | uses: actions/checkout@v4
44 | with:
45 | ref: ${{inputs.ref }}
46 | fetch-depth: 0
47 |
48 | - name: Calculate version
49 | id: version
50 | uses: Kantis/semantic-version@master
51 | with:
52 | version_format: "${major}.${minor}.${patch}.${increment}-SNAPSHOT"
53 |
54 | - name: Setup JDK
55 | uses: actions/setup-java@v4
56 | with:
57 | distribution: temurin
58 | java-version: 11
59 |
60 | - uses: gradle/actions/setup-gradle@v4
61 |
62 | - id: cache-kotlin-konan
63 | uses: actions/cache@v4
64 | with:
65 | path: ~/.konan
66 | key: ${{ runner.os }}-kotlin-konan
67 |
68 | - id: stable-version
69 | if: steps.version.outputs.is_tagged == 'true'
70 | run: >
71 | echo "version=${{ steps.version.outputs.major }}.${{steps.version.outputs.minor }}.${{steps.version.outputs.patch }}" >> $GITHUB_OUTPUT
72 |
73 | - name: Run ${{ inputs.gradle-task }}
74 | env:
75 | ORG_GRADLE_PROJECT_ks3_enableKotlinJvm: true
76 | ORG_GRADLE_PROJECT_ks3_enableKotlinJs: true
77 | ORG_GRADLE_PROJECT_ks3_enableKotlinNative: true
78 |
79 | ORG_GRADLE_PROJECT_signingKey: ${{ secrets.SIGNING_KEY }}
80 | ORG_GRADLE_PROJECT_signingPassword: ${{ secrets.SIGNING_PASSWORD }}
81 | ORG_GRADLE_PROJECT_ossrhUsername: ${{ secrets.OSSRH_USERNAME }}
82 | ORG_GRADLE_PROJECT_ossrhPassword: ${{ secrets.OSSRH_PASSWORD }}
83 | run: >-
84 | ./gradlew ${{ inputs.gradle-task }} -Pversion="${{ steps.stable-version.outputs.version || steps.version.outputs.version }}"
85 |
86 |
87 | - name: Upload build reports
88 | if: failure()
89 | uses: actions/upload-artifact@v4
90 | with:
91 | name: build-report-${{ runner.os }}-${{ github.action }}
92 | path: "**/build/reports/"
93 | if-no-files-found: ignore
94 |
--------------------------------------------------------------------------------
/.github/workflows/release.yaml:
--------------------------------------------------------------------------------
1 | name: publish
2 |
3 | on:
4 | workflow_dispatch:
5 | inputs:
6 | ref:
7 | description: "The git branch, tag or SHA to checkout"
8 | required: false
9 | default: ''
10 | type: string
11 |
12 | workflow_run:
13 | workflows: [ "build" ]
14 | types:
15 | - completed
16 |
17 |
18 | concurrency:
19 | group: "${{ github.workflow }} @ ${{ github.event.pull_request.head.label || github.head_ref || github.ref }}"
20 | cancel-in-progress: true
21 |
22 |
23 | permissions:
24 | contents: read
25 |
26 |
27 | jobs:
28 |
29 | publish:
30 | # only publish when
31 | # * on the main repo,
32 | # * if triggered by a workflow, the workflow was successful
33 | if: >-
34 | github.repository == 'Kantis/ks3'
35 | &&
36 | (!github.event.workflow_run || github.event.workflow_run.conclusion == 'success')
37 | strategy:
38 | max-parallel: 1 # Sonatype doesn't like parallel uploads, so disable it where possible
39 | fail-fast: false
40 | matrix:
41 | include:
42 | # Publish 'common' components (KotlinMultiplatform,jvm,js) only on Linux, to avoid duplicate publications
43 | - os: ubuntu-latest
44 | args: -P"ks3_enabledPublicationNamePrefixes=KotlinMultiplatform,jvm,js,linux"
45 |
46 | # Windows: MinGW
47 | - os: windows-latest
48 | args: -P"ks3_enabledPublicationNamePrefixes=mingw"
49 |
50 | # Apple: macOS, iOS, tvOS, watchOS
51 | - os: macos-latest
52 | args: -P"ks3_enabledPublicationNamePrefixes=macOS,iOS,tvOS,watchOS"
53 |
54 | uses: ./.github/workflows/gradle_task.yml
55 | secrets: inherit
56 | with:
57 | ref: ${{ inputs.ref }}
58 | gradle-task: publishAllPublicationsToSonatypeReleaseRepository ${{ matrix.args }} --no-parallel --stacktrace --no-configuration-cache
59 | runs-on: ${{ matrix.os }}
60 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | .gradle
3 | .idea
4 | *.iml
5 | build
6 | out
--------------------------------------------------------------------------------
/CNAME:
--------------------------------------------------------------------------------
1 | www.ks3.io
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | In the interest of fostering an open and welcoming environment, we as
6 | contributors and maintainers pledge to making participation in our project and
7 | our community a harassment-free experience for everyone, regardless of age, body
8 | size, disability, ethnicity, sex characteristics, gender identity and expression,
9 | level of experience, education, socio-economic status, nationality, personal
10 | appearance, race, religion, or sexual identity and orientation.
11 |
12 | ## Our Standards
13 |
14 | Examples of behavior that contributes to creating a positive environment
15 | include:
16 |
17 | * Using welcoming and inclusive language
18 | * Being respectful of differing viewpoints and experiences
19 | * Gracefully accepting constructive criticism
20 | * Focusing on what is best for the community
21 | * Showing empathy towards other community members
22 |
23 | Examples of unacceptable behavior by participants include:
24 |
25 | * The use of sexualized language or imagery and unwelcome sexual attention or
26 | advances
27 | * Trolling, insulting/derogatory comments, and personal or political attacks
28 | * Public or private harassment
29 | * Publishing others' private information, such as a physical or electronic
30 | address, without explicit permission
31 | * Other conduct which could reasonably be considered inappropriate in a
32 | professional setting
33 |
34 | ## Our Responsibilities
35 |
36 | Project maintainers are responsible for clarifying the standards of acceptable
37 | behavior and are expected to take appropriate and fair corrective action in
38 | response to any instances of unacceptable behavior.
39 |
40 | Project maintainers have the right and responsibility to remove, edit, or
41 | reject comments, commits, code, wiki edits, issues, and other contributions
42 | that are not aligned to this Code of Conduct, or to ban temporarily or
43 | permanently any contributor for other behaviors that they deem inappropriate,
44 | threatening, offensive, or harmful.
45 |
46 | ## Scope
47 |
48 | This Code of Conduct applies both within project spaces and in public spaces
49 | when an individual is representing the project or its community. Examples of
50 | representing a project or community include using an official project e-mail
51 | address, posting via an official social media account, or acting as an appointed
52 | representative at an online or offline event. Representation of a project may be
53 | further defined and clarified by project maintainers.
54 |
55 | ## Enforcement
56 |
57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
58 | reported by contacting the project team at team@junit.org. All
59 | complaints will be reviewed and investigated and will result in a response that
60 | is deemed necessary and appropriate to the circumstances. The project team is
61 | obligated to maintain confidentiality with regard to the reporter of an incident.
62 | Further details of specific enforcement policies may be posted separately.
63 |
64 | Project maintainers who do not follow or enforce the Code of Conduct in good
65 | faith may face temporary or permanent repercussions as determined by other
66 | members of the project's leadership.
67 |
68 | ## Attribution
69 |
70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
72 |
73 | [homepage]: https://www.contributor-covenant.org
74 |
75 | For answers to common questions about this code of conduct, see
76 | https://www.contributor-covenant.org/faq
77 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | ## Adding features
2 | Please raise an issue to discuss and assess support for new features before starting work on them.
3 |
4 | ## Code style
5 |
6 | This project uses Ktlint to ensure that code is consistently formatted and doesn't change based on automated formatting settings of
7 | individual contributors.
8 |
9 | Please follow [the official recommended Ktlint setup](https://pinterest.github.io/ktlint/latest/install/setup/]) to setup your environment.
10 |
11 | ## API Changes
12 |
13 | This project uses Kotlin Binary Compatibity Validator to ensure that changes to the public API are done in a backwards compatible way.
14 |
15 | Breaking changes are reserved for major version updates, and must be preceded by deprecations.
16 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 |
2 | Apache License
3 | Version 2.0, January 2004
4 | http://www.apache.org/licenses/
5 |
6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7 |
8 | 1. Definitions.
9 |
10 | "License" shall mean the terms and conditions for use, reproduction,
11 | and distribution as defined by Sections 1 through 9 of this document.
12 |
13 | "Licensor" shall mean the copyright owner or entity authorized by
14 | the copyright owner that is granting the License.
15 |
16 | "Legal Entity" shall mean the union of the acting entity and all
17 | other entities that control, are controlled by, or are under common
18 | control with that entity. For the purposes of this definition,
19 | "control" means (i) the power, direct or indirect, to cause the
20 | direction or management of such entity, whether by contract or
21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
22 | outstanding shares, or (iii) beneficial ownership of such entity.
23 |
24 | "You" (or "Your") shall mean an individual or Legal Entity
25 | exercising permissions granted by this License.
26 |
27 | "Source" form shall mean the preferred form for making modifications,
28 | including but not limited to software source code, documentation
29 | source, and configuration files.
30 |
31 | "Object" form shall mean any form resulting from mechanical
32 | transformation or translation of a Source form, including but
33 | not limited to compiled object code, generated documentation,
34 | and conversions to other media types.
35 |
36 | "Work" shall mean the work of authorship, whether in Source or
37 | Object form, made available under the License, as indicated by a
38 | copyright notice that is included in or attached to the work
39 | (an example is provided in the Appendix below).
40 |
41 | "Derivative Works" shall mean any work, whether in Source or Object
42 | form, that is based on (or derived from) the Work and for which the
43 | editorial revisions, annotations, elaborations, or other modifications
44 | represent, as a whole, an original work of authorship. For the purposes
45 | of this License, Derivative Works shall not include works that remain
46 | separable from, or merely link (or bind by name) to the interfaces of,
47 | the Work and Derivative Works thereof.
48 |
49 | "Contribution" shall mean any work of authorship, including
50 | the original version of the Work and any modifications or additions
51 | to that Work or Derivative Works thereof, that is intentionally
52 | submitted to Licensor for inclusion in the Work by the copyright owner
53 | or by an individual or Legal Entity authorized to submit on behalf of
54 | the copyright owner. For the purposes of this definition, "submitted"
55 | means any form of electronic, verbal, or written communication sent
56 | to the Licensor or its representatives, including but not limited to
57 | communication on electronic mailing lists, source code control systems,
58 | and issue tracking systems that are managed by, or on behalf of, the
59 | Licensor for the purpose of discussing and improving the Work, but
60 | excluding communication that is conspicuously marked or otherwise
61 | designated in writing by the copyright owner as "Not a Contribution."
62 |
63 | "Contributor" shall mean Licensor and any individual or Legal Entity
64 | on behalf of whom a Contribution has been received by Licensor and
65 | subsequently incorporated within the Work.
66 |
67 | 2. Grant of Copyright License. Subject to the terms and conditions of
68 | this License, each Contributor hereby grants to You a perpetual,
69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70 | copyright license to reproduce, prepare Derivative Works of,
71 | publicly display, publicly perform, sublicense, and distribute the
72 | Work and such Derivative Works in Source or Object form.
73 |
74 | 3. Grant of Patent License. Subject to the terms and conditions of
75 | this License, each Contributor hereby grants to You a perpetual,
76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77 | (except as stated in this section) patent license to make, have made,
78 | use, offer to sell, sell, import, and otherwise transfer the Work,
79 | where such license applies only to those patent claims licensable
80 | by such Contributor that are necessarily infringed by their
81 | Contribution(s) alone or by combination of their Contribution(s)
82 | with the Work to which such Contribution(s) was submitted. If You
83 | institute patent litigation against any entity (including a
84 | cross-claim or counterclaim in a lawsuit) alleging that the Work
85 | or a Contribution incorporated within the Work constitutes direct
86 | or contributory patent infringement, then any patent licenses
87 | granted to You under this License for that Work shall terminate
88 | as of the date such litigation is filed.
89 |
90 | 4. Redistribution. You may reproduce and distribute copies of the
91 | Work or Derivative Works thereof in any medium, with or without
92 | modifications, and in Source or Object form, provided that You
93 | meet the following conditions:
94 |
95 | (a) You must give any other recipients of the Work or
96 | Derivative Works a copy of this License; and
97 |
98 | (b) You must cause any modified files to carry prominent notices
99 | stating that You changed the files; and
100 |
101 | (c) You must retain, in the Source form of any Derivative Works
102 | that You distribute, all copyright, patent, trademark, and
103 | attribution notices from the Source form of the Work,
104 | excluding those notices that do not pertain to any part of
105 | the Derivative Works; and
106 |
107 | (d) If the Work includes a "NOTICE" text file as part of its
108 | distribution, then any Derivative Works that You distribute must
109 | include a readable copy of the attribution notices contained
110 | within such NOTICE file, excluding those notices that do not
111 | pertain to any part of the Derivative Works, in at least one
112 | of the following places: within a NOTICE text file distributed
113 | as part of the Derivative Works; within the Source form or
114 | documentation, if provided along with the Derivative Works; or,
115 | within a display generated by the Derivative Works, if and
116 | wherever such third-party notices normally appear. The contents
117 | of the NOTICE file are for informational purposes only and
118 | do not modify the License. You may add Your own attribution
119 | notices within Derivative Works that You distribute, alongside
120 | or as an addendum to the NOTICE text from the Work, provided
121 | that such additional attribution notices cannot be construed
122 | as modifying the License.
123 |
124 | You may add Your own copyright statement to Your modifications and
125 | may provide additional or different license terms and conditions
126 | for use, reproduction, or distribution of Your modifications, or
127 | for any such Derivative Works as a whole, provided Your use,
128 | reproduction, and distribution of the Work otherwise complies with
129 | the conditions stated in this License.
130 |
131 | 5. Submission of Contributions. Unless You explicitly state otherwise,
132 | any Contribution intentionally submitted for inclusion in the Work
133 | by You to the Licensor shall be under the terms and conditions of
134 | this License, without any additional terms or conditions.
135 | Notwithstanding the above, nothing herein shall supersede or modify
136 | the terms of any separate license agreement you may have executed
137 | with Licensor regarding such Contributions.
138 |
139 | 6. Trademarks. This License does not grant permission to use the trade
140 | names, trademarks, service marks, or product names of the Licensor,
141 | except as required for reasonable and customary use in describing the
142 | origin of the Work and reproducing the content of the NOTICE file.
143 |
144 | 7. Disclaimer of Warranty. Unless required by applicable law or
145 | agreed to in writing, Licensor provides the Work (and each
146 | Contributor provides its Contributions) on an "AS IS" BASIS,
147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148 | implied, including, without limitation, any warranties or conditions
149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150 | PARTICULAR PURPOSE. You are solely responsible for determining the
151 | appropriateness of using or redistributing the Work and assume any
152 | risks associated with Your exercise of permissions under this License.
153 |
154 | 8. Limitation of Liability. In no event and under no legal theory,
155 | whether in tort (including negligence), contract, or otherwise,
156 | unless required by applicable law (such as deliberate and grossly
157 | negligent acts) or agreed to in writing, shall any Contributor be
158 | liable to You for damages, including any direct, indirect, special,
159 | incidental, or consequential damages of any character arising as a
160 | result of this License or out of the use or inability to use the
161 | Work (including but not limited to damages for loss of goodwill,
162 | work stoppage, computer failure or malfunction, or any and all
163 | other commercial damages or losses), even if such Contributor
164 | has been advised of the possibility of such damages.
165 |
166 | 9. Accepting Warranty or Additional Liability. While redistributing
167 | the Work or Derivative Works thereof, You may choose to offer,
168 | and charge a fee for, acceptance of support, warranty, indemnity,
169 | or other liability obligations and/or rights consistent with this
170 | License. However, in accepting such obligations, You may act only
171 | on Your own behalf and on Your sole responsibility, not on behalf
172 | of any other Contributor, and only if You agree to indemnify,
173 | defend, and hold each Contributor harmless for any liability
174 | incurred by, or claims asserted against, such Contributor by reason
175 | of your accepting any such warranty or additional liability.
176 |
177 | END OF TERMS AND CONDITIONS
178 |
179 | APPENDIX: How to apply the Apache License to your work.
180 |
181 | To apply the Apache License to your work, attach the following
182 | boilerplate notice, with the fields enclosed by brackets "[]"
183 | replaced with your own identifying information. (Don't include
184 | the brackets!) The text should be enclosed in the appropriate
185 | comment syntax for the file format. We also recommend that a
186 | file or class name and description of purpose be included on the
187 | same "printed page" as the copyright notice for easier
188 | identification within third-party archives.
189 |
190 | Copyright [2016] [sksamuel]
191 |
192 | Licensed under the Apache License, Version 2.0 (the "License");
193 | you may not use this file except in compliance with the License.
194 | You may obtain a copy of the License at
195 |
196 | http://www.apache.org/licenses/LICENSE-2.0
197 |
198 | Unless required by applicable law or agreed to in writing, software
199 | distributed under the License is distributed on an "AS IS" BASIS,
200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201 | See the License for the specific language governing permissions and
202 | limitations under the License.
203 |
204 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # KotlinX Serialization Standard Serializers (KS3)
2 | 
3 | 
4 |
5 | This project solves two things:
6 | * Provides [out-of-the-box serializers](https://github.com/Kantis/ks3/blob/main/doc/jdk.md) for a lof of commonly used Java types.
7 | * Provides [utilities](https://github.com/Kantis/ks3/blob/main/doc/builders.md) for more conveniently defining your own serializers by composition.
8 |
9 | This is accomplished without any additional footprint (the project only depends on KotlinX serialization itself).
10 |
11 | ## Contributing
12 | See the contribution guide [here](CONTRIBUTING.md).
13 |
14 | ## Compatibility
15 |
16 | | Ks3 | Kotlin | KotlinX serialization |
17 | |-----|--------|-----------------------|
18 | | 1.0 | 1.9+ | 1.6+ |
19 |
20 | ## Getting started
21 |
22 | Add the dependency. With Gradle:
23 |
24 | ```kotlin
25 | dependencies {
26 | implementation("io.ks3:ks3-jdk:1.0.0")
27 | }
28 | ```
29 |
30 | Maven:
31 |
32 | ```xml
33 |
34 | io.ks3
35 | ks3-jdk-jvm
36 | 1.0.0
37 |
38 | ```
39 |
40 | > Note that when using Maven dependencies must specify the multiplatform variant. For example, append `-jvm` to specify the JVM variant of `ks3-jdk`.
41 |
42 | Now you can start using the provided serializers. There's several possible ways to do this.
43 |
44 | * Using [contextual serialization](https://github.com/Kotlin/kotlinx.serialization/blob/1.4.1-release/docs/serializers.md#contextual-serialization)
45 | * [Passing a serializer manually](https://github.com/Kotlin/kotlinx.serialization/blob/1.4.1-release/docs/serializers.md#passing-a-serializer-manually)
46 | * [Specifying serializer on a property](https://github.com/Kotlin/kotlinx.serialization/blob/1.4.1-release/docs/serializers.md#specifying-serializer-on-a-property)
47 | * [Specifying serializers for a file](https://github.com/Kotlin/kotlinx.serialization/blob/1.4.1-release/docs/serializers.md#specifying-serializers-for-a-file)
48 | * [Specifying serializer using a typealias](https://github.com/Kotlin/kotlinx.serialization/blob/1.4.1-release/docs/serializers.md#specifying-serializer-globally-using-typealias)
49 |
50 |
51 | ### Using `typealias`
52 |
53 | ```kotlin
54 | typealias LocalDateTimeAsString = @Serializable(LocalDateTimeAsStringSerializer::class) LocalDateTime
55 |
56 | data class Appointment(
57 | val datetime: LocalDateTimeAsString
58 | )
59 | ```
60 |
61 | > For details, see the [kotlinx.serialization guide](https://github.com/Kotlin/kotlinx.serialization/blob/1.4.1-release/docs/serializers.md#specifying-serializer-globally-using-typealias)
62 |
63 | This method is most useful when you want to use different serial formats for the same type, or when you can't configure the serializer itself.
64 |
65 | ### Using `@Contextual`
66 |
67 | > See the details in [kotlinx.serialization guide](https://github.com/Kotlin/kotlinx.serialization/blob/1.4.1-release/docs/serializers.md#contextual-serialization)
68 |
69 | ```kotlin
70 | @Serializable
71 | class ProgrammingLanguage(
72 | val name: String,
73 | @Contextual
74 | val stableReleaseDate: Date
75 | )
76 |
77 | private val module = SerializersModule {
78 | contextual(DateAsLongSerializer)
79 | }
80 |
81 | val format = Json { serializersModule = module }
82 | ```
83 |
--------------------------------------------------------------------------------
/build-logic/build.gradle.kts:
--------------------------------------------------------------------------------
1 | import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
2 |
3 | plugins {
4 | `kotlin-dsl`
5 | kotlin("jvm") version embeddedKotlinVersion
6 | }
7 |
8 | dependencies {
9 | implementation(libs.gradlePlugin.kotlin)
10 | implementation(libs.gradlePlugin.kotlinSerialization)
11 | implementation(libs.gradlePlugin.testlogger)
12 | implementation(libs.gradlePlugin.kotest)
13 | implementation(libs.gradlePlugin.dokkatoo)
14 | implementation(libs.gradlePlugin.ktlint)
15 | implementation(libs.gradlePlugin.kotlinBinaryCompatibilityValidator)
16 |
17 | testImplementation(libs.kotest.runnerJunit5)
18 | }
19 |
20 | val buildLogicJvmTarget = "11"
21 |
22 | kotlin {
23 | jvmToolchain {
24 | languageVersion.set(JavaLanguageVersion.of(buildLogicJvmTarget))
25 | }
26 | }
27 |
28 | tasks.withType().configureEach {
29 | kotlinOptions {
30 | jvmTarget = buildLogicJvmTarget
31 | }
32 | }
33 |
34 | tasks.withType().configureEach {
35 | useJUnitPlatform()
36 | }
37 |
38 | kotlinDslPluginOptions {
39 | jvmTarget.set(buildLogicJvmTarget)
40 | }
41 |
--------------------------------------------------------------------------------
/build-logic/repositories.gradle.kts:
--------------------------------------------------------------------------------
1 | // Shared repository config, for use in both build-logic and the root project
2 |
3 |
4 | @Suppress("UnstableApiUsage") // Central declaration of repositories is an incubating feature
5 | dependencyResolutionManagement {
6 |
7 | repositoriesMode.set(RepositoriesMode.PREFER_SETTINGS)
8 |
9 | repositories {
10 | mavenCentral()
11 |
12 | // Declare the Node.js & Yarn download repositories
13 | exclusiveContent {
14 | forRepository {
15 | ivy("https://nodejs.org/dist/") {
16 | name = "Node Distributions at $url"
17 | patternLayout { artifact("v[revision]/[artifact](-v[revision]-[classifier]).[ext]") }
18 | metadataSources { artifact() }
19 | content { includeModule("org.nodejs", "node") }
20 | }
21 | }
22 | filter { includeGroup("org.nodejs") }
23 | }
24 |
25 | exclusiveContent {
26 | forRepository {
27 | ivy("https://github.com/yarnpkg/yarn/releases/download") {
28 | name = "Yarn Distributions at $url"
29 | patternLayout { artifact("v[revision]/[artifact](-v[revision]).[ext]") }
30 | metadataSources { artifact() }
31 | content { includeModule("com.yarnpkg", "yarn") }
32 | }
33 | }
34 | filter { includeGroup("com.yarnpkg") }
35 | }
36 |
37 | // workaround for https://youtrack.jetbrains.com/issue/KT-51379
38 | exclusiveContent {
39 | forRepository {
40 | ivy("https://download.jetbrains.com/kotlin/native/builds") {
41 | name = "Kotlin Native"
42 | patternLayout {
43 |
44 | // example download URLs:
45 | // https://download.jetbrains.com/kotlin/native/builds/releases/1.7.20/linux-x86_64/kotlin-native-prebuilt-linux-x86_64-1.7.20.tar.gz
46 | // https://download.jetbrains.com/kotlin/native/builds/releases/1.7.20/windows-x86_64/kotlin-native-prebuilt-windows-x86_64-1.7.20.zip
47 | // https://download.jetbrains.com/kotlin/native/builds/releases/1.7.20/macos-x86_64/kotlin-native-prebuilt-macos-x86_64-1.7.20.tar.gz
48 | listOf(
49 | "macos-x86_64",
50 | "macos-aarch64",
51 | "osx-x86_64",
52 | "osx-aarch64",
53 | "linux-x86_64",
54 | "windows-x86_64",
55 | ).forEach { os ->
56 | listOf("dev", "releases").forEach { stage ->
57 | artifact("$stage/[revision]/$os/[artifact]-[revision].[ext]")
58 | }
59 | }
60 | }
61 | metadataSources { artifact() }
62 | }
63 | }
64 | filter { includeModuleByRegex(".*", ".*kotlin-native-prebuilt.*") }
65 | }
66 |
67 | sonatypeSnapshots()
68 | google()
69 | gradlePluginPortal() // tvOS builds need to be able to fetch a Kotlin Gradle plugin
70 | }
71 |
72 | pluginManagement {
73 | repositories {
74 | gradlePluginPortal()
75 | mavenCentral()
76 | google()
77 | sonatypeSnapshots()
78 | }
79 | }
80 | }
81 |
82 | fun RepositoryHandler.sonatypeSnapshots() {
83 | maven("https://s01.oss.sonatype.org/content/repositories/snapshots/") {
84 | name = "SonatypeSnapshotS01"
85 | mavenContent { snapshotsOnly() }
86 | }
87 | maven("https://oss.sonatype.org/content/repositories/snapshots/") {
88 | name = "SonatypeSnapshotsOSS"
89 | mavenContent { snapshotsOnly() }
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/build-logic/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | rootProject.name = "build-logic"
2 |
3 | apply(from = "repositories.gradle.kts")
4 |
5 | dependencyResolutionManagement {
6 | @Suppress("UnstableApiUsage")
7 | versionCatalogs {
8 | create("libs") {
9 | from(files("../gradle/libs.versions.toml"))
10 | }
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/build-logic/src/main/kotlin/ks3/conventions/Ks3BuildLogicSettings.kt:
--------------------------------------------------------------------------------
1 | package ks3.conventions
2 |
3 | import org.gradle.api.provider.Provider
4 | import org.gradle.api.provider.ProviderFactory
5 | import javax.inject.Inject
6 |
7 | /**
8 | * Common settings for configuring ks3's build logic.
9 | *
10 | * The settings need to be accessible during configuration, so they should come from Gradle
11 | * properties or environment variables.
12 | */
13 | abstract class Ks3BuildLogicSettings @Inject constructor(
14 | private val providers: ProviderFactory,
15 | ) {
16 |
17 | val kotlinTarget: Provider = ks3Setting("kotlinTarget", "1.8")
18 | val jvmTarget: Provider = ks3Setting("jvmTarget", "11")
19 |
20 | /** Controls whether Kotlin Multiplatform JVM is enabled */
21 | val enableKotlinJvm: Provider = ks3Flag("enableKotlinJvm", true)
22 | /** Controls whether Kotlin Multiplatform JS is enabled */
23 | val enableKotlinJs: Provider = ks3Flag("enableKotlinJs", true)
24 | /** Controls whether Kotlin Multiplatform Native is enabled */
25 | val enableKotlinNative: Provider = ks3Flag("enableKotlinNative", false)
26 |
27 | /**
28 | * Comma separated list of MavenPublication names that will have the publishing task enabled.
29 | * The provided names will be matched ignoring case, and by prefix, so `iOS` will match
30 | * `iosArm64`, `iosX64`, and `iosSimulatorArm64`.
31 | *
32 | * This is used to avoid duplicate publications, which can occur when a Kotlin Multiplatform
33 | * project is published in CI/CD on different host machines (Linux, Windows, and macOS).
34 | *
35 | * For example, by including `jvm` in the values when publishing on Linux, but omitting `jvm` on
36 | * Windows and macOS, this results in any Kotlin/JVM publications only being published once.
37 | */
38 | val enabledPublicationNamePrefixes: Provider> =
39 | ks3Setting("enabledPublicationNamePrefixes", "KotlinMultiplatform,Jvm,Js,iOS,macOS,watchOS,tvOS,mingw")
40 | .map { enabledPlatforms ->
41 | enabledPlatforms
42 | .split(",")
43 | .map { it.trim() }
44 | .filter { it.isNotBlank() }
45 | .toSet()
46 | }
47 |
48 | private fun ks3Setting(name: String, default: String? = null) =
49 | providers.gradleProperty("ks3_$name")
50 | .orElse(providers.provider { default }) // workaround for https://github.com/gradle/gradle/issues/12388
51 |
52 | private fun ks3Flag(name: String, default: Boolean) =
53 | providers.gradleProperty("ks3_$name").map { it.toBoolean() }.orElse(default)
54 |
55 | companion object {
56 | const val EXTENSION_NAME = "ks3Settings"
57 |
58 | /**
59 | * Regex for matching the release version.
60 | *
61 | * If a version does not match this code it should be treated as a SNAPSHOT version.
62 | */
63 | val releaseVersionRegex = Regex("\\d+.\\d+.\\d+")
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/build-logic/src/main/kotlin/ks3/conventions/api-validation.gradle.kts:
--------------------------------------------------------------------------------
1 | package ks3.conventions
2 |
3 | import org.gradle.kotlin.dsl.configure
4 |
5 | plugins {
6 | id("org.jetbrains.kotlinx.binary-compatibility-validator")
7 | }
8 |
9 | apiValidation {
10 | @OptIn(kotlinx.validation.ExperimentalBCVApi::class)
11 | klib {
12 | enabled = System.getProperty("enableKlibValidation") != null
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/build-logic/src/main/kotlin/ks3/conventions/base.gradle.kts:
--------------------------------------------------------------------------------
1 | package ks3.conventions
2 |
3 | plugins {
4 | base
5 | id("com.adarshr.test-logger")
6 | id("dev.adamko.dokkatoo-html")
7 | }
8 |
9 | // common config for all subprojects
10 |
11 | if (project != rootProject) {
12 | project.version = rootProject.version
13 | project.group = rootProject.group
14 | }
15 |
16 | extensions.create(Ks3BuildLogicSettings.EXTENSION_NAME, Ks3BuildLogicSettings::class)
17 |
18 | //testlogger {
19 | // showPassed = false
20 | //}
21 |
22 | tasks.withType().configureEach {
23 | // https://docs.gradle.org/current/userguide/working_with_files.html#sec:reproducible_archives
24 | isPreserveFileTimestamps = false
25 | isReproducibleFileOrder = true
26 | }
27 |
28 |
--------------------------------------------------------------------------------
/build-logic/src/main/kotlin/ks3/conventions/lang/accessors.kt:
--------------------------------------------------------------------------------
1 | package ks3.conventions.lang
2 |
3 | import org.gradle.api.Project
4 | import org.gradle.kotlin.dsl.configure
5 | import org.gradle.kotlin.dsl.getByType
6 | import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
7 |
8 |
9 | // hacks because IntelliJ is refusing to index generated accessors
10 |
11 | internal val Project.kotlin: KotlinMultiplatformExtension get() = extensions.getByType()
12 | internal fun Project.kotlin(configure: KotlinMultiplatformExtension.() -> Unit) = extensions.configure(configure)
13 |
--------------------------------------------------------------------------------
/build-logic/src/main/kotlin/ks3/conventions/lang/kotlin-multiplatform-base.gradle.kts:
--------------------------------------------------------------------------------
1 | package ks3.conventions.lang
2 |
3 | import ks3.conventions.Ks3BuildLogicSettings
4 | import org.gradle.configurationcache.extensions.capitalized
5 | import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType
6 | import org.jetbrains.kotlin.gradle.targets.jvm.KotlinJvmTarget
7 | import org.jetbrains.kotlin.gradle.testing.KotlinTaskTestRun
8 |
9 | plugins {
10 | id("ks3.conventions.base")
11 | kotlin("multiplatform")
12 | kotlin("plugin.serialization")
13 | id("io.kotest.multiplatform")
14 | id("org.jlleitschuh.gradle.ktlint")
15 | }
16 |
17 | // Base configuration for all Kotlin/Multiplatform conventions.
18 | // This plugin does not enable any Kotlin target. To enable a target in a subproject, prefer
19 | // applying specific Kotlin target convention plugins.
20 |
21 | val ks3Settings = extensions.getByType()
22 |
23 | kotlin {
24 | jvmToolchain {
25 | languageVersion.set(JavaLanguageVersion.of(ks3Settings.jvmTarget.get()))
26 | }
27 |
28 | sourceSets {
29 | all {
30 | languageSettings.optIn("io.ks3.core.Ks3Internal")
31 | languageSettings.optIn("kotlin.RequiresOptIn")
32 | }
33 | }
34 |
35 | targets.configureEach {
36 | compilations.configureEach {
37 | kotlinOptions {
38 | apiVersion = ks3Settings.kotlinTarget.get()
39 | languageVersion = ks3Settings.kotlinTarget.get()
40 | }
41 | }
42 | }
43 |
44 | // configure all Kotlin/JVM Tests to use JUnit
45 | targets.withType().configureEach {
46 | testRuns.configureEach {
47 | executionTask.configure {
48 | useJUnitPlatform()
49 | }
50 | }
51 | }
52 |
53 | sourceSets.configureEach {
54 | languageSettings {
55 | languageVersion = ks3Settings.kotlinTarget.get()
56 | apiVersion = ks3Settings.kotlinTarget.get()
57 | optIn("kotlin.RequiresOptIn")
58 | }
59 | }
60 | }
61 |
62 | // create lifecycle task for each Kotlin Platform, that will run all tests
63 | KotlinPlatformType.values().forEach { kotlinPlatform ->
64 | val kotlinPlatformName = kotlinPlatform.name.capitalized()
65 |
66 | val testKotlinTargetLifecycleTask =
67 | tasks.create("allKotlin${kotlinPlatformName}Tests") {
68 | group = LifecycleBasePlugin.VERIFICATION_GROUP
69 | description = "Run all Kotlin/$kotlinPlatformName tests"
70 | }
71 |
72 | kotlin.testableTargets.matching {
73 | it.platformType == kotlinPlatform
74 | }.configureEach {
75 | testRuns.configureEach {
76 | if (this is KotlinTaskTestRun<*, *>) {
77 | testKotlinTargetLifecycleTask.dependsOn(executionTask)
78 | }
79 | }
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/build-logic/src/main/kotlin/ks3/conventions/lang/kotlin-multiplatform-js.gradle.kts:
--------------------------------------------------------------------------------
1 | package ks3.conventions.lang
2 |
3 | import ks3.conventions.Ks3BuildLogicSettings
4 |
5 | plugins {
6 | id("ks3.conventions.lang.kotlin-multiplatform-base")
7 | }
8 |
9 | val ks3Settings = extensions.getByType()
10 |
11 | if (ks3Settings.enableKotlinJs.get()) {
12 | kotlin {
13 | js(IR) {
14 | browser()
15 | nodejs()
16 | }
17 | }
18 |
19 | relocateKotlinJsStore()
20 | }
21 |
--------------------------------------------------------------------------------
/build-logic/src/main/kotlin/ks3/conventions/lang/kotlin-multiplatform-jvm.gradle.kts:
--------------------------------------------------------------------------------
1 | package ks3.conventions.lang
2 |
3 | import ks3.conventions.Ks3BuildLogicSettings
4 |
5 | plugins {
6 | id("ks3.conventions.lang.kotlin-multiplatform-base")
7 | }
8 |
9 | val ks3Settings = extensions.getByType()
10 |
11 | if (ks3Settings.enableKotlinJvm.get()) {
12 | kotlin {
13 | jvm {
14 | withJava()
15 | }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/build-logic/src/main/kotlin/ks3/conventions/lang/kotlin-multiplatform-native.gradle.kts:
--------------------------------------------------------------------------------
1 | package ks3.conventions.lang
2 |
3 | import ks3.conventions.Ks3BuildLogicSettings
4 |
5 | // conventions for a Kotlin/Native subproject
6 |
7 | plugins {
8 | id("ks3.conventions.lang.kotlin-multiplatform-base")
9 | }
10 |
11 | val ks3Settings = extensions.getByType()
12 |
13 | if (ks3Settings.enableKotlinNative.get()) {
14 | kotlin {
15 |
16 | // Native targets all extend commonMain and commonTest.
17 | //
18 | // Some targets (ios, tvos, watchos) are shortcuts provided by the Kotlin DSL, that
19 | // provide additional targets, except for 'simulators' which must be defined manually.
20 | // https://kotlinlang.org/docs/multiplatform-share-on-platforms.html#use-target-shortcuts
21 | //
22 | // common/
23 | // └── native/
24 | // ├── linuxX64
25 | // ├── mingwX64
26 | // ├── macosX64
27 | // ├── macosArm64
28 | // ├── ios/ (shortcut)
29 | // │ ├── iosArm64
30 | // │ ├── iosX64
31 | // │ └── iosSimulatorArm64
32 | // ├── tvos/ (shortcut)
33 | // │ ├── tvosArm64
34 | // │ ├── tvosX64
35 | // │ └── tvosSimulatorArm64Main
36 | // └── watchos/ (shortcut)
37 | // ├── watchosArm32
38 | // ├── watchosArm64
39 | // ├── watchosX64
40 | // └── watchosSimulatorArm64Main
41 |
42 | linuxX64()
43 |
44 | mingwX64()
45 |
46 | macosX64()
47 | macosArm64()
48 |
49 | iosX64()
50 | iosArm64()
51 | iosSimulatorArm64()
52 |
53 | watchosX64()
54 | watchosArm32()
55 | watchosArm64()
56 | watchosSimulatorArm64()
57 |
58 | tvosX64()
59 | tvosArm64()
60 | tvosSimulatorArm64()
61 |
62 | // Targets inherit in a natural way since Kotlin 1.9
63 | // https://kotlinlang.org/docs/multiplatform-hierarchy.html#default-hierarchy-template
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/build-logic/src/main/kotlin/ks3/conventions/lang/kotlinJsExtensions.kt:
--------------------------------------------------------------------------------
1 | package ks3.conventions.lang
2 |
3 | import org.gradle.api.Project
4 | import org.gradle.kotlin.dsl.configure
5 | import org.jetbrains.kotlin.gradle.targets.js.yarn.YarnRootExtension
6 |
7 | /**
8 | * `kotlin-js` and `kotlin-multiplatform` plugins adds a directory in the root-dir for the Yarn
9 | * lockfile. That's a bit annoying. It's a little neater if it's in the Gradle dir, next to the
10 | * version catalog.
11 | */
12 | internal fun Project.relocateKotlinJsStore() {
13 | afterEvaluate {
14 | rootProject.extensions.configure {
15 | lockFileDirectory = project.rootDir.resolve("gradle/kotlin-js-store")
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/build-logic/src/main/kotlin/ks3/conventions/publishing/accessors.kt:
--------------------------------------------------------------------------------
1 | package ks3.conventions.publishing
2 |
3 | import org.gradle.api.Project
4 | import org.gradle.api.publish.PublishingExtension
5 | import org.gradle.kotlin.dsl.configure
6 | import org.gradle.kotlin.dsl.getByType
7 | import org.gradle.plugins.signing.SigningExtension
8 |
9 | // Hacks, because IntelliJ is refusing to load the generated Gradle Kotlin DSL accessors.
10 | // These aren't necessary for building, only so auto-complete works.
11 |
12 | internal val Project.signing get() = extensions.getByType()
13 | internal fun Project.signing(action: SigningExtension.() -> Unit) = extensions.configure(action)
14 | internal val Project.publishing get() = extensions.getByType()
15 | internal fun Project.publishing(action: PublishingExtension.() -> Unit) = extensions.configure(action)
16 |
--------------------------------------------------------------------------------
/build-logic/src/main/kotlin/ks3/conventions/publishing/maven-publish.gradle.kts:
--------------------------------------------------------------------------------
1 | package ks3.conventions.publishing
2 |
3 | import ks3.conventions.Ks3BuildLogicSettings
4 | import org.jetbrains.kotlin.gradle.plugin.KotlinMultiplatformPluginWrapper
5 |
6 | plugins {
7 | signing
8 | `maven-publish`
9 | }
10 |
11 |
12 | val ks3Settings = extensions.getByType()
13 |
14 |
15 | val javadocJarStub by tasks.registering(Jar::class) {
16 | group = JavaBasePlugin.DOCUMENTATION_GROUP
17 | description = "Empty Javadoc Jar (required by Maven Central)"
18 | archiveClassifier.set("javadoc")
19 | }
20 |
21 | // can be set with environment variables: ORG_GRADLE_PROJECT_ossrhUsername and ORG_GRADLE_PROJECT_ossrhPassword
22 | val ossrhUsername: Provider = providers.gradleProperty("ossrhUsername")
23 | val ossrhPassword: Provider = providers.gradleProperty("ossrhPassword")
24 |
25 | val isReleaseVersion = provider { version.toString().matches(Ks3BuildLogicSettings.releaseVersionRegex) }
26 |
27 | val sonatypeReleaseUrl: Provider = isReleaseVersion.map { isRelease ->
28 | if (isRelease) {
29 | "https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/"
30 | } else {
31 | "https://s01.oss.sonatype.org/content/repositories/snapshots/"
32 | }
33 | }
34 |
35 | publishing {
36 | repositories {
37 | if (ossrhUsername.isPresent && ossrhPassword.isPresent) {
38 | maven(sonatypeReleaseUrl) {
39 | name = "SonatypeRelease"
40 | credentials {
41 | username = ossrhUsername.get()
42 | password = ossrhPassword.get()
43 | }
44 | }
45 | }
46 |
47 | // Publish to a project-local Maven directory, for verification. To test, run:
48 | // ./gradlew publishAllPublicationsToMavenProjectLocalRepository
49 | // and check $rootDir/build/maven-project-local
50 | maven(rootProject.layout.buildDirectory.dir("maven-project-local")) {
51 | name = "MavenProjectLocal"
52 | }
53 | }
54 |
55 | publications.withType().forEach {
56 | it.apply {
57 |
58 | artifact(javadocJarStub)
59 |
60 | pom {
61 | name.set("Ks3")
62 | description.set("KotlinX Serialization standard serializers")
63 | url.set("https://github.com/Kantis/ks3")
64 |
65 | scm {
66 | connection.set("scm:git:https://github.com/Kantis/ks3/")
67 | developerConnection.set("scm:git:https://github.com/Kantis/")
68 | url.set("https://github.com/Kantis/ks3")
69 | }
70 |
71 | licenses {
72 | license {
73 | name.set("Apache-2.0")
74 | url.set("https://opensource.org/licenses/Apache-2.0")
75 | }
76 | }
77 |
78 | developers {
79 | developer {
80 | id.set("Kantis")
81 | name.set("Emil Kantis")
82 | email.set("emil.kantis@protonmail.com")
83 | }
84 | }
85 | }
86 | }
87 | }
88 | }
89 |
90 | signing {
91 | val signingKey: String? by project
92 | val signingPassword: String? by project
93 |
94 | logger.lifecycle("[maven-publish convention] signing is enabled for ${project.path}")
95 | useGpgCmd()
96 | useInMemoryPgpKeys(signingKey, signingPassword)
97 | sign(publishing.publications)
98 | }
99 |
100 | // https://youtrack.jetbrains.com/issue/KT-46466
101 | val signingTasks = tasks.withType()
102 |
103 | tasks.withType().configureEach {
104 | // Gradle warns about some signing tasks using publishing task outputs without explicit dependencies.
105 | // Here's a quick fix.
106 | dependsOn(signingTasks)
107 | mustRunAfter(signingTasks)
108 |
109 | // use a val for the GAV to avoid Gradle Configuration Cache issues
110 | val publicationGAV = publication?.run { "$group:$artifactId:$version" }
111 |
112 | doLast {
113 | if (publicationGAV != null) {
114 | logger.lifecycle("[task: ${path}] $publicationGAV")
115 | }
116 | }
117 | }
118 |
119 | tasks.withType().configureEach {
120 | // use vals - improves Gradle Config Cache compatibility
121 | val publicationName = publication.name
122 | val enabledPublicationNamePrefixes = ks3Settings.enabledPublicationNamePrefixes
123 |
124 | val isPublicationEnabled = enabledPublicationNamePrefixes.map { prefixes ->
125 | prefixes.any { prefix -> publicationName.startsWith(prefix, ignoreCase = true) }
126 | }
127 |
128 | // register an input so Gradle can do up-to-date checks
129 | inputs.property("publicationEnabled", isPublicationEnabled)
130 |
131 | onlyIf {
132 | val enabled = isPublicationEnabled.get()
133 | if (!enabled) {
134 | logger.lifecycle("[task: $path] publishing for $publicationName is disabled")
135 | }
136 | enabled
137 | }
138 | }
139 |
140 | // Kotlin Multiplatform specific publishing configuration
141 | plugins.withType().configureEach {
142 | // nothing yet!
143 | }
144 |
--------------------------------------------------------------------------------
/build-logic/src/test/kotlin/IsReleaseVersionTest.kt:
--------------------------------------------------------------------------------
1 | import io.kotest.core.spec.style.FreeSpec
2 | import io.kotest.matchers.shouldBe
3 | import ks3.conventions.Ks3BuildLogicSettings
4 |
5 | class IsReleaseVersionTest : FreeSpec (
6 | {
7 | "isReleaseVersion" - {
8 | fun isReleaseVersion(version: String): Boolean = version.matches(Ks3BuildLogicSettings.releaseVersionRegex)
9 |
10 | "should return true for a release version" {
11 | isReleaseVersion("1.2.3") shouldBe true
12 | }
13 | "should return false for a snapshot version" {
14 | isReleaseVersion("1.2.3-SNAPSHOT") shouldBe false
15 | }
16 | "should return false for a milestone version" {
17 | isReleaseVersion("1.2.3-M1") shouldBe false
18 | }
19 | "should return false for a release candidate version" {
20 | isReleaseVersion("1.2.3-RC1") shouldBe false
21 | }
22 | "should return false for a pre-release version" {
23 | isReleaseVersion("1.2.3-alpha1") shouldBe false
24 | }
25 | }
26 | }
27 | )
28 |
--------------------------------------------------------------------------------
/build.gradle.kts:
--------------------------------------------------------------------------------
1 | import dev.adamko.dokkatoo.dokka.plugins.DokkaHtmlPluginParameters
2 |
3 | plugins {
4 | id("ks3.conventions.base")
5 | id("ks3.conventions.api-validation")
6 | idea
7 | }
8 |
9 | group = "io.ks3"
10 |
11 | dependencies {
12 | dokkatoo(projects.ks3Jdk)
13 | dokkatoo(projects.ks3Standard)
14 | dokkatoo(projects.ks3Test)
15 | dokkatooPluginHtml(libs.dokka.templating)
16 | dokkatooPluginHtml(libs.dokka.allModulesPage)
17 | }
18 |
19 | dokkatoo {
20 | moduleName.set("KS3")
21 |
22 | dokkatooPublications.configureEach {
23 | includes.from("README.md")
24 | }
25 |
26 | pluginsConfiguration.named("html") {
27 | customStyleSheets.from(
28 | "./dokka/custom-styles.css",
29 | )
30 | }
31 | }
32 |
33 | idea {
34 | module {
35 | isDownloadSources = true
36 | isDownloadJavadoc = false
37 | excludeDirs = excludeDirs +
38 | layout.files(
39 | ".idea",
40 | "gradle/kotlin-js-store", // location of the lock file, overridden by Kotlin/JS convention
41 | "gradle/wrapper",
42 | )
43 |
44 | // exclude generated Gradle code, so it doesn't clog up search results
45 | excludeDirs.addAll(
46 | layout.files(
47 | "build-logic/build/generated-sources/kotlin-dsl-accessors/kotlin/gradle",
48 | "build-logic/build/generated-sources/kotlin-dsl-external-plugin-spec-builders/kotlin/gradle",
49 | "build-logic/build/generated-sources/kotlin-dsl-plugins/kotlin",
50 | "build-logic/build/pluginUnderTestMetadata",
51 | ),
52 | )
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/doc/README.md:
--------------------------------------------------------------------------------
1 | Serializers for:
2 | * [Java types](jdk.md)
3 | * [Generic Kotlin stuff](standard.md)
4 |
--------------------------------------------------------------------------------
/doc/builders.md:
--------------------------------------------------------------------------------
1 | # Builders
2 |
3 | Builders provide a convenient way of creating a serializer that turns your type into a primitive by simply providing two conversion methods.
4 |
5 | ### Example:
6 |
7 | ```kotlin
8 | object UriSerializer : KSerializer by stringSerializer(
9 | ::URI,
10 | URI::toASCIIString,
11 | )
12 | ```
13 |
14 | Here we create a serializer for `URI`. Our object implements `KSerializer` by delegating to the result of `stringSerializer(..)`.
15 |
16 | The first parameter, `::URI`, is the _decoding_ method which takes our primitive (`String`) and returns a `URI`.
17 |
18 | The second parameter is the _encoding_ method, which takes our type and returns the primitive.
19 |
20 | Since the methods to go back and forth from String to URI already exists in Java, this serializer is easily created using `stringSerializer`.
21 |
22 |
23 | ### Available builders are:
24 |
25 | * `intSerializer`
26 | * `longSerializer`
27 | * `stringSerializer`
28 |
--------------------------------------------------------------------------------
/doc/jdk.md:
--------------------------------------------------------------------------------
1 | ## `java.time`
2 |
3 | | Type | Serializer | Typealias | Example |
4 | |----------------|---------------------------------|------------------------|-------------------------------|
5 | | Instant | InstantAsStringSerializer | InstantAsString | `2023-04-27T21:24:37.476555Z` |
6 | | Instant | InstantAsLongSerializer | InstantAsLong | `1682630695852` |
7 | | LocalDate | LocalDateAsStringSerializer | LocalDateAsString | `"2022-10-23"` |
8 | | LocalDateTime | LocalDateTimeAsStringSerializer | LocalDateTimeAsString | `"2022-10-23T21:44:00"` |
9 | | LocalTime | LocalTimeAsStringSerializer | LocalTimeAsString | `"21:44:00"` |
10 | | OffsetDateTime | OffsetDateTimeSerializer | OffsetDateTimeAsString | `2022` |
11 | | YearMonth | YearMonthAsStringSerializer | YearMonthAsString | `"2022-10"` |
12 | | Year | YearAsStringSerializer | not defined | `"2022"` |
13 | | Year | YearAsIntSerializer | not defined | `2022` |
14 |
15 | ## `java.io`
16 |
17 | | Type | Serializer | Typealias | Example |
18 | |------|--------------------|--------------|-------------------------|
19 | | File | FilePathSerializer | FileAsString | `"/home/emil/file.txt"` |
20 |
21 | ## `java.math`
22 |
23 | | Type | Serializer | Typealias | Example |
24 | |------------|------------------------------|-------------------------|----------------------------|
25 | | BigDecimal | BigDecimalAsStringSerializer | BigDecimalAsString | `"3.12345678901234567890"` |
26 | | BigDecimal | BigDecimalAsDouble | BigDecimalAsDouble | `3.1234567890123457` |
27 | | BigDecimal | BigDecimalAsJsonLiteral | BigDecimalAsJsonLiteral | `3.12345678901234567890` |
28 | | BigInteger | BigIntegerAsString | BigIntegerAsString | `"9223372036854775808"` |
29 | | BigInteger | BigIntegerAsJsonLiteral | BigIntegerAsJsonLiteral | `9223372036854775808` |
30 |
31 | ## `java.net`
32 |
33 | | Type | Serializer | Example |
34 | |------|---------------|-----------------------------|
35 | | URL | UrlSerializer | `"https://www.github.com"` |
36 | | URI | UriSerializer | `"telnet://192.0.2.16:80/"` |
37 |
38 | ## `java.util`
39 |
40 | | Type | Serializer | Example |
41 | |---------------|-------------------------|------------------------------------------|
42 | | UUID | UuidSerializer | `"8d44ac12-394b-4a50-ad6a-eff2cff129c4"` |
43 | | AtomicInteger | AtomicIntegerSerializer | `5` |
44 | | AtomicBoolean | AtomicBooleanSerializer | `true` |
45 | | AtomicLong | AtomicLongSerializer | `22` |
46 | | Locale | LocaleSerializer | `"en_US"` |
47 | | Currency | CurrencySerializer | `"AUD"` |
48 |
--------------------------------------------------------------------------------
/doc/standard.md:
--------------------------------------------------------------------------------
1 | | Type | Serializer | Example |
2 | |---------|-------------------------|---------|
3 | | Enum<*> | EnumAsOrdinalSerializer | `0` |
4 |
5 | ### EnumAsOrdinalSerializer
6 |
7 | Encodes/decodes an enum to its ordinal int value.
8 |
9 | Example usage:
10 |
11 | ```kotlin
12 | enum class Shape {
13 | SQUARE, CIRCLE;
14 | }
15 |
16 | object ShapeSerializer : KSerializer by enumAsOrdinalSerializer()
17 |
18 | println(Json.encodeToString(ShapeSerializer, Shape.CIRCLE)) // "1"
19 | ```
20 |
21 |
--------------------------------------------------------------------------------
/dokka/custom-styles.css:
--------------------------------------------------------------------------------
1 | .theme-dark .quotation {
2 | background-color: #2b2b2b;
3 | color: #d4d4d4;
4 | padding: 0.5em 10px;
5 | margin: 0.5em 10px;
6 | }
7 |
8 | .theme-dark .quotation code {
9 |
10 | }
11 |
12 | :root {
13 | --default-gray: rgb(66, 66, 66);
14 | }
15 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | ### Gradle build settings ###
2 |
3 | org.gradle.jvmargs=-Xmx3g -XX:MaxMetaspaceSize=756m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
4 | systemProp.org.gradle.jvmargs=-Xmx3g -XX:MaxMetaspaceSize=756m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
5 |
6 | org.gradle.daemon=true
7 | org.gradle.parallel=true
8 | org.gradle.caching=true
9 | org.gradle.configureondemand=false
10 |
11 | # overriden by GitHub action
12 | version = 0.1.0-SNAPSHOT
13 |
14 | # cache accessors - defaults to 'true' in Gradle 8.0 https://github.com/gradle/gradle/issues/20416
15 | org.gradle.kotlin.dsl.precompiled.accessors.strict=true
16 |
17 | org.gradle.unsafe.configuration-cache=true
18 | org.gradle.unsafe.configuration-cache-problems=warn
19 | # signing & Kotlin Multiplatform plugins triggers the default max-problems limit of 512 - so increase it to the max
20 | org.gradle.unsafe.configuration-cache.max-problems=2147483647
21 |
22 | #https://github.com/gradle/gradle/issues/11308
23 | org.gradle.internal.publish.checksums.insecure=true
24 | systemProp.org.gradle.internal.publish.checksums.insecure=true
25 |
26 |
27 | ### Kotlin config ###
28 |
29 | kotlin.code.style=official
30 |
31 | kotlin.native.ignoreIncorrectDependencies=true
32 | kotlin.native.disableCompilerDaemon=true
33 | kotlin.native.ignoreDisabledTargets=true
34 | kotlin.mpp.stability.nowarn=true
35 |
36 | kotlin.incremental=true
37 | kotlin.incremental.js=true
38 | systemProp.kotlin.native.disableCompilerDaemon=true
39 |
40 |
41 | ### Android config ###
42 |
43 | android.useAndroidX=true
44 | android.enableJetifier=true
45 |
46 |
47 | ### Ks3BuildLogicSettings ###
48 |
49 | # comma separated list of MavenPublication name prefixes, each of which will have its publishing task enabled
50 | ks3_enabledPublicationNamePrefixes=KotlinMultiplatform,Jvm,Js,iOS,macOS,watchOS,tvOS,mingw
51 |
52 | # enabled/disable Kotlin targets, for improving local dev & CI/CD performance
53 | ks3_enableKotlinJvm=true
54 | ks3_enableKotlinJs=true
55 | ks3_enableKotlinNative=false
56 |
--------------------------------------------------------------------------------
/gradle/libs.versions.toml:
--------------------------------------------------------------------------------
1 | [versions]
2 | dokka = "2.0.0"
3 | dokkatoo = "2.4.0"
4 |
5 | kotlin = "1.9.25"
6 | kotlinx-serialization = "1.6.3"
7 |
8 | kotest = "5.9.1"
9 | kotest-propertyArbsExtension = "2.1.2"
10 |
11 | okio = "3.9.1"
12 |
13 | gradlePlugin-ktlint = "12.1.2"
14 | gradlePlugin-testlogger = "4.0.0"
15 |
16 | kotlinBinaryCompatibilityValidator = "0.17.0"
17 |
18 | [libraries]
19 |
20 | ## KotlinX Serialization ##
21 | kotlinxSerialization-core = { module = "org.jetbrains.kotlinx:kotlinx-serialization-core", version.ref = "kotlinx-serialization" }
22 | kotlinxSerialization-cbor = { module = "org.jetbrains.kotlinx:kotlinx-serialization-cbor", version.ref = "kotlinx-serialization" }
23 | kotlinxSerialization-hocon = { module = "org.jetbrains.kotlinx:kotlinx-serialization-hocon", version.ref = "kotlinx-serialization" }
24 | kotlinxSerialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinx-serialization" }
25 | kotlinxSerialization-properties = { module = "org.jetbrains.kotlinx:kotlinx-serialization-properties", version.ref = "kotlinx-serialization" }
26 | kotlinxSerialization-protobuf = { module = "org.jetbrains.kotlinx:kotlinx-serialization-protobuf", version.ref = "kotlinx-serialization" }
27 | kotlinxSerialization-jsonOkio = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json-okio", version.ref = "kotlinx-serialization" }
28 |
29 |
30 | ## Kotest ##
31 | kotest-assertionsCore = { module = "io.kotest:kotest-assertions-core", version.ref = "kotest" }
32 | kotest-assertionsJson = { module = "io.kotest:kotest-assertions-json", version.ref = "kotest" }
33 | kotest-datatest = { module = "io.kotest:kotest-framework-datatest", version.ref = "kotest" }
34 | kotest-property = { module = "io.kotest:kotest-property", version.ref = "kotest" }
35 | kotest-frameworkApi = { module = "io.kotest:kotest-framework-api", version.ref = "kotest" }
36 | kotest-frameworkEngine = { module = "io.kotest:kotest-framework-engine", version.ref = "kotest" } # Not included in the BOM
37 | kotest-frameworkDatatest = { module = "io.kotest:kotest-framework-datatest", version.ref = "kotest" }
38 | kotest-runnerJunit5 = { module = "io.kotest:kotest-runner-junit5", version.ref = "kotest" }
39 |
40 | kotest-propertyArbsExtension = { module = "io.kotest.extensions:kotest-property-arbs", version.ref = "kotest-propertyArbsExtension" }
41 |
42 | kotestPlugin-embeddedCompiler = { module = "io.kotest:kotest-framework-multiplatform-plugin-embeddable-compiler" }
43 | kotestPlugin-multiplatform = { module = "io.kotest:kotest-framework-multiplatform-plugin-gradle" }
44 |
45 | ## Dokka plugins ##
46 | dokka-allModulesPage = { module = "org.jetbrains.dokka:all-modules-page-plugin", version.ref = "dokka" }
47 | dokka-templating = { module = "org.jetbrains.dokka:templating-plugin", version.ref = "dokka" }
48 |
49 |
50 | ## Okio ##
51 | okio-core = { module = "com.squareup.okio:okio", version.ref = "okio" }
52 |
53 |
54 | ### Gradle plugins ###
55 | # Maven coordinates of Gradle plugins. Used in ./build-logic/build.gradle.kts.
56 | gradlePlugin-kotlin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" }
57 | gradlePlugin-kotlinAllOpen = { module = "org.jetbrains.kotlin:kotlin-allopen", version.ref = "kotlin" }
58 | gradlePlugin-kotlinNoArg = { module = "org.jetbrains.kotlin:kotlin-noarg", version.ref = "kotlin" }
59 | gradlePlugin-kotlinSerialization = { module = "org.jetbrains.kotlin:kotlin-serialization", version.ref = "kotlin" }
60 |
61 | gradlePlugin-testlogger = { module = "com.adarshr:gradle-test-logger-plugin", version.ref = "gradlePlugin-testlogger" }
62 |
63 | gradlePlugin-kotest = { module = "io.kotest:kotest-framework-multiplatform-plugin-gradle", version.ref = "kotest" }
64 |
65 | gradlePlugin-dokka = { module = "org.jetbrains.dokka:dokka-gradle-plugin", version.ref = "dokka" }
66 | gradlePlugin-dokkatoo = { module = "dev.adamko.dokkatoo:dokkatoo-plugin", version.ref = "dokkatoo" }
67 |
68 | gradlePlugin-ktlint = { module = "org.jlleitschuh.gradle:ktlint-gradle", version.ref = "gradlePlugin-ktlint" }
69 | gradlePlugin-kotlinBinaryCompatibilityValidator = { module = "org.jetbrains.kotlinx:binary-compatibility-validator", version.ref = "kotlinBinaryCompatibilityValidator" }
70 | [bundles]
71 |
72 |
73 | [plugins]
74 |
75 | # If plugin will be used by conventions, define plugins in ./build-logic/build.gradle.kts to avoid error:
76 | # The Kotlin Gradle plugin was loaded multiple times in different subprojects, which is not supported and may break the build.
77 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Kantis/ks3/c80f89080aba6744f396c72394ac6a8b089829b4/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-all.zip
4 | networkTimeout=10000
5 | zipStoreBase=GRADLE_USER_HOME
6 | zipStorePath=wrapper/dists
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | #
4 | # Copyright © 2015-2021 the original authors.
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # https://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 |
19 | ##############################################################################
20 | #
21 | # Gradle start up script for POSIX generated by Gradle.
22 | #
23 | # Important for running:
24 | #
25 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
26 | # noncompliant, but you have some other compliant shell such as ksh or
27 | # bash, then to run this script, type that shell name before the whole
28 | # command line, like:
29 | #
30 | # ksh Gradle
31 | #
32 | # Busybox and similar reduced shells will NOT work, because this script
33 | # requires all of these POSIX shell features:
34 | # * functions;
35 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
36 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»;
37 | # * compound commands having a testable exit status, especially «case»;
38 | # * various built-in commands including «command», «set», and «ulimit».
39 | #
40 | # Important for patching:
41 | #
42 | # (2) This script targets any POSIX shell, so it avoids extensions provided
43 | # by Bash, Ksh, etc; in particular arrays are avoided.
44 | #
45 | # The "traditional" practice of packing multiple parameters into a
46 | # space-separated string is a well documented source of bugs and security
47 | # problems, so this is (mostly) avoided, by progressively accumulating
48 | # options in "$@", and eventually passing that to Java.
49 | #
50 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
51 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
52 | # see the in-line comments for details.
53 | #
54 | # There are tweaks for specific operating systems such as AIX, CygWin,
55 | # Darwin, MinGW, and NonStop.
56 | #
57 | # (3) This script is generated from the Groovy template
58 | # https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
59 | # within the Gradle project.
60 | #
61 | # You can find Gradle at https://github.com/gradle/gradle/.
62 | #
63 | ##############################################################################
64 |
65 | # Attempt to set APP_HOME
66 |
67 | # Resolve links: $0 may be a link
68 | app_path=$0
69 |
70 | # Need this for daisy-chained symlinks.
71 | while
72 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
73 | [ -h "$app_path" ]
74 | do
75 | ls=$( ls -ld "$app_path" )
76 | link=${ls#*' -> '}
77 | case $link in #(
78 | /*) app_path=$link ;; #(
79 | *) app_path=$APP_HOME$link ;;
80 | esac
81 | done
82 |
83 | # This is normally unused
84 | # shellcheck disable=SC2034
85 | APP_BASE_NAME=${0##*/}
86 | APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
87 |
88 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
89 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
90 |
91 | # Use the maximum available, or set MAX_FD != -1 to use that value.
92 | MAX_FD=maximum
93 |
94 | warn () {
95 | echo "$*"
96 | } >&2
97 |
98 | die () {
99 | echo
100 | echo "$*"
101 | echo
102 | exit 1
103 | } >&2
104 |
105 | # OS specific support (must be 'true' or 'false').
106 | cygwin=false
107 | msys=false
108 | darwin=false
109 | nonstop=false
110 | case "$( uname )" in #(
111 | CYGWIN* ) cygwin=true ;; #(
112 | Darwin* ) darwin=true ;; #(
113 | MSYS* | MINGW* ) msys=true ;; #(
114 | NONSTOP* ) nonstop=true ;;
115 | esac
116 |
117 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
118 |
119 |
120 | # Determine the Java command to use to start the JVM.
121 | if [ -n "$JAVA_HOME" ] ; then
122 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
123 | # IBM's JDK on AIX uses strange locations for the executables
124 | JAVACMD=$JAVA_HOME/jre/sh/java
125 | else
126 | JAVACMD=$JAVA_HOME/bin/java
127 | fi
128 | if [ ! -x "$JAVACMD" ] ; then
129 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
130 |
131 | Please set the JAVA_HOME variable in your environment to match the
132 | location of your Java installation."
133 | fi
134 | else
135 | JAVACMD=java
136 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
137 |
138 | Please set the JAVA_HOME variable in your environment to match the
139 | location of your Java installation."
140 | fi
141 |
142 | # Increase the maximum file descriptors if we can.
143 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
144 | case $MAX_FD in #(
145 | max*)
146 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
147 | # shellcheck disable=SC3045
148 | MAX_FD=$( ulimit -H -n ) ||
149 | warn "Could not query maximum file descriptor limit"
150 | esac
151 | case $MAX_FD in #(
152 | '' | soft) :;; #(
153 | *)
154 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
155 | # shellcheck disable=SC3045
156 | ulimit -n "$MAX_FD" ||
157 | warn "Could not set maximum file descriptor limit to $MAX_FD"
158 | esac
159 | fi
160 |
161 | # Collect all arguments for the java command, stacking in reverse order:
162 | # * args from the command line
163 | # * the main class name
164 | # * -classpath
165 | # * -D...appname settings
166 | # * --module-path (only if needed)
167 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
168 |
169 | # For Cygwin or MSYS, switch paths to Windows format before running java
170 | if "$cygwin" || "$msys" ; then
171 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
172 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
173 |
174 | JAVACMD=$( cygpath --unix "$JAVACMD" )
175 |
176 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
177 | for arg do
178 | if
179 | case $arg in #(
180 | -*) false ;; # don't mess with options #(
181 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
182 | [ -e "$t" ] ;; #(
183 | *) false ;;
184 | esac
185 | then
186 | arg=$( cygpath --path --ignore --mixed "$arg" )
187 | fi
188 | # Roll the args list around exactly as many times as the number of
189 | # args, so each arg winds up back in the position where it started, but
190 | # possibly modified.
191 | #
192 | # NB: a `for` loop captures its iteration list before it begins, so
193 | # changing the positional parameters here affects neither the number of
194 | # iterations, nor the values presented in `arg`.
195 | shift # remove old arg
196 | set -- "$@" "$arg" # push replacement arg
197 | done
198 | fi
199 |
200 | # Collect all arguments for the java command;
201 | # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
202 | # shell script including quotes and variable substitutions, so put them in
203 | # double quotes to make sure that they get re-expanded; and
204 | # * put everything else in single quotes, so that it's not re-expanded.
205 |
206 | set -- \
207 | "-Dorg.gradle.appname=$APP_BASE_NAME" \
208 | -classpath "$CLASSPATH" \
209 | org.gradle.wrapper.GradleWrapperMain \
210 | "$@"
211 |
212 | # Stop when "xargs" is not available.
213 | if ! command -v xargs >/dev/null 2>&1
214 | then
215 | die "xargs is not available"
216 | fi
217 |
218 | # Use "xargs" to parse quoted args.
219 | #
220 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed.
221 | #
222 | # In Bash we could simply go:
223 | #
224 | # readarray ARGS < <( xargs -n1 <<<"$var" ) &&
225 | # set -- "${ARGS[@]}" "$@"
226 | #
227 | # but POSIX shell has neither arrays nor command substitution, so instead we
228 | # post-process each arg (as a line of input to sed) to backslash-escape any
229 | # character that might be a shell metacharacter, then use eval to reverse
230 | # that process (while maintaining the separation between arguments), and wrap
231 | # the whole thing up as a single "set" statement.
232 | #
233 | # This will of course break if any of these variables contains a newline or
234 | # an unmatched quote.
235 | #
236 |
237 | eval "set -- $(
238 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
239 | xargs -n1 |
240 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
241 | tr '\n' ' '
242 | )" '"$@"'
243 |
244 | exec "$JAVACMD" "$@"
245 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%"=="" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%"=="" set DIRNAME=.
29 | @rem This is normally unused
30 | set APP_BASE_NAME=%~n0
31 | set APP_HOME=%DIRNAME%
32 |
33 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
34 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
35 |
36 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
37 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
38 |
39 | @rem Find java.exe
40 | if defined JAVA_HOME goto findJavaFromJavaHome
41 |
42 | set JAVA_EXE=java.exe
43 | %JAVA_EXE% -version >NUL 2>&1
44 | if %ERRORLEVEL% equ 0 goto execute
45 |
46 | echo.
47 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
48 | echo.
49 | echo Please set the JAVA_HOME variable in your environment to match the
50 | echo location of your Java installation.
51 |
52 | goto fail
53 |
54 | :findJavaFromJavaHome
55 | set JAVA_HOME=%JAVA_HOME:"=%
56 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
57 |
58 | if exist "%JAVA_EXE%" goto execute
59 |
60 | echo.
61 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
62 | echo.
63 | echo Please set the JAVA_HOME variable in your environment to match the
64 | echo location of your Java installation.
65 |
66 | goto fail
67 |
68 | :execute
69 | @rem Setup the command line
70 |
71 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
72 |
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if %ERRORLEVEL% equ 0 goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | set EXIT_CODE=%ERRORLEVEL%
85 | if %EXIT_CODE% equ 0 set EXIT_CODE=1
86 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
87 | exit /b %EXIT_CODE%
88 |
89 | :mainEnd
90 | if "%OS%"=="Windows_NT" endlocal
91 |
92 | :omega
93 |
--------------------------------------------------------------------------------
/ks3-core/api/ks3-core.api:
--------------------------------------------------------------------------------
1 | public abstract interface annotation class io/ks3/core/ExperimentalKs3 : java/lang/annotation/Annotation {
2 | }
3 |
4 | public abstract interface annotation class io/ks3/core/Ks3Internal : java/lang/annotation/Annotation {
5 | }
6 |
7 |
--------------------------------------------------------------------------------
/ks3-core/api/ks3-core.klib.api:
--------------------------------------------------------------------------------
1 | // Klib ABI Dump
2 | // Targets: [iosArm64, iosSimulatorArm64, iosX64, js, linuxX64, macosArm64, macosX64, mingwX64, tvosArm64, tvosSimulatorArm64, tvosX64, watchosArm32, watchosArm64, watchosSimulatorArm64, watchosX64]
3 | // Rendering settings:
4 | // - Signature version: 2
5 | // - Show manifest properties: true
6 | // - Show declarations: true
7 |
8 | // Library unique name:
9 | open annotation class io.ks3.core/ExperimentalKs3 : kotlin/Annotation { // io.ks3.core/ExperimentalKs3|null[0]
10 | constructor () // io.ks3.core/ExperimentalKs3.|(){}[0]
11 | }
12 |
13 | open annotation class io.ks3.core/Ks3Internal : kotlin/Annotation { // io.ks3.core/Ks3Internal|null[0]
14 | constructor () // io.ks3.core/Ks3Internal.|(){}[0]
15 | }
16 |
--------------------------------------------------------------------------------
/ks3-core/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("ks3.conventions.lang.kotlin-multiplatform-js")
3 | id("ks3.conventions.lang.kotlin-multiplatform-jvm")
4 | id("ks3.conventions.lang.kotlin-multiplatform-native")
5 | id("ks3.conventions.publishing.maven-publish")
6 | }
7 |
--------------------------------------------------------------------------------
/ks3-core/src/commonMain/kotlin/io/ks3/core/ExperimentalKs3.kt:
--------------------------------------------------------------------------------
1 | package io.ks3.core
2 |
3 | @RequiresOptIn(level = RequiresOptIn.Level.WARNING, message = "This API is experimental and is prone to change in the future.")
4 | @MustBeDocumented
5 | @Retention(value = AnnotationRetention.BINARY)
6 | annotation class ExperimentalKs3
7 |
--------------------------------------------------------------------------------
/ks3-core/src/commonMain/kotlin/io/ks3/core/Ks3Internal.kt:
--------------------------------------------------------------------------------
1 | package io.ks3.core
2 |
3 | /**
4 | * An internal Ks3 feature that is public for operational reasons but should not be used by end users.
5 | */
6 | @Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION, AnnotationTarget.TYPEALIAS)
7 | @MustBeDocumented
8 | @Retention(value = AnnotationRetention.BINARY)
9 | @RequiresOptIn(level = RequiresOptIn.Level.WARNING)
10 | annotation class Ks3Internal
11 |
--------------------------------------------------------------------------------
/ks3-jdk/Module.md:
--------------------------------------------------------------------------------
1 | # Module ks3-jdk
2 |
3 | Serializers for Java types. Available for JVM targets.
4 |
5 | # Package io.ks3.java.io
6 |
7 | Contains serializers for types in the `java.io`-package, such as `java.io.File`.
8 |
9 | ## Typealiases
10 |
11 | | Type | Typealias | Example |
12 | |------|--------------|---------------------------------------|
13 | | File | FileAsString | `"/Users/emil/Documents/theFile.txt"` |
14 |
15 | # Package io.ks3.java.net
16 |
17 | Contains serializers for networking-related types, such as `java.net.URL`.
18 |
19 | ## Typealiases
20 |
21 | | Type | Typealias | Example |
22 | |------|-------------|-----------------------------|
23 | | URL | UrlAsString | `"https://www.github.com"` |
24 | | URI | UriAsString | `"telnet://192.0.2.16:80/"` |
25 |
26 | # Package io.ks3.java.time
27 |
28 | Contains serializers for time-related types, such as `java.time.Instant`.
29 |
30 | ## Typealiases
31 |
32 | | Type | Typealias | Example |
33 | |----------------|------------------------|---------------------------------|
34 | | Instant | InstantAsString | `"2023-04-27T21:24:37.476555Z"` |
35 | | Instant | InstantAsLong | `1682630695852` |
36 | | LocalDate | LocalDateAsString | `"2022-10-23"` |
37 | | LocalDateTime | LocalDateTimeAsString | `"2022-10-23T21:44:00"` |
38 | | LocalTime | LocalTimeAsString | `"21:44:00"` |
39 | | OffsetDateTime | OffsetDateTimeAsString | `"2022-10-23T21:44:00+02:00"` |
40 | | YearMonth | YearMonthAsString | `"2022-10"` |
41 |
42 | # Package io.ks3.java.typealias
43 |
44 | Contains `typealias`es for easily defining types where the Java type uses a pre-determined `Serializer`.
45 |
46 | See each package for available `typealias`es.
47 |
48 | # Package io.ks3.java.util
49 |
50 | Contains serializers for types in `java.util`, such as `UUID` or `AtomicLong`.
51 |
--------------------------------------------------------------------------------
/ks3-jdk/README.md:
--------------------------------------------------------------------------------
1 | Serializers for Java types, available for JVM targets.
2 |
--------------------------------------------------------------------------------
/ks3-jdk/api/ks3-jdk.api:
--------------------------------------------------------------------------------
1 | public final class io/ks3/java/io/FilePathSerializer : kotlinx/serialization/KSerializer {
2 | public static final field INSTANCE Lio/ks3/java/io/FilePathSerializer;
3 | public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/io/File;
4 | public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
5 | public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
6 | public fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/io/File;)V
7 | public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
8 | }
9 |
10 | public final class io/ks3/java/math/BigDecimalAsDoubleSerializer : kotlinx/serialization/KSerializer {
11 | public static final field INSTANCE Lio/ks3/java/math/BigDecimalAsDoubleSerializer;
12 | public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
13 | public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/math/BigDecimal;
14 | public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
15 | public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
16 | public fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/math/BigDecimal;)V
17 | }
18 |
19 | public final class io/ks3/java/math/BigDecimalAsJsonNumberSerializer : kotlinx/serialization/KSerializer {
20 | public static final field INSTANCE Lio/ks3/java/math/BigDecimalAsJsonNumberSerializer;
21 | public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
22 | public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/math/BigDecimal;
23 | public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
24 | public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
25 | public fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/math/BigDecimal;)V
26 | }
27 |
28 | public final class io/ks3/java/math/BigDecimalAsStringSerializer : kotlinx/serialization/KSerializer {
29 | public static final field INSTANCE Lio/ks3/java/math/BigDecimalAsStringSerializer;
30 | public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
31 | public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/math/BigDecimal;
32 | public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
33 | public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
34 | public fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/math/BigDecimal;)V
35 | }
36 |
37 | public final class io/ks3/java/math/BigIntegerAsJsonLiteralSerializer : kotlinx/serialization/KSerializer {
38 | public static final field INSTANCE Lio/ks3/java/math/BigIntegerAsJsonLiteralSerializer;
39 | public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
40 | public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/math/BigInteger;
41 | public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
42 | public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
43 | public fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/math/BigInteger;)V
44 | }
45 |
46 | public final class io/ks3/java/math/BigIntegerAsStringSerializer : kotlinx/serialization/KSerializer {
47 | public static final field INSTANCE Lio/ks3/java/math/BigIntegerAsStringSerializer;
48 | public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
49 | public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/math/BigInteger;
50 | public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
51 | public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
52 | public fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/math/BigInteger;)V
53 | }
54 |
55 | public final class io/ks3/java/net/UriSerializer : kotlinx/serialization/KSerializer {
56 | public static final field INSTANCE Lio/ks3/java/net/UriSerializer;
57 | public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
58 | public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/net/URI;
59 | public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
60 | public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
61 | public fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/net/URI;)V
62 | }
63 |
64 | public final class io/ks3/java/net/UrlSerializer : kotlinx/serialization/KSerializer {
65 | public static final field INSTANCE Lio/ks3/java/net/UrlSerializer;
66 | public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
67 | public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/net/URL;
68 | public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
69 | public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
70 | public fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/net/URL;)V
71 | }
72 |
73 | public final class io/ks3/java/time/InstantAsLongSerializer : kotlinx/serialization/KSerializer {
74 | public static final field INSTANCE Lio/ks3/java/time/InstantAsLongSerializer;
75 | public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
76 | public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/time/Instant;
77 | public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
78 | public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
79 | public fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/time/Instant;)V
80 | }
81 |
82 | public final class io/ks3/java/time/InstantAsStringSerializer : kotlinx/serialization/KSerializer {
83 | public static final field INSTANCE Lio/ks3/java/time/InstantAsStringSerializer;
84 | public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
85 | public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/time/Instant;
86 | public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
87 | public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
88 | public fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/time/Instant;)V
89 | }
90 |
91 | public final class io/ks3/java/time/LocalDateAsStringSerializer : kotlinx/serialization/KSerializer {
92 | public static final field INSTANCE Lio/ks3/java/time/LocalDateAsStringSerializer;
93 | public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
94 | public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/time/LocalDate;
95 | public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
96 | public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
97 | public fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/time/LocalDate;)V
98 | }
99 |
100 | public final class io/ks3/java/time/LocalDateTimeAsStringSerializer : kotlinx/serialization/KSerializer {
101 | public static final field INSTANCE Lio/ks3/java/time/LocalDateTimeAsStringSerializer;
102 | public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
103 | public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/time/LocalDateTime;
104 | public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
105 | public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
106 | public fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/time/LocalDateTime;)V
107 | }
108 |
109 | public final class io/ks3/java/time/LocalTimeAsStringSerializer : kotlinx/serialization/KSerializer {
110 | public static final field INSTANCE Lio/ks3/java/time/LocalTimeAsStringSerializer;
111 | public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
112 | public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/time/LocalTime;
113 | public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
114 | public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
115 | public fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/time/LocalTime;)V
116 | }
117 |
118 | public final class io/ks3/java/time/OffsetDateTimeAsStringSerializer : kotlinx/serialization/KSerializer {
119 | public static final field INSTANCE Lio/ks3/java/time/OffsetDateTimeAsStringSerializer;
120 | public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
121 | public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/time/OffsetDateTime;
122 | public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
123 | public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
124 | public fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/time/OffsetDateTime;)V
125 | }
126 |
127 | public final class io/ks3/java/time/YearAsIntSerializer : kotlinx/serialization/KSerializer {
128 | public static final field INSTANCE Lio/ks3/java/time/YearAsIntSerializer;
129 | public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
130 | public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/time/Year;
131 | public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
132 | public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
133 | public fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/time/Year;)V
134 | }
135 |
136 | public final class io/ks3/java/time/YearAsStringSerializer : kotlinx/serialization/KSerializer {
137 | public static final field INSTANCE Lio/ks3/java/time/YearAsStringSerializer;
138 | public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
139 | public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/time/Year;
140 | public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
141 | public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
142 | public fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/time/Year;)V
143 | }
144 |
145 | public final class io/ks3/java/time/YearMonthAsStringSerializer : kotlinx/serialization/KSerializer {
146 | public static final field INSTANCE Lio/ks3/java/time/YearMonthAsStringSerializer;
147 | public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
148 | public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/time/YearMonth;
149 | public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
150 | public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
151 | public fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/time/YearMonth;)V
152 | }
153 |
154 | public final class io/ks3/java/util/AtomicBooleanSerializer : kotlinx/serialization/KSerializer {
155 | public static final field INSTANCE Lio/ks3/java/util/AtomicBooleanSerializer;
156 | public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
157 | public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/util/concurrent/atomic/AtomicBoolean;
158 | public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
159 | public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
160 | public fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/util/concurrent/atomic/AtomicBoolean;)V
161 | }
162 |
163 | public final class io/ks3/java/util/AtomicIntegerSerializer : kotlinx/serialization/KSerializer {
164 | public static final field INSTANCE Lio/ks3/java/util/AtomicIntegerSerializer;
165 | public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
166 | public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/util/concurrent/atomic/AtomicInteger;
167 | public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
168 | public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
169 | public fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/util/concurrent/atomic/AtomicInteger;)V
170 | }
171 |
172 | public final class io/ks3/java/util/AtomicLongSerializer : kotlinx/serialization/KSerializer {
173 | public static final field INSTANCE Lio/ks3/java/util/AtomicLongSerializer;
174 | public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
175 | public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/util/concurrent/atomic/AtomicLong;
176 | public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
177 | public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
178 | public fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/util/concurrent/atomic/AtomicLong;)V
179 | }
180 |
181 | public final class io/ks3/java/util/CurrencySerializer : kotlinx/serialization/KSerializer {
182 | public static final field INSTANCE Lio/ks3/java/util/CurrencySerializer;
183 | public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
184 | public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/util/Currency;
185 | public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
186 | public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
187 | public fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/util/Currency;)V
188 | }
189 |
190 | public final class io/ks3/java/util/LocaleSerializer : kotlinx/serialization/KSerializer {
191 | public static final field INSTANCE Lio/ks3/java/util/LocaleSerializer;
192 | public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
193 | public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/util/Locale;
194 | public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
195 | public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
196 | public fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/util/Locale;)V
197 | }
198 |
199 | public final class io/ks3/java/util/UuidSerializer : kotlinx/serialization/KSerializer {
200 | public static final field INSTANCE Lio/ks3/java/util/UuidSerializer;
201 | public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
202 | public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/util/UUID;
203 | public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
204 | public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
205 | public fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/util/UUID;)V
206 | }
207 |
208 |
--------------------------------------------------------------------------------
/ks3-jdk/api/ks3-jdk.klib.api:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Kantis/ks3/c80f89080aba6744f396c72394ac6a8b089829b4/ks3-jdk/api/ks3-jdk.klib.api
--------------------------------------------------------------------------------
/ks3-jdk/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("ks3.conventions.lang.kotlin-multiplatform-jvm") // Kotlin/JVM only
3 | id("ks3.conventions.publishing.maven-publish")
4 | }
5 |
6 | dokkatoo {
7 | dokkatooSourceSets.configureEach {
8 | includes.from("Module.md")
9 | }
10 |
11 | dokkatooSourceSets.named("jvmMain") {
12 | sourceLink {
13 | localDirectory.set(file("src/jvmMain/kotlin"))
14 | remoteUrl("https://github.com/Kantis/ks3/blob/main/ks3-jdk/src/jvmMain/kotlin")
15 | remoteLineSuffix.set("#L")
16 | }
17 | }
18 |
19 | modulePath.set("ks3-jdk") // match the original dokka default
20 | }
21 |
22 | tasks.withType().configureEach {
23 | generator.dokkaSourceSets.configureEach {
24 | sourceSetScope.set(":ks3-jdk:dokkaHtmlPartial")
25 | }
26 | }
27 |
28 | kotlin {
29 | sourceSets {
30 | commonMain {
31 | dependencies {
32 | implementation(projects.ks3Core)
33 | implementation(projects.ks3Standard)
34 | implementation(libs.kotlinxSerialization.core)
35 | implementation(libs.kotlinxSerialization.json)
36 | }
37 | }
38 |
39 | commonTest {
40 | dependencies {
41 | implementation(projects.ks3Test)
42 |
43 | implementation(libs.kotest.frameworkEngine)
44 | implementation(libs.kotest.assertionsCore)
45 | implementation(libs.kotest.assertionsJson)
46 | implementation(libs.kotest.property)
47 |
48 | implementation(libs.kotlinxSerialization.json)
49 | }
50 | }
51 |
52 | if (ks3Settings.enableKotlinJvm.get()) {
53 | jvmTest {
54 | dependencies {
55 | implementation(kotlin("reflect"))
56 | implementation(libs.kotest.runnerJunit5)
57 | }
58 | }
59 | }
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/ks3-jdk/src/jvmMain/kotlin/io/ks3/java/io/FilePathSerializer.kt:
--------------------------------------------------------------------------------
1 | package io.ks3.java.io
2 |
3 | import io.ks3.standard.stringSerializer
4 | import kotlinx.serialization.KSerializer
5 | import java.io.File
6 |
7 | object FilePathSerializer : KSerializer by stringSerializer(::File, File::invariantSeparatorsPath)
8 |
--------------------------------------------------------------------------------
/ks3-jdk/src/jvmMain/kotlin/io/ks3/java/math/BigDecimalSerializers.kt:
--------------------------------------------------------------------------------
1 | package io.ks3.java.math
2 |
3 | import io.ks3.standard.doubleSerializer
4 | import io.ks3.standard.stringSerializer
5 | import kotlinx.serialization.ExperimentalSerializationApi
6 | import kotlinx.serialization.KSerializer
7 | import kotlinx.serialization.Serializable
8 | import kotlinx.serialization.descriptors.PrimitiveKind
9 | import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
10 | import kotlinx.serialization.encoding.Decoder
11 | import kotlinx.serialization.encoding.Encoder
12 | import kotlinx.serialization.json.JsonDecoder
13 | import kotlinx.serialization.json.JsonEncoder
14 | import kotlinx.serialization.json.JsonUnquotedLiteral
15 | import kotlinx.serialization.json.jsonPrimitive
16 | import java.math.BigDecimal
17 |
18 | /**
19 | * Encodes a [BigDecimal] as a string, preserving the full precision of the number. Wraps the number in quotes.
20 | */
21 | typealias BigDecimalAsString =
22 | @Serializable(with = BigDecimalAsStringSerializer::class)
23 | BigDecimal
24 |
25 | /**
26 | * Encodes a [BigDecimal] to it's double representation. [Double] have limited precision, so the number might not be
27 | * exactly the same as the original [BigDecimal].
28 | */
29 | typealias BigDecimalAsDouble =
30 | @Serializable(with = BigDecimalAsDoubleSerializer::class)
31 | BigDecimal
32 |
33 | /**
34 | * Encodes a [BigDecimal] as an unquoted JSON literal, preserving the full precision of the number while being encoded as a number.
35 | *
36 | * Note that this typealias is primarily meant for JSON, other formats will have the [BigDecimal] encoded as a string.
37 | */
38 | @ExperimentalSerializationApi
39 | typealias BigDecimalAsJsonNumber =
40 | @Serializable(with = BigDecimalAsJsonNumberSerializer::class)
41 | BigDecimal
42 |
43 | object BigDecimalAsStringSerializer : KSerializer by stringSerializer(::BigDecimal, BigDecimal::toPlainString)
44 |
45 | object BigDecimalAsDoubleSerializer : KSerializer by doubleSerializer({ it.toString().toBigDecimal() }, BigDecimal::toDouble)
46 |
47 | /**
48 | * Encodes a [BigDecimal] as an exact numeric value, preserving the full precision of the number.
49 | *
50 | * Note that this serializer is primarily meant for JSON, other formats will have the [BigDecimal] encoded as a string.
51 | */
52 | @ExperimentalSerializationApi
53 | object BigDecimalAsJsonNumberSerializer : KSerializer {
54 | override val descriptor = PrimitiveSerialDescriptor(BigDecimal::class.qualifiedName!!, PrimitiveKind.STRING)
55 |
56 | override fun deserialize(decoder: Decoder): BigDecimal {
57 | return if (decoder is JsonDecoder) {
58 | BigDecimal(decoder.decodeJsonElement().jsonPrimitive.content)
59 | } else {
60 | BigDecimal(decoder.decodeString())
61 | }
62 | }
63 |
64 | override fun serialize(
65 | encoder: Encoder,
66 | value: BigDecimal,
67 | ) {
68 | val bdString = value.toPlainString()
69 |
70 | if (encoder is JsonEncoder) {
71 | encoder.encodeJsonElement(JsonUnquotedLiteral(bdString))
72 | } else {
73 | encoder.encodeString(bdString)
74 | }
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/ks3-jdk/src/jvmMain/kotlin/io/ks3/java/math/BigIntegerSerializers.kt:
--------------------------------------------------------------------------------
1 | package io.ks3.java.math
2 |
3 | import io.ks3.standard.stringSerializer
4 | import kotlinx.serialization.ExperimentalSerializationApi
5 | import kotlinx.serialization.KSerializer
6 | import kotlinx.serialization.Serializable
7 | import kotlinx.serialization.descriptors.PrimitiveKind
8 | import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
9 | import kotlinx.serialization.encoding.Decoder
10 | import kotlinx.serialization.encoding.Encoder
11 | import kotlinx.serialization.json.JsonDecoder
12 | import kotlinx.serialization.json.JsonEncoder
13 | import kotlinx.serialization.json.JsonUnquotedLiteral
14 | import kotlinx.serialization.json.jsonPrimitive
15 | import java.math.BigDecimal
16 | import java.math.BigInteger
17 |
18 | /**
19 | * Encodes a [BigDecimal] as a string, preserving the full precision of the number. Wraps the number in quotes.
20 | */
21 | typealias BigIntegerAsString =
22 | @Serializable(with = BigIntegerAsStringSerializer::class)
23 | BigInteger
24 |
25 | /**
26 | * Encodes a [BigInteger] as an unquoted JSON literal, preserving the full precision of the number while being encoded as a number.
27 | *
28 | * Note that this typealias is primarily meant for JSON, other formats will have the [BigInteger] encoded as a string.
29 | */
30 | @ExperimentalSerializationApi
31 | typealias BigIntegerAsJsonLiteral =
32 | @Serializable(with = BigIntegerAsJsonLiteralSerializer::class)
33 | BigInteger
34 |
35 | object BigIntegerAsStringSerializer : KSerializer by stringSerializer(::BigInteger, BigInteger::toString)
36 |
37 | /**
38 | * Encodes a [BigInteger] as an exact numeric value, preserving the full precision of the number.
39 | *
40 | * Note that this serializer is primarily meant for JSON, other formats will have the [BigInteger] encoded as a string.
41 | */
42 | @ExperimentalSerializationApi
43 | object BigIntegerAsJsonLiteralSerializer : KSerializer {
44 | override val descriptor = PrimitiveSerialDescriptor(BigInteger::class.qualifiedName!!, PrimitiveKind.DOUBLE)
45 |
46 | override fun deserialize(decoder: Decoder): BigInteger {
47 | return if (decoder is JsonDecoder) {
48 | BigInteger(decoder.decodeJsonElement().jsonPrimitive.content)
49 | } else {
50 | BigInteger(decoder.decodeString())
51 | }
52 | }
53 |
54 | override fun serialize(
55 | encoder: Encoder,
56 | value: BigInteger,
57 | ) {
58 | val bdString = value.toString()
59 |
60 | if (encoder is JsonEncoder) {
61 | encoder.encodeJsonElement(JsonUnquotedLiteral(bdString))
62 | } else {
63 | encoder.encodeString(bdString)
64 | }
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/ks3-jdk/src/jvmMain/kotlin/io/ks3/java/net/UriSerializer.kt:
--------------------------------------------------------------------------------
1 | package io.ks3.java.net
2 |
3 | import io.ks3.standard.stringSerializer
4 | import kotlinx.serialization.KSerializer
5 | import java.net.URI
6 |
7 | object UriSerializer : KSerializer by stringSerializer(::URI, URI::toASCIIString)
8 |
--------------------------------------------------------------------------------
/ks3-jdk/src/jvmMain/kotlin/io/ks3/java/net/UrlSerializer.kt:
--------------------------------------------------------------------------------
1 | package io.ks3.java.net
2 |
3 | import io.ks3.standard.stringSerializer
4 | import kotlinx.serialization.KSerializer
5 | import java.net.URL
6 |
7 | object UrlSerializer : KSerializer by stringSerializer(::URL, URL::toExternalForm)
8 |
--------------------------------------------------------------------------------
/ks3-jdk/src/jvmMain/kotlin/io/ks3/java/time/InstantAsStringSerializer.kt:
--------------------------------------------------------------------------------
1 | package io.ks3.java.time
2 |
3 | import io.ks3.standard.longSerializer
4 | import io.ks3.standard.stringSerializer
5 | import kotlinx.serialization.KSerializer
6 | import java.time.Instant
7 |
8 | /**
9 | * Serializes an [Instant] to its' [String] representation. Example: `"2023-01-01T18:49:00.123456Z"`
10 | */
11 | object InstantAsStringSerializer : KSerializer by stringSerializer(
12 | Instant::parse,
13 | Instant::toString,
14 | )
15 |
16 | /**
17 | * Serializes an [Instant] to its' epoch millis.
18 | * WARNING!️ Nanosecond precision is lost when encoding/decoding [Instant]s with this serializer.
19 | * Considering using [InstantAsStringSerializer] instead.
20 | */
21 | object InstantAsLongSerializer : KSerializer by longSerializer(
22 | Instant::ofEpochMilli,
23 | Instant::toEpochMilli,
24 | )
25 |
--------------------------------------------------------------------------------
/ks3-jdk/src/jvmMain/kotlin/io/ks3/java/time/LocalDateAsStringSerializer.kt:
--------------------------------------------------------------------------------
1 | package io.ks3.java.time
2 |
3 | import io.ks3.standard.stringSerializer
4 | import kotlinx.serialization.KSerializer
5 | import java.time.LocalDate
6 |
7 | /**
8 | * Uses [ISO8601](https://en.wikipedia.org/wiki/ISO_8601) format
9 | * Example: `"2020-01-01"`
10 | */
11 | object LocalDateAsStringSerializer : KSerializer by stringSerializer(
12 | { if (it.endsWith("T00:00:00")) LocalDate.parse(it.split("T")[0]) else LocalDate.parse(it) },
13 | LocalDate::toString,
14 | )
15 |
--------------------------------------------------------------------------------
/ks3-jdk/src/jvmMain/kotlin/io/ks3/java/time/LocalDateTimeAsStringSerializer.kt:
--------------------------------------------------------------------------------
1 | package io.ks3.java.time
2 |
3 | import io.ks3.standard.stringSerializer
4 | import kotlinx.serialization.KSerializer
5 | import java.time.LocalDateTime
6 |
7 | /**
8 | * Uses [ISO8601](https://en.wikipedia.org/wiki/ISO_8601) format, with no timezone information
9 | * Example: `"2020-01-01T18:49:00"`
10 | */
11 | object LocalDateTimeAsStringSerializer : KSerializer by stringSerializer(LocalDateTime::parse)
12 |
--------------------------------------------------------------------------------
/ks3-jdk/src/jvmMain/kotlin/io/ks3/java/time/LocalTimeAsStringSerializer.kt:
--------------------------------------------------------------------------------
1 | package io.ks3.java.time
2 |
3 | import io.ks3.standard.stringSerializer
4 | import kotlinx.serialization.KSerializer
5 | import java.time.LocalTime
6 |
7 | /**
8 | * Uses [ISO8601](https://en.wikipedia.org/wiki/ISO_8601) format, with no timezone information
9 | * Example: `"12:00:00"` means 12 'o clock at noon.
10 | */
11 | object LocalTimeAsStringSerializer : KSerializer by stringSerializer(LocalTime::parse)
12 |
--------------------------------------------------------------------------------
/ks3-jdk/src/jvmMain/kotlin/io/ks3/java/time/OffsetDateTimeAsStringSerializer.kt:
--------------------------------------------------------------------------------
1 | package io.ks3.java.time
2 |
3 | import io.ks3.standard.stringSerializer
4 | import kotlinx.serialization.KSerializer
5 | import java.time.OffsetDateTime
6 |
7 | /**
8 | * Serializes an [OffsetDateTime] to a string, including offset. Example: `"2020-01-01T18:49:00+01:00"`
9 | */
10 | object OffsetDateTimeAsStringSerializer : KSerializer by stringSerializer(OffsetDateTime::parse)
11 |
--------------------------------------------------------------------------------
/ks3-jdk/src/jvmMain/kotlin/io/ks3/java/time/YearMonthAsStringSerializer.kt:
--------------------------------------------------------------------------------
1 | package io.ks3.java.time
2 |
3 | import io.ks3.standard.stringSerializer
4 | import kotlinx.serialization.KSerializer
5 | import java.time.YearMonth
6 |
7 | /**
8 | * Serializes a [YearMonth] to a string. Example: `"2020-01"`
9 | */
10 | object YearMonthAsStringSerializer : KSerializer by stringSerializer(YearMonth::parse)
11 |
--------------------------------------------------------------------------------
/ks3-jdk/src/jvmMain/kotlin/io/ks3/java/time/YearSerializers.kt:
--------------------------------------------------------------------------------
1 | package io.ks3.java.time
2 |
3 | import io.ks3.standard.intSerializer
4 | import io.ks3.standard.stringSerializer
5 | import kotlinx.serialization.KSerializer
6 | import java.time.Year
7 |
8 | /**
9 | * Serializes a [Year] to a string. Example: `"2020"`
10 | */
11 | object YearAsStringSerializer : KSerializer by stringSerializer(
12 | { Year.of(it.toInt()) },
13 | )
14 |
15 | /**
16 | * Serializes a [Year] to an integer. Example: `2020`
17 | */
18 | object YearAsIntSerializer : KSerializer by intSerializer(
19 | Year::of,
20 | Year::getValue,
21 | )
22 |
--------------------------------------------------------------------------------
/ks3-jdk/src/jvmMain/kotlin/io/ks3/java/typealiases/Typealiases.kt:
--------------------------------------------------------------------------------
1 | @file:Suppress("unused")
2 |
3 | package io.ks3.java.typealiases
4 |
5 | import io.ks3.java.io.FilePathSerializer
6 | import io.ks3.java.net.UriSerializer
7 | import io.ks3.java.net.UrlSerializer
8 | import io.ks3.java.time.InstantAsLongSerializer
9 | import io.ks3.java.time.InstantAsStringSerializer
10 | import io.ks3.java.time.LocalDateAsStringSerializer
11 | import io.ks3.java.time.LocalDateTimeAsStringSerializer
12 | import io.ks3.java.time.LocalTimeAsStringSerializer
13 | import io.ks3.java.time.OffsetDateTimeAsStringSerializer
14 | import io.ks3.java.time.YearMonthAsStringSerializer
15 | import io.ks3.java.util.CurrencySerializer
16 | import io.ks3.java.util.LocaleSerializer
17 | import io.ks3.java.util.UuidSerializer
18 | import kotlinx.serialization.Serializable
19 | import java.io.File
20 | import java.net.URI
21 | import java.net.URL
22 | import java.time.Instant
23 | import java.time.LocalDate
24 | import java.time.LocalDateTime
25 | import java.time.LocalTime
26 | import java.time.OffsetDateTime
27 | import java.time.YearMonth
28 | import java.util.Currency
29 | import java.util.Locale
30 | import java.util.UUID
31 |
32 | // -- IO --
33 | typealias FileAsString =
34 | @Serializable(with = FilePathSerializer::class)
35 | File
36 |
37 | // -- NET --
38 | typealias UrlAsString =
39 | @Serializable(with = UrlSerializer::class)
40 | URL
41 |
42 | typealias UriAsString =
43 | @Serializable(with = UriSerializer::class)
44 | URI
45 |
46 | // -- TIME --
47 | typealias YearMonthAsString =
48 | @Serializable(with = YearMonthAsStringSerializer::class)
49 | YearMonth
50 |
51 | typealias InstantAsString =
52 | @Serializable(with = InstantAsStringSerializer::class)
53 | Instant
54 |
55 | typealias InstantAsLong =
56 | @Serializable(with = InstantAsLongSerializer::class)
57 | Instant
58 |
59 | typealias LocalDateAsString =
60 | @Serializable(with = LocalDateAsStringSerializer::class)
61 | LocalDate
62 |
63 | typealias LocalDateTimeAsString =
64 | @Serializable(with = LocalDateTimeAsStringSerializer::class)
65 | LocalDateTime
66 |
67 | typealias LocalTimeAsString =
68 | @Serializable(with = LocalTimeAsStringSerializer::class)
69 | LocalTime
70 |
71 | typealias OffsetDateTimeAsString =
72 | @Serializable(with = OffsetDateTimeAsStringSerializer::class)
73 | OffsetDateTime
74 |
75 | // -- UTIL --
76 | typealias LocaleAsString =
77 | @Serializable(with = LocaleSerializer::class)
78 | Locale
79 |
80 | typealias UuidAsString =
81 | @Serializable(with = UuidSerializer::class)
82 | UUID
83 |
84 | typealias CurrencyAsString =
85 | @Serializable(with = CurrencySerializer::class)
86 | Currency
87 |
--------------------------------------------------------------------------------
/ks3-jdk/src/jvmMain/kotlin/io/ks3/java/util/AtomicSerializers.kt:
--------------------------------------------------------------------------------
1 | package io.ks3.java.util
2 |
3 | import io.ks3.standard.intSerializer
4 | import kotlinx.serialization.KSerializer
5 | import kotlinx.serialization.descriptors.PrimitiveKind
6 | import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
7 | import kotlinx.serialization.encoding.Decoder
8 | import kotlinx.serialization.encoding.Encoder
9 | import java.util.concurrent.atomic.AtomicBoolean
10 | import java.util.concurrent.atomic.AtomicInteger
11 | import java.util.concurrent.atomic.AtomicLong
12 |
13 | // From https://github.com/Kotlin/kotlinx.serialization/blob/15c6b59d2175485f8f5f4563ff17610003459113/runtime/jvm/src/main/kotlin/kotlinx/serialization/java/AtomicSerializers.kt
14 |
15 | object AtomicIntegerSerializer : KSerializer by intSerializer(
16 | ::AtomicInteger,
17 | AtomicInteger::get,
18 | )
19 |
20 | object AtomicLongSerializer : KSerializer {
21 | override fun serialize(
22 | encoder: Encoder,
23 | value: AtomicLong,
24 | ) = encoder.encodeLong(value.get())
25 |
26 | override fun deserialize(decoder: Decoder): AtomicLong = AtomicLong(decoder.decodeLong())
27 |
28 | override val descriptor = PrimitiveSerialDescriptor(AtomicLongSerializer::class.qualifiedName!!, PrimitiveKind.LONG)
29 | }
30 |
31 | object AtomicBooleanSerializer : KSerializer {
32 | override fun serialize(
33 | encoder: Encoder,
34 | value: AtomicBoolean,
35 | ) = encoder.encodeBoolean(value.get())
36 |
37 | override fun deserialize(decoder: Decoder): AtomicBoolean = AtomicBoolean(decoder.decodeBoolean())
38 |
39 | override val descriptor = PrimitiveSerialDescriptor(AtomicBooleanSerializer::class.qualifiedName!!, PrimitiveKind.BOOLEAN)
40 | }
41 |
--------------------------------------------------------------------------------
/ks3-jdk/src/jvmMain/kotlin/io/ks3/java/util/CurrencySerializer.kt:
--------------------------------------------------------------------------------
1 | package io.ks3.java.util
2 |
3 | import io.ks3.standard.stringSerializer
4 | import kotlinx.serialization.KSerializer
5 | import java.util.Currency
6 |
7 | object CurrencySerializer : KSerializer by stringSerializer(Currency::getInstance)
8 |
--------------------------------------------------------------------------------
/ks3-jdk/src/jvmMain/kotlin/io/ks3/java/util/LocaleSerializer.kt:
--------------------------------------------------------------------------------
1 | package io.ks3.java.util
2 |
3 | import io.ks3.standard.stringSerializer
4 | import kotlinx.serialization.KSerializer
5 | import java.util.Locale
6 |
7 | object LocaleSerializer : KSerializer by stringSerializer(::Locale)
8 |
--------------------------------------------------------------------------------
/ks3-jdk/src/jvmMain/kotlin/io/ks3/java/util/UuidSerializer.kt:
--------------------------------------------------------------------------------
1 | package io.ks3.java.util
2 |
3 | import io.ks3.standard.stringSerializer
4 | import kotlinx.serialization.KSerializer
5 | import java.util.UUID
6 |
7 | object UuidSerializer : KSerializer by stringSerializer(UUID::fromString)
8 |
--------------------------------------------------------------------------------
/ks3-jdk/src/jvmTest/kotlin/io/ks3/java/io/FileSerializerTests.kt:
--------------------------------------------------------------------------------
1 | package io.ks3.java.io
2 |
3 | import io.kotest.core.spec.style.FunSpec
4 | import io.kotest.property.exhaustive.exhaustive
5 | import io.ks3.test.generateSerializerTests
6 | import java.io.File
7 |
8 | class FileSerializerTests : FunSpec(
9 | {
10 | val someFiles =
11 | listOf(
12 | "/",
13 | "/home/kotlin",
14 | "relative/to/linux",
15 | "C:",
16 | "C:\\Windows\\System32",
17 | "relative\\to\\windows",
18 | ).map(::File)
19 |
20 | include(generateSerializerTests(FilePathSerializer, someFiles.exhaustive()))
21 | },
22 | )
23 |
--------------------------------------------------------------------------------
/ks3-jdk/src/jvmTest/kotlin/io/ks3/java/math/BigDecimalSerializerTests.kt:
--------------------------------------------------------------------------------
1 | package io.ks3.java.math
2 |
3 | import io.kotest.core.spec.style.FunSpec
4 | import io.kotest.property.Arb
5 | import io.kotest.property.arbitrary.bigDecimal
6 | import io.kotest.property.arbitrary.double
7 | import io.kotest.property.arbitrary.map
8 | import io.kotest.property.arbitrary.withEdgecases
9 | import io.ks3.test.generateSerializerTests
10 | import kotlinx.serialization.ExperimentalSerializationApi
11 | import java.math.BigDecimal
12 |
13 | @OptIn(ExperimentalSerializationApi::class)
14 | class BigDecimalSerializerTests : FunSpec(
15 | {
16 | val someNumbers =
17 | listOf(
18 | "0",
19 | "-1E307",
20 | "-1E-307",
21 | // Very many decimal digits..
22 | "1.234349584529824359834295834958345892374892173498721349872398" +
23 | "457234985798234758927349872341908273978174239823475982743982734988",
24 | // Very many digits
25 | "98213749824375892743987239847298347982734598245982437598217394" +
26 | "8721398479817223987129483745782347823461982379812739817239873450.1",
27 | ).map(::BigDecimal)
28 | .map(BigDecimal::toPlainString) // Get rid of scientific notation
29 | .map(::BigDecimal)
30 |
31 | include(
32 | generateSerializerTests(
33 | BigDecimalAsStringSerializer,
34 | Arb.bigDecimal().withEdgecases(someNumbers),
35 | { "BigDecimalAsStringSerializer performs round-trip serialization" },
36 | ),
37 | )
38 |
39 | include(
40 | generateSerializerTests(
41 | BigDecimalAsJsonNumberSerializer,
42 | Arb.bigDecimal().withEdgecases(someNumbers),
43 | { "BigDecimalAsHighPrecisionNumberSerializer performs round-trip serialization" },
44 | ),
45 | )
46 |
47 | include(
48 | generateSerializerTests(
49 | BigDecimalAsDoubleSerializer,
50 | Arb.double()
51 | .withEdgecases(emptyList()) // get rid of NaN and Infinity
52 | .map { BigDecimal(it.toString()) },
53 | { "BigDecimalAsDoubleSerializer performs round-trip serialization" },
54 | ),
55 | )
56 | },
57 | )
58 |
--------------------------------------------------------------------------------
/ks3-jdk/src/jvmTest/kotlin/io/ks3/java/math/BigIntegerSerializerTests.kt:
--------------------------------------------------------------------------------
1 | package io.ks3.java.math
2 |
3 | import io.kotest.core.spec.style.FunSpec
4 | import io.kotest.property.Arb
5 | import io.kotest.property.arbitrary.bigInt
6 | import io.ks3.test.generateSerializerTests
7 | import kotlinx.serialization.ExperimentalSerializationApi
8 |
9 | @OptIn(ExperimentalSerializationApi::class)
10 | class BigIntegerSerializerTests : FunSpec(
11 | {
12 |
13 | include(
14 | generateSerializerTests(
15 | BigIntegerAsStringSerializer,
16 | Arb.bigInt(maxNumBits = 4096),
17 | { "BigIntegerAsStringSerializer performs round-trip serialization" },
18 | ),
19 | )
20 |
21 | include(
22 | generateSerializerTests(
23 | BigIntegerAsJsonLiteralSerializer,
24 | Arb.bigInt(maxNumBits = 4096),
25 | { "BigIntegerAsJsonLiteralSerializer performs round-trip serialization" },
26 | ),
27 | )
28 | },
29 | )
30 |
--------------------------------------------------------------------------------
/ks3-jdk/src/jvmTest/kotlin/io/ks3/java/net/UriSerializerTests.kt:
--------------------------------------------------------------------------------
1 | package io.ks3.java.net
2 |
3 | import io.kotest.core.spec.style.FunSpec
4 | import io.kotest.property.exhaustive.exhaustive
5 | import io.ks3.test.generateSerializerTests
6 | import java.net.URI
7 |
8 | class UriSerializerTests : FunSpec(
9 | {
10 | val someUris =
11 | listOf(
12 | "https://john.doe@www.example.com:123/forum/questions/?tag=networking&order=newest#top",
13 | "ldap://[2001:db8::7]/c=GB?objectClass?one",
14 | "mailto:John.Doe@example.com",
15 | "news:comp.infosystems.www.servers.unix",
16 | "tel:+1-816-555-1212",
17 | "telnet://192.0.2.16:80/",
18 | "urn:oasis:names:specification:docbook:dtd:xml:4.1.2",
19 | ).map(::URI)
20 |
21 | include(generateSerializerTests(UriSerializer, someUris.exhaustive()))
22 | },
23 | )
24 |
--------------------------------------------------------------------------------
/ks3-jdk/src/jvmTest/kotlin/io/ks3/java/net/UrlSerializerTests.kt:
--------------------------------------------------------------------------------
1 | package io.ks3.java.net
2 |
3 | import io.kotest.core.spec.style.FunSpec
4 | import io.kotest.property.exhaustive.exhaustive
5 | import io.ks3.test.generateSerializerTests
6 | import java.net.URL
7 |
8 | class UrlSerializerTests : FunSpec(
9 | {
10 | val someUrls =
11 | listOf(
12 | "http://localhost:1234/path?param=7",
13 | "https://www.google.com:1234",
14 | "ftp://www.dn.se",
15 | ).map(::URL)
16 |
17 | include(generateSerializerTests(UrlSerializer, someUrls.exhaustive()))
18 | },
19 | )
20 |
--------------------------------------------------------------------------------
/ks3-jdk/src/jvmTest/kotlin/io/ks3/java/time/InstantSerializersTests.kt:
--------------------------------------------------------------------------------
1 | package io.ks3.java.time
2 |
3 | import io.kotest.core.spec.style.FreeSpec
4 | import io.kotest.matchers.shouldBe
5 | import io.kotest.property.Arb
6 | import io.kotest.property.arbitrary.instant
7 | import io.ks3.test.generateSerializerTests
8 | import kotlinx.serialization.json.Json
9 | import java.time.Instant
10 | import java.time.Instant.ofEpochMilli
11 | import java.time.temporal.ChronoUnit.MILLIS
12 |
13 | class InstantSerializersTests : FreeSpec(
14 | {
15 | include(
16 | generateSerializerTests(
17 | InstantAsLongSerializer,
18 | // Only values that can be represented by a Long
19 | Arb.instant(ofEpochMilli(Long.MIN_VALUE)..ofEpochMilli(Long.MAX_VALUE)),
20 | { "Encodes Instant as epoch millis and back again" },
21 | ) { originalValue ->
22 | truncatedTo(MILLIS) shouldBe originalValue.truncatedTo(MILLIS) // We lose nanosecond parts when encoding to epoch millis
23 | },
24 | )
25 |
26 | include(generateSerializerTests(InstantAsStringSerializer, Arb.instant(), { "Encodes Instant as String and back again" }))
27 |
28 | "samples" {
29 | Json.encodeToString(InstantAsLongSerializer, Instant.ofEpochMilli(1666528056)) shouldBe "1666528056"
30 | Json.encodeToString(InstantAsStringSerializer, Instant.ofEpochMilli(1666528056)) shouldBe "\"1970-01-20T06:55:28.056Z\""
31 | }
32 | },
33 | )
34 |
--------------------------------------------------------------------------------
/ks3-jdk/src/jvmTest/kotlin/io/ks3/java/time/LocalDateAsStringSerializerTests.kt:
--------------------------------------------------------------------------------
1 | package io.ks3.java.time
2 |
3 | import io.kotest.assertions.throwables.shouldThrow
4 | import io.kotest.core.spec.style.FunSpec
5 | import io.kotest.matchers.shouldBe
6 | import io.kotest.property.Arb
7 | import io.kotest.property.arbitrary.localDate
8 | import io.kotest.property.checkAll
9 | import io.kotest.property.exhaustive.exhaustive
10 | import io.ks3.test.generateEncoders
11 | import io.ks3.test.generateSerializerTests
12 | import kotlinx.serialization.Serializable
13 | import kotlinx.serialization.decodeFromString
14 | import kotlinx.serialization.json.Json
15 | import java.time.LocalDate
16 | import java.time.format.DateTimeParseException
17 |
18 | class LocalDateAsStringSerializerTests : FunSpec(
19 | {
20 | val format = Json
21 | val (_, decoders) = format.generateEncoders(LocalDateAsStringSerializer)
22 |
23 | include(generateSerializerTests(LocalDateAsStringSerializer, Arb.localDate()))
24 |
25 | test("sample") {
26 | format.encodeToString(LocalDateAsStringSerializer, LocalDate.of(2022, 10, 25)) shouldBe
27 | "\"2022-10-25\""
28 | }
29 |
30 | test("handles timestamps with time included if, and only if, it contains no information") {
31 | format.decodeFromString(
32 | """
33 | {
34 | "date": "2021-01-01T00:00:00"
35 | }
36 | """.trimIndent(),
37 | ) shouldBe Sample(LocalDate.of(2021, 1, 1))
38 | }
39 |
40 | test("timestamps cause error") {
41 | checkAll(decoders.exhaustive()) { decode ->
42 | shouldThrow {
43 | "\"2021-01-01T12:30:45.000Z\"".decode()
44 | }
45 | }
46 | }
47 |
48 | test("Just date is fine as well") {
49 | checkAll(decoders.exhaustive()) { decode ->
50 | "\"2021-01-01\"".decode() shouldBe LocalDate.of(2021, 1, 1)
51 | }
52 | }
53 | },
54 | )
55 |
56 | @Serializable
57 | private data class Sample(
58 | @Serializable(with = LocalDateAsStringSerializer::class)
59 | val date: LocalDate? = null,
60 | )
61 |
--------------------------------------------------------------------------------
/ks3-jdk/src/jvmTest/kotlin/io/ks3/java/time/LocalDateTimeAsStringSerializerTests.kt:
--------------------------------------------------------------------------------
1 | package io.ks3.java.time
2 |
3 | import io.kotest.core.spec.style.FunSpec
4 | import io.kotest.matchers.shouldBe
5 | import io.kotest.property.Arb
6 | import io.kotest.property.arbitrary.localDateTime
7 | import io.ks3.test.generateSerializerTests
8 | import kotlinx.serialization.json.Json
9 | import java.time.LocalDateTime
10 |
11 | class LocalDateTimeAsStringSerializerTests : FunSpec(
12 | {
13 | include(generateSerializerTests(LocalDateTimeAsStringSerializer, Arb.localDateTime()))
14 |
15 | test("sample") {
16 | Json.encodeToString(LocalDateTimeAsStringSerializer, LocalDateTime.of(2022, 10, 25, 18, 59, 13)) shouldBe
17 | "\"2022-10-25T18:59:13\""
18 | }
19 | },
20 | )
21 |
--------------------------------------------------------------------------------
/ks3-jdk/src/jvmTest/kotlin/io/ks3/java/time/LocalTimeAsStringSerializerTests.kt:
--------------------------------------------------------------------------------
1 | package io.ks3.java.time
2 |
3 | import io.kotest.core.spec.style.FreeSpec
4 | import io.kotest.property.Arb
5 | import io.kotest.property.arbitrary.localTime
6 | import io.ks3.test.generateSerializerTests
7 |
8 | class LocalTimeAsStringSerializerTests : FreeSpec(
9 | {
10 | include(generateSerializerTests(LocalTimeAsStringSerializer, Arb.localTime()))
11 | },
12 | )
13 |
--------------------------------------------------------------------------------
/ks3-jdk/src/jvmTest/kotlin/io/ks3/java/time/OffsetDateTimeAsStringSerializerTests.kt:
--------------------------------------------------------------------------------
1 | package io.ks3.java.time
2 |
3 | import io.kotest.core.spec.style.FreeSpec
4 | import io.kotest.matchers.shouldBe
5 | import io.kotest.property.Arb
6 | import io.kotest.property.arbitrary.localTime
7 | import io.ks3.test.generateSerializerTests
8 | import kotlinx.serialization.json.Json
9 | import java.time.OffsetDateTime
10 | import java.time.ZoneOffset
11 |
12 | class OffsetDateTimeAsStringSerializerTests : FreeSpec(
13 | {
14 | include(generateSerializerTests(LocalTimeAsStringSerializer, Arb.localTime()))
15 |
16 | "Serialized form is expected" {
17 | Json.encodeToString(
18 | OffsetDateTimeAsStringSerializer,
19 | OffsetDateTime.of(
20 | 2022, 10, 25, 18, 59, 13, 0, ZoneOffset.ofHours(1),
21 | ),
22 | ) shouldBe "\"2022-10-25T18:59:13+01:00\""
23 | }
24 | },
25 | )
26 |
--------------------------------------------------------------------------------
/ks3-jdk/src/jvmTest/kotlin/io/ks3/java/time/YearMonthAsStringSerializerTests.kt:
--------------------------------------------------------------------------------
1 | package io.ks3.java.time
2 |
3 | import io.kotest.core.spec.style.FreeSpec
4 | import io.kotest.matchers.shouldBe
5 | import io.kotest.property.Arb
6 | import io.kotest.property.arbitrary.yearMonth
7 | import io.ks3.java.typealiases.YearMonthAsString
8 | import io.ks3.test.generateSerializerTests
9 | import kotlinx.serialization.Serializable
10 | import kotlinx.serialization.json.Json
11 | import java.time.YearMonth
12 |
13 | class YearMonthAsStringSerializerTests : FreeSpec(
14 | {
15 | include(generateSerializerTests(YearMonthAsStringSerializer, Arb.yearMonth()))
16 |
17 | "sample" {
18 | Json.encodeToString(YearMonthAsStringSerializer, YearMonth.of(2022, 10)) shouldBe "\"2022-10\""
19 | }
20 |
21 | "nullability with typealias" {
22 | @Serializable
23 | data class Appointment(
24 | val yearMonth: YearMonthAsString?,
25 | )
26 |
27 | Json.decodeFromString("""{ "yearMonth": null }""") shouldBe Appointment(null)
28 | Json.decodeFromString("""{ "yearMonth": "2022-10" }""") shouldBe Appointment(YearMonth.of(2022, 10))
29 | }
30 | },
31 | )
32 |
--------------------------------------------------------------------------------
/ks3-jdk/src/jvmTest/kotlin/io/ks3/java/time/YearSerializersTests.kt:
--------------------------------------------------------------------------------
1 | package io.ks3.java.time
2 |
3 | import io.kotest.core.spec.style.FunSpec
4 | import io.kotest.matchers.shouldBe
5 | import io.kotest.property.Arb
6 | import io.kotest.property.arbitrary.int
7 | import io.kotest.property.arbitrary.map
8 | import io.ks3.test.generateSerializerTests
9 | import kotlinx.serialization.json.Json
10 | import java.time.Year
11 |
12 | class YearSerializersTests : FunSpec(
13 | {
14 | val generator = Arb.int(min = -999999999, max = 999999999).map(Year::of)
15 |
16 | include(generateSerializerTests(YearAsIntSerializer, generator, { "Encodes Year as Int and back again" }))
17 | include(generateSerializerTests(YearAsStringSerializer, generator, { "Encodes Year as String and back again" }))
18 |
19 | test("Sample") {
20 | Json.encodeToString(YearAsStringSerializer, Year.of(2022)) shouldBe "\"2022\""
21 | Json.encodeToString(YearAsIntSerializer, Year.of(2022)) shouldBe "2022"
22 | }
23 | },
24 | )
25 |
--------------------------------------------------------------------------------
/ks3-jdk/src/jvmTest/kotlin/io/ks3/java/util/AtomicSerializersTests.kt:
--------------------------------------------------------------------------------
1 | package io.ks3.java.util
2 |
3 | import io.kotest.assertions.json.shouldEqualJson
4 | import io.kotest.core.spec.style.FunSpec
5 | import io.kotest.matchers.shouldBe
6 | import io.kotest.matchers.types.shouldBeInstanceOf
7 | import io.kotest.property.Arb
8 | import io.kotest.property.Exhaustive
9 | import io.kotest.property.arbitrary.int
10 | import io.kotest.property.arbitrary.long
11 | import io.kotest.property.arbitrary.map
12 | import io.kotest.property.exhaustive.boolean
13 | import io.kotest.property.exhaustive.map
14 | import io.ks3.test.generateSerializerTests
15 | import kotlinx.serialization.Serializable
16 | import kotlinx.serialization.json.Json
17 | import java.util.concurrent.atomic.AtomicBoolean
18 | import java.util.concurrent.atomic.AtomicInteger
19 | import java.util.concurrent.atomic.AtomicLong
20 |
21 | class AtomicSerializersTests : FunSpec(
22 | {
23 | include(
24 | generateSerializerTests(AtomicBooleanSerializer, Exhaustive.boolean().map(::AtomicBoolean)) { originalValue ->
25 | this
26 | .shouldBeInstanceOf()
27 | .get() shouldBe originalValue.get()
28 | },
29 | )
30 |
31 | include(
32 | generateSerializerTests(AtomicIntegerSerializer, Arb.int().map(::AtomicInteger)) { originalValue ->
33 | this
34 | .shouldBeInstanceOf()
35 | .get() shouldBe originalValue.get()
36 | },
37 | )
38 |
39 | include(
40 | generateSerializerTests(AtomicLongSerializer, Arb.long().map(::AtomicLong)) { originalValue ->
41 | this
42 | .shouldBeInstanceOf()
43 | .get() shouldBe originalValue.get()
44 | },
45 | )
46 |
47 | @Serializable
48 | data class Sample(
49 | @Serializable(with = AtomicBooleanSerializer::class)
50 | val bool: AtomicBoolean,
51 | @Serializable(with = AtomicIntegerSerializer::class)
52 | val int: AtomicInteger,
53 | @Serializable(with = AtomicLongSerializer::class)
54 | val long: AtomicLong,
55 | )
56 |
57 | test("sample") {
58 | Json.encodeToString(Sample.serializer(), Sample(AtomicBoolean(false), AtomicInteger(5), AtomicLong(22))) shouldEqualJson
59 | """
60 | {
61 | "bool": false,
62 | "int": 5,
63 | "long": 22
64 | }
65 | """.trimIndent()
66 | }
67 | },
68 | )
69 |
--------------------------------------------------------------------------------
/ks3-jdk/src/jvmTest/kotlin/io/ks3/java/util/CurrencySerializerTest.kt:
--------------------------------------------------------------------------------
1 | package io.ks3.java.util
2 |
3 | import io.kotest.core.spec.style.FreeSpec
4 | import io.kotest.matchers.shouldBe
5 | import io.kotest.property.Arb
6 | import io.kotest.property.arbitrary.map
7 | import io.kotest.property.arbitrary.of
8 | import io.ks3.test.generateSerializerTests
9 | import kotlinx.serialization.json.Json
10 | import java.util.Currency
11 |
12 | class CurrencySerializerTest :
13 | FreeSpec(
14 | {
15 | include(
16 | generateSerializerTests(
17 | CurrencySerializer,
18 | Arb.of(Currency.getAvailableCurrencies()).map { it },
19 | ),
20 | )
21 | "sample" {
22 | Json.encodeToString(CurrencySerializer, Currency.getInstance("AUD")) shouldBe "\"AUD\""
23 | }
24 | },
25 | )
26 |
--------------------------------------------------------------------------------
/ks3-jdk/src/jvmTest/kotlin/io/ks3/java/util/LocaleSerializerTests.kt:
--------------------------------------------------------------------------------
1 | package io.ks3.java.util
2 |
3 | import io.kotest.core.spec.style.FunSpec
4 | import io.kotest.property.Arb
5 | import io.kotest.property.arbitrary.locale
6 | import io.kotest.property.arbitrary.map
7 | import io.ks3.test.generateSerializerTests
8 | import java.util.Locale
9 |
10 | class LocaleSerializerTests : FunSpec(
11 | {
12 | include(
13 | generateSerializerTests(
14 | LocaleSerializer,
15 | Arb.locale().map { Locale(it) },
16 | ),
17 | )
18 | },
19 | )
20 |
--------------------------------------------------------------------------------
/ks3-jdk/src/jvmTest/kotlin/io/ks3/java/util/UuidSerializerTests.kt:
--------------------------------------------------------------------------------
1 | package io.ks3.java.util
2 |
3 | import io.kotest.core.spec.style.FunSpec
4 | import io.kotest.property.Arb
5 | import io.kotest.property.arbitrary.UUIDVersion
6 | import io.kotest.property.arbitrary.uuid
7 | import io.ks3.test.generateSerializerTests
8 |
9 | class UuidSerializerTests : FunSpec(
10 | {
11 | UUIDVersion.values().forEach { uuidVersion ->
12 | include(
13 | generateSerializerTests(
14 | UuidSerializer,
15 | Arb.uuid(uuidVersion),
16 | nameFn = { "Encode and decode UUID${uuidVersion.name}" },
17 | ),
18 | )
19 | }
20 | },
21 | )
22 |
--------------------------------------------------------------------------------
/ks3-standard/Module.md:
--------------------------------------------------------------------------------
1 | # Module ks3-standard
2 | * Serializers for standard Kotlin types, available on all (or at least, most) platforms.
3 | * Functions for more conveniently creating serializers.
4 |
5 | ## Serializer builders
6 |
7 | Serializer builders provide a convenient way of creating a serializer that turns `T` into a primitive, by simply providing the two
8 | conversion methods `(T) -> ` and `() -> T`.
9 |
10 | The return value of serializer builders is an anonymous object implementing `KSerializer`.
11 |
12 | ### Example:
13 |
14 | ```kotlin
15 | // Delegate the implementation to the return value of `stringSerializer(..)`
16 | object UriSerializer : KSerializer by stringSerializer(
17 | ::URI,
18 | URI::toASCIIString,
19 | )
20 | ```
21 |
22 | Here we create a serializer for `URI`. Our object implements `KSerializer` by delegating to the result of `stringSerializer(..)`.
23 |
24 | The first parameter, `::URI`, is the _decoding_ method which takes our primitive (`String`) and returns a `URI`.
25 |
26 | The second parameter is the _encoding_ method, which takes our type and returns the primitive.
27 |
28 | Since the methods to go back and forth from String to URI already exists in Java, this serializer is easily created using `stringSerializer`.
29 |
30 |
31 | ### Available builders are:
32 |
33 | * `intSerializer`
34 | * `longSerializer`
35 | * `stringSerializer`
36 |
--------------------------------------------------------------------------------
/ks3-standard/README.md:
--------------------------------------------------------------------------------
1 | Serializers for standard Kotlin types, available on all (or at least, most) platforms.
2 |
3 |
4 | ## Serializers
5 |
6 | ### EnumAsOrdinalSerializer
7 |
8 | Used to serialize/deserialize enums to their ordinal value.
9 |
10 | Example usage:
11 |
12 | ```kotlin
13 | enum class Shape {
14 | SQUARE, CIRCLE;
15 | }
16 |
17 | object ShapeSerializer: KSerializer by enumAsOrdinalSerializer()
18 | ```
19 |
20 | Now you can use the `ShapeSerializer` like you would any other.
21 |
--------------------------------------------------------------------------------
/ks3-standard/api/ks3-standard.api:
--------------------------------------------------------------------------------
1 | public final class io/ks3/standard/ByteArrayAsBase64StringSerializer : kotlinx/serialization/KSerializer {
2 | public static final field INSTANCE Lio/ks3/standard/ByteArrayAsBase64StringSerializer;
3 | public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
4 | public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)[B
5 | public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
6 | public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
7 | public fun serialize (Lkotlinx/serialization/encoding/Encoder;[B)V
8 | }
9 |
10 | public final class io/ks3/standard/EnumDecodingException : kotlinx/serialization/SerializationException {
11 | public fun ()V
12 | public fun (Ljava/lang/String;Ljava/lang/Throwable;)V
13 | public synthetic fun (Ljava/lang/String;Ljava/lang/Throwable;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
14 | }
15 |
16 | public final class io/ks3/standard/TupleDescriptor : kotlinx/serialization/descriptors/SerialDescriptor {
17 | public fun (Ljava/lang/String;[Lkotlinx/serialization/descriptors/SerialDescriptor;)V
18 | public fun equals (Ljava/lang/Object;)Z
19 | public fun getAnnotations ()Ljava/util/List;
20 | public fun getElementAnnotations (I)Ljava/util/List;
21 | public fun getElementDescriptor (I)Lkotlinx/serialization/descriptors/SerialDescriptor;
22 | public final fun getElementDescriptors ()[Lkotlinx/serialization/descriptors/SerialDescriptor;
23 | public fun getElementIndex (Ljava/lang/String;)I
24 | public fun getElementName (I)Ljava/lang/String;
25 | public fun getElementsCount ()I
26 | public fun getKind ()Lkotlinx/serialization/descriptors/SerialKind;
27 | public fun getSerialName ()Ljava/lang/String;
28 | public fun hashCode ()I
29 | public fun isElementOptional (I)Z
30 | public fun isInline ()Z
31 | public fun isNullable ()Z
32 | public fun toString ()Ljava/lang/String;
33 | }
34 |
35 |
--------------------------------------------------------------------------------
/ks3-standard/api/ks3-standard.klib.api:
--------------------------------------------------------------------------------
1 | // Klib ABI Dump
2 | // Targets: [iosArm64, iosSimulatorArm64, iosX64, js, linuxX64, macosArm64, macosX64, mingwX64, tvosArm64, tvosSimulatorArm64, tvosX64, watchosArm32, watchosArm64, watchosSimulatorArm64, watchosX64]
3 | // Rendering settings:
4 | // - Signature version: 2
5 | // - Show manifest properties: true
6 | // - Show declarations: true
7 |
8 | // Library unique name:
9 | final class io.ks3.standard/EnumDecodingException : kotlinx.serialization/SerializationException { // io.ks3.standard/EnumDecodingException|null[0]
10 | constructor (kotlin/String? =..., kotlin/Throwable? =...) // io.ks3.standard/EnumDecodingException.|(kotlin.String?;kotlin.Throwable?){}[0]
11 | }
12 |
13 | final object io.ks3.standard/ByteArrayAsBase64StringSerializer : kotlinx.serialization/KSerializer { // io.ks3.standard/ByteArrayAsBase64StringSerializer|null[0]
14 | final val descriptor // io.ks3.standard/ByteArrayAsBase64StringSerializer.descriptor|{}descriptor[0]
15 | final fun (): kotlinx.serialization.descriptors/SerialDescriptor // io.ks3.standard/ByteArrayAsBase64StringSerializer.descriptor.|(){}[0]
16 |
17 | final fun deserialize(kotlinx.serialization.encoding/Decoder): kotlin/ByteArray // io.ks3.standard/ByteArrayAsBase64StringSerializer.deserialize|deserialize(kotlinx.serialization.encoding.Decoder){}[0]
18 | final fun serialize(kotlinx.serialization.encoding/Encoder, kotlin/ByteArray) // io.ks3.standard/ByteArrayAsBase64StringSerializer.serialize|serialize(kotlinx.serialization.encoding.Encoder;kotlin.ByteArray){}[0]
19 | }
20 |
21 | final inline fun <#A: reified kotlin/Any> io.ks3.standard/objectWrappingSerializer(kotlin/String): kotlinx.serialization.json/JsonTransformingSerializer<#A> // io.ks3.standard/objectWrappingSerializer|objectWrappingSerializer(kotlin.String){0§}[0]
22 | final inline fun <#A: reified kotlin/Any?> io.ks3.standard/doubleSerializer(crossinline kotlin/Function1, crossinline kotlin/Function1<#A, kotlin/Double>, kotlin/String? =...): kotlinx.serialization/KSerializer<#A> // io.ks3.standard/doubleSerializer|doubleSerializer(kotlin.Function1;kotlin.Function1<0:0,kotlin.Double>;kotlin.String?){0§}[0]
23 | final inline fun <#A: reified kotlin/Any?> io.ks3.standard/intSerializer(crossinline kotlin/Function1, crossinline kotlin/Function1<#A, kotlin/Int>, kotlin/String? =...): kotlinx.serialization/KSerializer<#A> // io.ks3.standard/intSerializer|intSerializer(kotlin.Function1;kotlin.Function1<0:0,kotlin.Int>;kotlin.String?){0§}[0]
24 | final inline fun <#A: reified kotlin/Any?> io.ks3.standard/lenientJsonArraySerializer(kotlinx.serialization/KSerializer<#A>? =...): kotlinx.serialization/KSerializer> // io.ks3.standard/lenientJsonArraySerializer|lenientJsonArraySerializer(kotlinx.serialization.KSerializer<0:0>?){0§}[0]
25 | final inline fun <#A: reified kotlin/Any?> io.ks3.standard/longSerializer(crossinline kotlin/Function1, crossinline kotlin/Function1<#A, kotlin/Long>, kotlin/String? =...): kotlinx.serialization/KSerializer<#A> // io.ks3.standard/longSerializer|longSerializer(kotlin.Function1;kotlin.Function1<0:0,kotlin.Long>;kotlin.String?){0§}[0]
26 | final inline fun <#A: reified kotlin/Any?> io.ks3.standard/sortedCollectionSerializer(kotlin/Comparator): kotlinx.serialization/KSerializer> // io.ks3.standard/sortedCollectionSerializer|sortedCollectionSerializer(kotlin.Comparator){0§}[0]
27 | final inline fun <#A: reified kotlin/Any?> io.ks3.standard/sortedListSerializer(kotlin/Comparator): kotlinx.serialization/KSerializer> // io.ks3.standard/sortedListSerializer|sortedListSerializer(kotlin.Comparator){0§}[0]
28 | final inline fun <#A: reified kotlin/Any?> io.ks3.standard/sortedSetSerializer(kotlin/Comparator): kotlinx.serialization/KSerializer> // io.ks3.standard/sortedSetSerializer|sortedSetSerializer(kotlin.Comparator){0§}[0]
29 | final inline fun <#A: reified kotlin/Any?> io.ks3.standard/stringSerializer(crossinline kotlin/Function1, crossinline kotlin/Function1<#A, kotlin/String> =..., kotlin/String? =...): kotlinx.serialization/KSerializer<#A> // io.ks3.standard/stringSerializer|stringSerializer(kotlin.Function1;kotlin.Function1<0:0,kotlin.String>;kotlin.String?){0§}[0]
30 | final inline fun <#A: reified kotlin/Enum<#A>> io.ks3.standard/enumAsOrdinalSerializer(): kotlinx.serialization/KSerializer<#A> // io.ks3.standard/enumAsOrdinalSerializer|enumAsOrdinalSerializer(){0§>}[0]
31 |
--------------------------------------------------------------------------------
/ks3-standard/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("ks3.conventions.lang.kotlin-multiplatform-js")
3 | id("ks3.conventions.lang.kotlin-multiplatform-jvm")
4 | id("ks3.conventions.lang.kotlin-multiplatform-native")
5 | id("ks3.conventions.publishing.maven-publish")
6 | }
7 |
8 | dokkatoo {
9 | dokkatooSourceSets.configureEach {
10 | includes.from("Module.md")
11 | }
12 |
13 | dokkatooSourceSets.named("commonMain") {
14 | sourceLink {
15 | localDirectory.set(file("src/commonMain/kotlin"))
16 | remoteUrl("https://github.com/Kantis/ks3/blob/main/ks3-standard/src/commonMain/kotlin")
17 | remoteLineSuffix.set("#L")
18 | }
19 | }
20 |
21 | dokkatooSourceSets.named("jvmMain") {
22 | sourceLink {
23 | localDirectory.set(file("src/jvmMain/kotlin"))
24 | remoteUrl("https://github.com/Kantis/ks3/blob/main/ks3-standard/src/jvmMain/kotlin")
25 | remoteLineSuffix.set("#L")
26 | }
27 | }
28 |
29 | modulePath.set("ks3-standard") // match the original dokka default
30 | }
31 |
32 | tasks.withType().configureEach {
33 | generator.dokkaSourceSets.configureEach {
34 | sourceSetScope.set(":ks3-standard:dokkaHtmlPartial")
35 | }
36 | }
37 |
38 | kotlin {
39 | sourceSets {
40 | commonMain {
41 | dependencies {
42 | implementation(projects.ks3Core)
43 | implementation(libs.kotlinxSerialization.core)
44 | implementation(libs.kotlinxSerialization.json)
45 | }
46 | }
47 |
48 | commonTest {
49 | dependencies {
50 | implementation(libs.kotest.frameworkEngine)
51 | implementation(libs.kotest.assertionsCore)
52 | implementation(libs.kotest.assertionsJson)
53 | implementation(libs.kotest.property)
54 |
55 | implementation(libs.okio.core)
56 |
57 | implementation(libs.kotlinxSerialization.json)
58 | }
59 | }
60 |
61 | if (ks3Settings.enableKotlinJvm.get()) {
62 | jvmMain {
63 | dependencies {
64 | implementation(kotlin("reflect"))
65 | implementation(projects.ks3Core)
66 | }
67 | }
68 |
69 | jvmTest {
70 | dependencies {
71 | implementation(libs.kotest.runnerJunit5)
72 | }
73 | }
74 | }
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/ks3-standard/src/commonMain/kotlin/io/ks3/standard/ByteArrayAsBase64StringSerializer.kt:
--------------------------------------------------------------------------------
1 | package io.ks3.standard
2 |
3 | import kotlinx.serialization.KSerializer
4 | import kotlinx.serialization.Serializable
5 | import kotlinx.serialization.descriptors.PrimitiveKind
6 | import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
7 | import kotlinx.serialization.descriptors.SerialDescriptor
8 | import kotlinx.serialization.encoding.Decoder
9 | import kotlinx.serialization.encoding.Encoder
10 | import kotlin.io.encoding.Base64
11 | import kotlin.io.encoding.ExperimentalEncodingApi
12 |
13 | typealias ByteArrayAsBase64String =
14 | @Serializable(with = ByteArrayAsBase64StringSerializer::class)
15 | ByteArray
16 |
17 | @OptIn(ExperimentalEncodingApi::class)
18 | object ByteArrayAsBase64StringSerializer : KSerializer {
19 | override val descriptor: SerialDescriptor =
20 | PrimitiveSerialDescriptor(
21 | "io.ks3.standard.ByteArrayAsBase64StringSerializer",
22 | PrimitiveKind.STRING,
23 | )
24 |
25 | override fun deserialize(decoder: Decoder): ByteArray {
26 | return Base64.decode(decoder.decodeString())
27 | }
28 |
29 | override fun serialize(
30 | encoder: Encoder,
31 | value: ByteArray,
32 | ) {
33 | encoder.encodeString(Base64.encode(value))
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/ks3-standard/src/commonMain/kotlin/io/ks3/standard/DoubleSerializer.kt:
--------------------------------------------------------------------------------
1 | package io.ks3.standard
2 |
3 | import kotlinx.serialization.KSerializer
4 | import kotlinx.serialization.descriptors.PrimitiveKind
5 | import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
6 | import kotlinx.serialization.encoding.Decoder
7 | import kotlinx.serialization.encoding.Encoder
8 |
9 | inline fun doubleSerializer(
10 | crossinline decode: (Double) -> T,
11 | crossinline encode: (T) -> Double,
12 | nameOverride: String? = null,
13 | ): KSerializer =
14 | object : KSerializer {
15 | override val descriptor = PrimitiveSerialDescriptor(nameOverride ?: T::class.simpleName!!, PrimitiveKind.DOUBLE)
16 |
17 | override fun deserialize(decoder: Decoder) = decode(decoder.decodeDouble())
18 |
19 | override fun serialize(
20 | encoder: Encoder,
21 | value: T,
22 | ) = encoder.encodeDouble(encode(value))
23 | }
24 |
--------------------------------------------------------------------------------
/ks3-standard/src/commonMain/kotlin/io/ks3/standard/EnumAsOrdinalSerializer.kt:
--------------------------------------------------------------------------------
1 | package io.ks3.standard
2 |
3 | import kotlinx.serialization.KSerializer
4 | import kotlinx.serialization.descriptors.PrimitiveKind
5 | import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
6 | import kotlinx.serialization.encoding.Decoder
7 | import kotlinx.serialization.encoding.Encoder
8 |
9 | inline fun > enumAsOrdinalSerializer() =
10 | object : KSerializer {
11 | val values = enumValues()
12 | val name = T::class.simpleName
13 |
14 | override val descriptor = PrimitiveSerialDescriptor("io.ks3.standard.EnumAsOrdinal", PrimitiveKind.INT)
15 |
16 | override fun deserialize(decoder: Decoder): T {
17 | val i = decoder.decodeInt()
18 | if (i !in values.indices) {
19 | throw EnumDecodingException("Invalid ordinal value $i for $name, must be in ${values.indices}")
20 | }
21 | return values[i]
22 | }
23 |
24 | override fun serialize(
25 | encoder: Encoder,
26 | value: T,
27 | ) {
28 | return encoder.encodeInt(values.indexOf(value))
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/ks3-standard/src/commonMain/kotlin/io/ks3/standard/EnumDecodingException.kt:
--------------------------------------------------------------------------------
1 | package io.ks3.standard
2 |
3 | import kotlinx.serialization.SerializationException
4 |
5 | class EnumDecodingException(message: String? = null, cause: Throwable? = null) : SerializationException(message, cause)
6 |
--------------------------------------------------------------------------------
/ks3-standard/src/commonMain/kotlin/io/ks3/standard/IntSerializer.kt:
--------------------------------------------------------------------------------
1 | package io.ks3.standard
2 |
3 | import kotlinx.serialization.KSerializer
4 | import kotlinx.serialization.descriptors.PrimitiveKind
5 | import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
6 | import kotlinx.serialization.encoding.Decoder
7 | import kotlinx.serialization.encoding.Encoder
8 |
9 | inline fun intSerializer(
10 | crossinline decode: (Int) -> T,
11 | crossinline encode: (T) -> Int,
12 | nameOverride: String? = null,
13 | ): KSerializer =
14 | object : KSerializer {
15 | override val descriptor = PrimitiveSerialDescriptor(nameOverride ?: T::class.simpleName!!, PrimitiveKind.LONG)
16 |
17 | override fun deserialize(decoder: Decoder) = decode(decoder.decodeInt())
18 |
19 | override fun serialize(
20 | encoder: Encoder,
21 | value: T,
22 | ) = encoder.encodeInt(encode(value))
23 | }
24 |
--------------------------------------------------------------------------------
/ks3-standard/src/commonMain/kotlin/io/ks3/standard/LenientJsonArraySerializer.kt:
--------------------------------------------------------------------------------
1 | package io.ks3.standard
2 |
3 | import io.ks3.core.ExperimentalKs3
4 | import kotlinx.serialization.KSerializer
5 | import kotlinx.serialization.SerializationException
6 | import kotlinx.serialization.SerializationStrategy
7 | import kotlinx.serialization.builtins.ListSerializer
8 | import kotlinx.serialization.descriptors.SerialDescriptor
9 | import kotlinx.serialization.encoding.Decoder
10 | import kotlinx.serialization.encoding.Encoder
11 | import kotlinx.serialization.json.JsonDecoder
12 | import kotlinx.serialization.json.jsonArray
13 | import kotlinx.serialization.serializer
14 |
15 | /**
16 | * Creates a serializer that will parse a JSON array into a list of [T] elements, ignoring any elements that fail to parse.
17 | *
18 | * Example:
19 | * ```
20 | * val lenientIntListSerializer = lenientJsonArraySerializer()
21 | * val numbers = Json.decodeFromString(lenientIntListSerializer, "[1,2,3, \"foo\",5]")
22 | *
23 | * println(numbers) // > [1, 2, 3, 5]
24 | * ```
25 | */
26 | @ExperimentalKs3
27 | inline fun lenientJsonArraySerializer(explicitElementSerializer: KSerializer? = null) =
28 | object : KSerializer> {
29 | private val elementSerializer = explicitElementSerializer ?: serializer()
30 | private val serializer: SerializationStrategy> = ListSerializer(elementSerializer)
31 | override val descriptor: SerialDescriptor = serializer.descriptor
32 |
33 | override fun deserialize(decoder: Decoder): List {
34 | require(decoder is JsonDecoder) { "This deserializer can only be used with Json" }
35 | val json = decoder.json
36 | val list = decoder.decodeJsonElement()
37 | return list
38 | .jsonArray
39 | .mapNotNull {
40 | try {
41 | json.decodeFromJsonElement(elementSerializer, it)
42 | } catch (e: SerializationException) {
43 | null
44 | } catch (e: IllegalArgumentException) {
45 | null
46 | }
47 | }
48 | }
49 |
50 | override fun serialize(
51 | encoder: Encoder,
52 | value: List,
53 | ): Unit = serializer.serialize(encoder, value)
54 | }
55 |
--------------------------------------------------------------------------------
/ks3-standard/src/commonMain/kotlin/io/ks3/standard/LongSerializer.kt:
--------------------------------------------------------------------------------
1 | package io.ks3.standard
2 |
3 | import kotlinx.serialization.KSerializer
4 | import kotlinx.serialization.descriptors.PrimitiveKind
5 | import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
6 | import kotlinx.serialization.encoding.Decoder
7 | import kotlinx.serialization.encoding.Encoder
8 |
9 | inline fun longSerializer(
10 | crossinline decode: (Long) -> T,
11 | crossinline encode: (T) -> Long,
12 | nameOverride: String? = null,
13 | ): KSerializer =
14 | object : KSerializer {
15 | override val descriptor = PrimitiveSerialDescriptor(nameOverride ?: T::class.simpleName!!, PrimitiveKind.LONG)
16 |
17 | override fun deserialize(decoder: Decoder) = decode(decoder.decodeLong())
18 |
19 | override fun serialize(
20 | encoder: Encoder,
21 | value: T,
22 | ) = encoder.encodeLong(encode(value))
23 | }
24 |
--------------------------------------------------------------------------------
/ks3-standard/src/commonMain/kotlin/io/ks3/standard/ObjectWrappingSerializer.kt:
--------------------------------------------------------------------------------
1 | package io.ks3.standard
2 |
3 | import kotlinx.serialization.json.JsonElement
4 | import kotlinx.serialization.json.JsonTransformingSerializer
5 | import kotlinx.serialization.json.buildJsonObject
6 | import kotlinx.serialization.json.jsonObject
7 | import kotlinx.serialization.serializer
8 |
9 | /**
10 | * Wraps the inner serializer by putting the entire structure into a JSON object, keyed by [fieldName].
11 | *
12 | * Example:
13 | * ```kotlin
14 | * val serializer = objectWrappingSerializer("someFieldName")
15 | * data class Person(val name: String, val age: Int)
16 | *
17 | * println(Json.encodeToString(serializer, Person("John", 42)))
18 | * // > { "someFieldName": { "name" : "John" , "age" : 42 } }
19 | * ```
20 | *
21 | * Can be used to wrap a value class in a JSON object, for instance if an API requires you to send ` { "id": 42 } ` instead of just `42`.
22 | */
23 | inline fun objectWrappingSerializer(fieldName: String) =
24 | object : JsonTransformingSerializer(serializer()) {
25 | override fun transformSerialize(element: JsonElement): JsonElement {
26 | return buildJsonObject {
27 | put(fieldName, element)
28 | }
29 | }
30 |
31 | override fun transformDeserialize(element: JsonElement): JsonElement {
32 | return element.jsonObject[fieldName]!!
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/ks3-standard/src/commonMain/kotlin/io/ks3/standard/SortedCollectionSerializer.kt:
--------------------------------------------------------------------------------
1 | package io.ks3.standard
2 |
3 | import io.ks3.core.ExperimentalKs3
4 | import kotlinx.serialization.KSerializer
5 | import kotlinx.serialization.encoding.Encoder
6 | import kotlinx.serialization.serializer
7 |
8 | /**
9 | * Serializers that sort collections / lists / sets with the given [comparator] upon serialization.
10 | *
11 | * Example:
12 | * ```kotlin
13 | * class SortedStringListSerializer :
14 | * KSerializer> by sortedListSerializer(compareBy { it })
15 | *
16 | * @Serializable
17 | * data class Friends(
18 | * @Serializable(SortedStringListSerializer::class)
19 | * val names: List
20 | * )
21 | * ```
22 | */
23 | @ExperimentalKs3
24 | inline fun sortedCollectionSerializer(comparator: Comparator): KSerializer> {
25 | val delegate = serializer>()
26 | return object : KSerializer> by delegate {
27 | override fun serialize(
28 | encoder: Encoder,
29 | value: Collection,
30 | ) = delegate.serialize(encoder, value.sortedWith(comparator))
31 | }
32 | }
33 |
34 | @ExperimentalKs3
35 | inline fun sortedListSerializer(comparator: Comparator): KSerializer> {
36 | val delegate = serializer>()
37 | return object : KSerializer> by delegate {
38 | override fun serialize(
39 | encoder: Encoder,
40 | value: List,
41 | ) = delegate.serialize(encoder, value.sortedWith(comparator))
42 | }
43 | }
44 |
45 | @ExperimentalKs3
46 | inline fun sortedSetSerializer(comparator: Comparator): KSerializer> {
47 | val delegate = serializer>()
48 | return object : KSerializer> by delegate {
49 | override fun serialize(
50 | encoder: Encoder,
51 | value: Set,
52 | ) = delegate.serialize(encoder, value.sortedWith(comparator).toSet())
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/ks3-standard/src/commonMain/kotlin/io/ks3/standard/StringSerializer.kt:
--------------------------------------------------------------------------------
1 | package io.ks3.standard
2 |
3 | import kotlinx.serialization.KSerializer
4 | import kotlinx.serialization.descriptors.PrimitiveKind
5 | import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
6 | import kotlinx.serialization.encoding.Decoder
7 | import kotlinx.serialization.encoding.Encoder
8 |
9 | inline fun stringSerializer(
10 | crossinline decode: (String) -> T,
11 | crossinline encode: (T) -> String = { it.toString() },
12 | nameOverride: String? = null,
13 | ): KSerializer =
14 | object : KSerializer {
15 | override val descriptor = PrimitiveSerialDescriptor(nameOverride ?: T::class.simpleName!!, PrimitiveKind.STRING)
16 |
17 | override fun deserialize(decoder: Decoder) = decode(decoder.decodeString())
18 |
19 | override fun serialize(
20 | encoder: Encoder,
21 | value: T,
22 | ) = encoder.encodeString(encode(value))
23 | }
24 |
--------------------------------------------------------------------------------
/ks3-standard/src/commonTest/kotlin/io/ks3/standard/ByteArrayAsBase64StringSerializerTest.kt:
--------------------------------------------------------------------------------
1 | package io.ks3.standard
2 |
3 | import io.kotest.assertions.throwables.shouldThrowWithMessage
4 | import io.kotest.core.spec.style.FreeSpec
5 | import io.kotest.matchers.shouldBe
6 | import kotlinx.serialization.json.Json
7 |
8 | class ByteArrayAsBase64StringSerializerTest : FreeSpec(
9 | {
10 | val format = Json
11 |
12 | fun ByteArray.serializeBase64() = format.encodeToString(ByteArrayAsBase64StringSerializer, this)
13 |
14 | fun String.deserializeBase64() = format.decodeFromString(ByteArrayAsBase64StringSerializer, this)
15 |
16 | "Encode to base64 string" {
17 | "test".encodeToByteArray().serializeBase64() shouldBe "\"dGVzdA==\""
18 | }
19 |
20 | "Decode from base64 string" {
21 | "\"dGVzdA==\"".deserializeBase64() shouldBe "test".encodeToByteArray()
22 | }
23 |
24 | "Non-base64 content" {
25 | shouldThrowWithMessage("Invalid symbol '['(133) at index 0") {
26 | "\"[][]\"".deserializeBase64()
27 | }
28 | }
29 |
30 | "Invalid base64" {
31 | shouldThrowWithMessage("The last unit of input does not have enough bits") {
32 | "\"A===\"".deserializeBase64()
33 | }
34 | }
35 | },
36 | )
37 |
--------------------------------------------------------------------------------
/ks3-standard/src/commonTest/kotlin/io/ks3/standard/EnumAsOrdinalSerializerTest.kt:
--------------------------------------------------------------------------------
1 | package io.ks3.standard
2 |
3 | import io.kotest.assertions.json.shouldEqualJson
4 | import io.kotest.assertions.throwables.shouldThrow
5 | import io.kotest.core.spec.style.FreeSpec
6 | import io.kotest.matchers.shouldBe
7 | import kotlinx.serialization.KSerializer
8 | import kotlinx.serialization.Serializable
9 | import kotlinx.serialization.decodeFromString
10 | import kotlinx.serialization.encodeToString
11 | import kotlinx.serialization.json.Json
12 |
13 | object ShapeSerializer : KSerializer by enumAsOrdinalSerializer()
14 |
15 | class EnumAsOrdinalSerializerTest : FreeSpec(
16 | {
17 |
18 | val serializer = ShapeSerializer
19 | val format = Json
20 |
21 | @Serializable
22 | data class EnumWrapper(
23 | @Serializable(with = ShapeSerializer::class)
24 | val shape: Shape?,
25 | )
26 |
27 | "Given a nullable enum property, Then null should be encoded" {
28 | format.encodeToString(EnumWrapper(null)) shouldEqualJson
29 | """
30 | {
31 | "shape": null
32 | }
33 | """.trimIndent()
34 | }
35 |
36 | "Given a nullable enum property, Then non-null should be encoded" {
37 | format.encodeToString(EnumWrapper(Shape.SQUARE)) shouldEqualJson
38 | """
39 | {
40 | "shape": 1
41 | }
42 | """.trimIndent()
43 | }
44 |
45 | "Given a nullable enum property, Then null should be decoded" {
46 | format.decodeFromString("{ \"shape\": null }") shouldBe EnumWrapper(null)
47 | }
48 |
49 | "Given a nullable enum property, Then non-null value should be decoded" {
50 | format.decodeFromString("{ \"shape\": 1 }") shouldBe EnumWrapper(Shape.SQUARE)
51 | }
52 |
53 | "Encodes to ordinal value" {
54 | format.encodeToString(serializer, Shape.SQUARE) shouldBe "1"
55 | format.encodeToString(serializer, Shape.CIRCLE) shouldBe "0"
56 | }
57 |
58 | "Decodes from ordinal value" {
59 | format.decodeFromString(serializer, "1") shouldBe Shape.SQUARE
60 | format.decodeFromString(serializer, "0") shouldBe Shape.CIRCLE
61 | }
62 |
63 | "Decodes quoted value" {
64 | format.decodeFromString(serializer, "\"1\"") shouldBe Shape.SQUARE
65 | }
66 |
67 | "Throws exception on invalid ordinal" {
68 | shouldThrow {
69 | format.decodeFromString(serializer, "-1")
70 | }.message shouldBe "Invalid ordinal value -1 for Shape, must be in 0..1"
71 |
72 | shouldThrow {
73 | format.decodeFromString(serializer, "2")
74 | }.message shouldBe "Invalid ordinal value 2 for Shape, must be in 0..1"
75 | }
76 | },
77 | )
78 |
79 | private enum class Shape {
80 | CIRCLE,
81 | SQUARE,
82 | }
83 |
--------------------------------------------------------------------------------
/ks3-standard/src/commonTest/kotlin/io/ks3/standard/LenientJsonArraySerializerTest.kt:
--------------------------------------------------------------------------------
1 | package io.ks3.standard
2 |
3 | import io.kotest.core.spec.style.StringSpec
4 | import io.kotest.matchers.shouldBe
5 | import kotlinx.serialization.Serializable
6 | import kotlinx.serialization.json.Json
7 |
8 | class LenientJsonArraySerializerTest : StringSpec(
9 | {
10 | val json = Json
11 |
12 | "Single bad element is discarded" {
13 | json.decodeFromString(lenientJsonArraySerializer(), "[1,2,3, \"foo\",5]") shouldBe listOf(1, 2, 3, 5)
14 | }
15 |
16 | "Entire list can be discarded" {
17 | json.decodeFromString(lenientJsonArraySerializer(), "[\"foo\",\"bar\"]") shouldBe listOf()
18 | }
19 |
20 | "Should work with nested lists" {
21 | json.decodeFromString(
22 | // We need to pass the inner serializer so resilience will be applied within the nested list deserialization
23 | lenientJsonArraySerializer>(
24 | lenientJsonArraySerializer(),
25 | ),
26 | "[[1,2,3], [4,\"foo\",6], [7,8,9]]",
27 | ) shouldBe
28 | listOf(
29 | listOf(1, 2, 3),
30 | listOf(4, 6),
31 | listOf(7, 8, 9),
32 | )
33 | }
34 |
35 | "Should omit data classes when they fail to deserialize" {
36 | @Serializable
37 | data class Foo(
38 | val bar: String,
39 | val baz: Int,
40 | )
41 |
42 | json.decodeFromString(
43 | lenientJsonArraySerializer(),
44 | """
45 | [
46 | {"bar": "bar", "baz":1},
47 | {"bar": "bar"},
48 | {"bar": "bar", "baz": "x"}
49 | ]
50 | """.trimIndent(),
51 | ) shouldBe listOf(Foo("bar", 1))
52 | }
53 | },
54 | )
55 |
--------------------------------------------------------------------------------
/ks3-standard/src/commonTest/kotlin/io/ks3/standard/SortedCollectionSerializerTest.kt:
--------------------------------------------------------------------------------
1 | package io.ks3.standard
2 |
3 | import io.kotest.core.spec.style.StringSpec
4 | import io.kotest.matchers.shouldBe
5 | import io.ks3.core.ExperimentalKs3
6 | import kotlinx.serialization.Serializable
7 | import kotlinx.serialization.json.Json
8 |
9 | @OptIn(ExperimentalKs3::class)
10 | class SortedCollectionSerializerTest : StringSpec(
11 | {
12 | val json = Json
13 |
14 | "A list of strings can be serialized by natural ordering" {
15 | json.encodeToString(
16 | sortedListSerializer(
17 | compareBy { it },
18 | ),
19 | listOf("c", "b", "a"),
20 | ) shouldBe
21 | """
22 | ["a","b","c"]
23 | """.trimIndent()
24 | }
25 |
26 | "A list of non-comparable objects can be serialized by custom ordering" {
27 | @Serializable
28 | data class Friend(val name: String, val age: Int)
29 |
30 | val friends =
31 | listOf(
32 | Friend("Alice", 20),
33 | Friend("Bob", 30),
34 | Friend("Charles", 40),
35 | )
36 |
37 | json.encodeToString(sortedListSerializer(compareByDescending { it.age }), friends) shouldBe
38 | """
39 | [{"name":"Charles","age":40},{"name":"Bob","age":30},{"name":"Alice","age":20}]
40 | """.trimIndent()
41 | }
42 | },
43 | )
44 |
--------------------------------------------------------------------------------
/ks3-standard/src/jvmMain/kotlin/io/ks3/standard/TupleDescriptor.kt:
--------------------------------------------------------------------------------
1 | package io.ks3.standard
2 |
3 | import io.ks3.core.Ks3Internal
4 | import kotlinx.serialization.ExperimentalSerializationApi
5 | import kotlinx.serialization.descriptors.SerialDescriptor
6 | import kotlinx.serialization.descriptors.SerialKind
7 | import kotlinx.serialization.descriptors.StructureKind
8 |
9 | @Ks3Internal
10 | @ExperimentalSerializationApi
11 | class TupleDescriptor(override val serialName: String, vararg val elementDescriptors: SerialDescriptor) :
12 | SerialDescriptor {
13 | override val kind: SerialKind get() = StructureKind.LIST
14 | override val elementsCount: Int = elementDescriptors.size
15 |
16 | override fun getElementName(index: Int): String = index.toString()
17 |
18 | override fun getElementIndex(name: String): Int = name.toIntOrNull() ?: throw IllegalArgumentException("$name is not a valid list index")
19 |
20 | override fun isElementOptional(index: Int): Boolean {
21 | require(index >= 0) { "Illegal index $index, $serialName expects only non-negative indices" }
22 | return false
23 | }
24 |
25 | override fun getElementAnnotations(index: Int): List {
26 | require(index >= 0) { "Illegal index $index, $serialName expects only non-negative indices" }
27 | return emptyList()
28 | }
29 |
30 | override fun getElementDescriptor(index: Int): SerialDescriptor {
31 | require(index >= 0) { "Illegal index $index, $serialName expects only non-negative indices" }
32 | return elementDescriptors[index]
33 | }
34 |
35 | override fun equals(other: Any?): Boolean {
36 | if (this === other) return true
37 | if (other !is TupleDescriptor) return false
38 | return elementDescriptors.contentEquals(other.elementDescriptors) && serialName == other.serialName
39 | }
40 |
41 | override fun hashCode(): Int {
42 | return elementDescriptors.hashCode() * 31 + serialName.hashCode()
43 | }
44 |
45 | override fun toString(): String = "$serialName(${elementDescriptors.joinToString()})"
46 | }
47 |
--------------------------------------------------------------------------------
/ks3-standard/src/jvmMain/kotlin/io/ks3/standard/TupleSerializer.kt:
--------------------------------------------------------------------------------
1 | package io.ks3.standard
2 |
3 | import kotlinx.serialization.ExperimentalSerializationApi
4 | import kotlinx.serialization.KSerializer
5 | import kotlinx.serialization.encoding.Decoder
6 | import kotlinx.serialization.encoding.Encoder
7 | import kotlinx.serialization.encoding.decodeStructure
8 | import kotlinx.serialization.encoding.encodeStructure
9 | import kotlinx.serialization.serializer
10 | import kotlin.reflect.KParameter
11 | import kotlin.reflect.KProperty1
12 | import kotlin.reflect.full.isSubtypeOf
13 |
14 | /**
15 | * Creates a serializer that encodes select properties of [T] as a tuple.
16 | *
17 | * For deserialization to work, the following conditions must be fulfilled:
18 | * * There must be a constructor where only the serialized properties are required.
19 | * * The constructor _can_ have other parameters, but they must have a default value.
20 | * * The properties must match parameters of the constructor by name and type.
21 | *
22 | *
23 | * Example:
24 | * ```kotlin
25 | *
26 | * @Serializable(with = PersonSerializer::class)
27 | * data class Person(val name: String, val age: Int, val profession: String)
28 | *
29 | * object PersonSerializer : KSerializer by tupleSerializer(
30 | * Person::name,
31 | * Person::age,
32 | * Person::profession
33 | * )
34 | *
35 | * println(Json.encodeToString(PersonSerializer, Person("Kaylee", 21, "Mechanic")))
36 | * // > ["Kaylee", 21, "Mechanic"]
37 | * ```
38 | */
39 | @ExperimentalSerializationApi
40 | inline fun tupleSerializer(vararg properties: KProperty1): KSerializer =
41 | object : KSerializer {
42 | override val descriptor =
43 | TupleDescriptor(
44 | "io.ks3.Tuple(${T::class.qualifiedName})",
45 | *properties.map { serializer(it.returnType).descriptor }.toTypedArray(),
46 | )
47 |
48 | private val constructorPropertyMap =
49 | T::class.constructors
50 | .mapNotNull { constructor ->
51 | val mapping =
52 | constructor.parameters
53 | .filterNot(KParameter::isOptional)
54 | .associateWith { param ->
55 | properties.singleOrNull { prop ->
56 | prop.returnType.isSubtypeOf(param.type) && prop.name == param.name
57 | }
58 | }
59 |
60 | if (mapping.any { (_, key) -> key == null }) {
61 | null
62 | } else {
63 | constructor to mapping
64 | }
65 | }
66 | .single()
67 |
68 | override fun deserialize(decoder: Decoder): T {
69 | return decoder.decodeStructure(descriptor) {
70 | val args = mutableListOf()
71 | while (true) {
72 | val index = decodeElementIndex(descriptor)
73 | if (index == -1) break
74 | decodeSerializableElement(descriptor, index, serializer(properties[index].returnType)).let { args.add(it) }
75 | }
76 |
77 | val mappedArgs = constructorPropertyMap.second.mapValues { (_, prop) -> args[properties.indexOf(prop)] }
78 |
79 | constructorPropertyMap.first.callBy(mappedArgs)
80 | }
81 | }
82 |
83 | override fun serialize(
84 | encoder: Encoder,
85 | value: T,
86 | ) {
87 | encoder.encodeStructure(descriptor) {
88 | for (i in properties.indices) {
89 | encodeSerializableElement(descriptor, i, serializer(properties[i].returnType), properties[i].get(value))
90 | }
91 | }
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/ks3-standard/src/jvmTest/kotlin/io/ks3/standard/TupleSerializerTest.kt:
--------------------------------------------------------------------------------
1 | package io.ks3.standard
2 |
3 | import io.kotest.assertions.json.shouldEqualJson
4 | import io.kotest.core.spec.style.FreeSpec
5 | import io.kotest.matchers.shouldBe
6 | import kotlinx.serialization.ExperimentalSerializationApi
7 | import kotlinx.serialization.KSerializer
8 | import kotlinx.serialization.Serializable
9 | import kotlinx.serialization.decodeFromString
10 | import kotlinx.serialization.encodeToString
11 | import kotlinx.serialization.json.Json
12 |
13 | @Serializable(with = PointAsTupleSerializer::class)
14 | data class Point(val x: Int, val y: Int)
15 |
16 | @OptIn(ExperimentalSerializationApi::class)
17 | object PointAsTupleSerializer : KSerializer by tupleSerializer(Point::x, Point::y)
18 |
19 | @Serializable(with = PersonAsTupleSerializer::class)
20 | data class Person(val name: String, val age: Int, val origin: Point)
21 |
22 | @OptIn(ExperimentalSerializationApi::class)
23 | object PersonAsTupleSerializer : KSerializer by tupleSerializer(Person::name, Person::age, Person::origin)
24 |
25 | @Serializable(with = CoordinatesDetailsSerializer::class)
26 | data class CoordinatesDetails(
27 | val x: Int,
28 | val y: Int,
29 | private val colour: String,
30 | private val active: Boolean,
31 | ) {
32 | // Required for decoding partially encoded tuple
33 | constructor(x: Int, y: Int) : this(x, y, "red", true)
34 | }
35 |
36 | @OptIn(ExperimentalSerializationApi::class)
37 | object CoordinatesDetailsSerializer : KSerializer by tupleSerializer(CoordinatesDetails::x, CoordinatesDetails::y)
38 |
39 | @Serializable(with = CoordinatesDetailsWithDefaultsSerializer::class)
40 | data class CoordinatesDetailsWithDefaults(
41 | val x: Int,
42 | val y: Int,
43 | val colour: String = "red",
44 | val active: Boolean = true,
45 | )
46 |
47 | @OptIn(ExperimentalSerializationApi::class)
48 | object CoordinatesDetailsWithDefaultsSerializer :
49 | KSerializer by tupleSerializer(CoordinatesDetailsWithDefaults::x, CoordinatesDetailsWithDefaults::y)
50 |
51 | class TupleSerializerTest : FreeSpec(
52 | {
53 | val format = Json
54 |
55 | "should serialize" {
56 | format.encodeToString(Point(1, 2)) shouldEqualJson "[1,2]"
57 | }
58 |
59 | "should deserialize" {
60 | format.decodeFromString("[1,2]") shouldBe Point(1, 2)
61 | }
62 |
63 | "Tuple in tuple" - {
64 | val person = Person("Emil", 35, Point(1, 2))
65 | val serialForm = """["Emil", 35, [1,2]]"""
66 |
67 | "should serialize" {
68 | format.encodeToString(person) shouldEqualJson serialForm
69 | }
70 |
71 | "should deserialize" {
72 | format.decodeFromString(serialForm) shouldBe person
73 | }
74 | }
75 |
76 | "Partially encoded tuple can be decoded using matching constructor" {
77 | format.encodeToString(CoordinatesDetails(1, 2, "red", true)) shouldEqualJson "[1,2]"
78 | format.decodeFromString("[1,2]") shouldBe CoordinatesDetails(1, 2, "red", true)
79 | }
80 |
81 | "Partially encoded tuple can be decoded using default values" {
82 | format.encodeToString(CoordinatesDetailsWithDefaults(1, 2, "red", true)) shouldEqualJson "[1,2]"
83 | format.decodeFromString("[1,2]") shouldBe CoordinatesDetailsWithDefaults(1, 2, "red", true)
84 | }
85 | },
86 | )
87 |
--------------------------------------------------------------------------------
/ks3-test/Module.md:
--------------------------------------------------------------------------------
1 | # Module ks3-test
2 | Utilities for testing serializers using [Kotest](https://kotest.io).
3 |
4 | `generateSerializerTests` returns a Kotest [test factory](https://kotest.io/docs/framework/test-factories.html) for testing a serializer.
5 |
6 | It uses property testing to test round-trip serialization of random inputs.
7 |
--------------------------------------------------------------------------------
/ks3-test/api/ks3-test.api:
--------------------------------------------------------------------------------
1 | public final class io/ks3/test/Encoders {
2 | public fun (Ljava/util/List;Ljava/util/List;)V
3 | public final fun component1 ()Ljava/util/List;
4 | public final fun component2 ()Ljava/util/List;
5 | public final fun copy (Ljava/util/List;Ljava/util/List;)Lio/ks3/test/Encoders;
6 | public static synthetic fun copy$default (Lio/ks3/test/Encoders;Ljava/util/List;Ljava/util/List;ILjava/lang/Object;)Lio/ks3/test/Encoders;
7 | public fun equals (Ljava/lang/Object;)Z
8 | public final fun getDecoders ()Ljava/util/List;
9 | public final fun getEncoders ()Ljava/util/List;
10 | public fun hashCode ()I
11 | public fun toString ()Ljava/lang/String;
12 | }
13 |
14 |
--------------------------------------------------------------------------------
/ks3-test/api/ks3-test.klib.api:
--------------------------------------------------------------------------------
1 | // Klib ABI Dump
2 | // Targets: [iosArm64, iosSimulatorArm64, iosX64, js, linuxX64, macosArm64, macosX64, mingwX64, tvosArm64, tvosSimulatorArm64, tvosX64, watchosArm32, watchosArm64, watchosSimulatorArm64, watchosX64]
3 | // Rendering settings:
4 | // - Signature version: 2
5 | // - Show manifest properties: true
6 | // - Show declarations: true
7 |
8 | // Library unique name:
9 | final class <#A: kotlin/Any?> io.ks3.test/Encoders { // io.ks3.test/Encoders|null[0]
10 | constructor (kotlin.collections/List>, kotlin.collections/List>) // io.ks3.test/Encoders.|(kotlin.collections.List>;kotlin.collections.List>){}[0]
11 |
12 | final val decoders // io.ks3.test/Encoders.decoders|{}decoders[0]
13 | final fun (): kotlin.collections/List> // io.ks3.test/Encoders.decoders.|(){}[0]
14 | final val encoders // io.ks3.test/Encoders.encoders|{}encoders[0]
15 | final fun (): kotlin.collections/List> // io.ks3.test/Encoders.encoders.|(){}[0]
16 |
17 | final fun component1(): kotlin.collections/List> // io.ks3.test/Encoders.component1|component1(){}[0]
18 | final fun component2(): kotlin.collections/List> // io.ks3.test/Encoders.component2|component2(){}[0]
19 | final fun copy(kotlin.collections/List> =..., kotlin.collections/List> =...): io.ks3.test/Encoders<#A> // io.ks3.test/Encoders.copy|copy(kotlin.collections.List>;kotlin.collections.List>){}[0]
20 | final fun equals(kotlin/Any?): kotlin/Boolean // io.ks3.test/Encoders.equals|equals(kotlin.Any?){}[0]
21 | final fun hashCode(): kotlin/Int // io.ks3.test/Encoders.hashCode|hashCode(){}[0]
22 | final fun toString(): kotlin/String // io.ks3.test/Encoders.toString|toString(){}[0]
23 | }
24 |
25 | final inline fun <#A: reified kotlin/Any?> (kotlinx.serialization.json/Json).io.ks3.test/decoders(kotlinx.serialization/KSerializer<#A>): kotlin.collections/List> // io.ks3.test/decoders|decoders@kotlinx.serialization.json.Json(kotlinx.serialization.KSerializer<0:0>){0§}[0]
26 | final inline fun <#A: reified kotlin/Any?> (kotlinx.serialization.json/Json).io.ks3.test/encoders(kotlinx.serialization/KSerializer<#A>): kotlin.collections/List> // io.ks3.test/encoders|encoders@kotlinx.serialization.json.Json(kotlinx.serialization.KSerializer<0:0>){0§}[0]
27 | final inline fun <#A: reified kotlin/Any?> (kotlinx.serialization.json/Json).io.ks3.test/generateEncoders(kotlinx.serialization/KSerializer<#A>): io.ks3.test/Encoders<#A> // io.ks3.test/generateEncoders|generateEncoders@kotlinx.serialization.json.Json(kotlinx.serialization.KSerializer<0:0>){0§}[0]
28 | final inline fun <#A: reified kotlin/Any?> io.ks3.test/generateSerializerTests(kotlinx.serialization/KSerializer<#A>, io.kotest.property/Gen<#A>, crossinline kotlin/Function0 =..., crossinline kotlin/Function2<#A, #A, kotlin/Unit> =...): io.kotest.core.factory/TestFactory // io.ks3.test/generateSerializerTests|generateSerializerTests(kotlinx.serialization.KSerializer<0:0>;io.kotest.property.Gen<0:0>;kotlin.Function0;kotlin.Function2<0:0,0:0,kotlin.Unit>){0§}[0]
29 |
--------------------------------------------------------------------------------
/ks3-test/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("ks3.conventions.lang.kotlin-multiplatform-js")
3 | id("ks3.conventions.lang.kotlin-multiplatform-jvm")
4 | id("ks3.conventions.lang.kotlin-multiplatform-native")
5 | id("ks3.conventions.publishing.maven-publish")
6 | }
7 |
8 | dokkatoo {
9 | dokkatooSourceSets.configureEach {
10 | includes.from("Module.md")
11 | }
12 | modulePath.set("ks3-test") // match the original dokka default
13 | }
14 |
15 | tasks.withType().configureEach {
16 | generator.dokkaSourceSets.configureEach {
17 | sourceSetScope.set(":ks3-test:dokkaHtmlPartial")
18 | }
19 | }
20 |
21 | kotlin {
22 | targets.configureEach {
23 | compilations.all {
24 | kotlinOptions {
25 | freeCompilerArgs += listOf("-opt-in=kotlinx.serialization.json.internal.JsonFriendModuleApi")
26 | }
27 | }
28 | }
29 |
30 | sourceSets {
31 | commonMain {
32 | dependencies {
33 | api(libs.okio.core)
34 | api(libs.kotlinxSerialization.jsonOkio)
35 |
36 | implementation(projects.ks3Core)
37 |
38 | implementation(libs.kotest.property)
39 | implementation(libs.kotest.frameworkEngine)
40 |
41 | implementation(libs.kotlinxSerialization.core)
42 | implementation(libs.kotlinxSerialization.json)
43 | }
44 | }
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/ks3-test/src/commonMain/kotlin/io.ks3.test/SerializerTestFactory.kt:
--------------------------------------------------------------------------------
1 | package io.ks3.test
2 |
3 | import io.kotest.core.spec.style.funSpec
4 | import io.kotest.matchers.shouldBe
5 | import io.kotest.property.Exhaustive
6 | import io.kotest.property.Gen
7 | import io.kotest.property.checkAll
8 | import io.kotest.property.exhaustive.cartesianPairs
9 | import io.kotest.property.exhaustive.exhaustive
10 | import kotlinx.serialization.KSerializer
11 | import kotlinx.serialization.json.Json
12 |
13 | /**
14 | * Creates a test factory for [serializer].
15 | */
16 | inline fun generateSerializerTests(
17 | serializer: KSerializer,
18 | generator: Gen,
19 | crossinline nameFn: () -> String = { "Encodes and decodes values back to original form" },
20 | crossinline assertion: T.(T) -> Unit = { original -> this shouldBe original },
21 | ) = funSpec {
22 | val (encoders, decoders) = Json.generateEncoders(serializer)
23 |
24 | test(nameFn()) {
25 | checkAll(generator, Exhaustive.cartesianPairs(encoders.exhaustive(), decoders.exhaustive())) { value, (encode, decode) ->
26 | value.encode().decode().assertion(value)
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/ks3-test/src/commonMain/kotlin/io.ks3.test/TestHelpers.kt:
--------------------------------------------------------------------------------
1 | package io.ks3.test
2 |
3 | import kotlinx.serialization.ExperimentalSerializationApi
4 | import kotlinx.serialization.InternalSerializationApi
5 | import kotlinx.serialization.KSerializer
6 | import kotlinx.serialization.encodeToString
7 | import kotlinx.serialization.json.Json
8 | import kotlinx.serialization.json.internal.decodeStringToJsonTree
9 | import kotlinx.serialization.json.internal.readJson
10 | import kotlinx.serialization.json.okio.decodeFromBufferedSource
11 | import kotlinx.serialization.json.okio.encodeToBufferedSink
12 | import okio.Buffer
13 |
14 | typealias Encoder = T.() -> String
15 | typealias Decoder = String.() -> T
16 |
17 | data class Encoders(
18 | val encoders: List>,
19 | val decoders: List>,
20 | )
21 |
22 | inline fun Json.generateEncoders(serializer: KSerializer) =
23 | Encoders(
24 | encoders(serializer),
25 | decoders(serializer),
26 | )
27 |
28 | @OptIn(ExperimentalSerializationApi::class)
29 | inline fun Json.encoders(serializer: KSerializer): List> =
30 | listOf(
31 | { encodeToString(serializer, this) },
32 | { encodeToString(encodeToJsonElement(serializer, this)) },
33 | {
34 | val buffer = Buffer()
35 | encodeToBufferedSink(serializer, this, buffer)
36 | buffer.readUtf8()
37 | },
38 | )
39 |
40 | @OptIn(ExperimentalSerializationApi::class, InternalSerializationApi::class)
41 | inline fun Json.decoders(serializer: KSerializer): List> =
42 | listOf(
43 | { decodeFromString(serializer, this) },
44 | {
45 | val tree = decodeStringToJsonTree(this@decoders, serializer, this)
46 | readJson(this@decoders, tree, serializer)
47 | },
48 | {
49 | val buffer = Buffer()
50 | buffer.writeUtf8(this)
51 | decodeFromBufferedSource(serializer, buffer)
52 | },
53 | )
54 |
--------------------------------------------------------------------------------
/ks3-test/src/commonMain/resources/kotest.properties:
--------------------------------------------------------------------------------
1 | kotest.framework.classpath.scanning.autoscan.disable=true
2 | kotest.framework.classpath.scanning.config.disable=true
3 |
--------------------------------------------------------------------------------
/renovate.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": [
3 | "config:base"
4 | ]
5 | }
6 |
--------------------------------------------------------------------------------
/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | rootProject.name = "ks3"
2 |
3 | plugins {
4 | id("com.gradle.enterprise") version "3.19"
5 | }
6 |
7 | apply(from = "build-logic/repositories.gradle.kts")
8 |
9 | enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS")
10 | enableFeaturePreview("STABLE_CONFIGURATION_CACHE")
11 |
12 | includeBuild("build-logic")
13 |
14 | include(
15 | ":ks3-jdk",
16 | ":ks3-standard",
17 | ":ks3-test",
18 | ":ks3-core",
19 | )
20 |
21 |
22 | develocity {
23 |
24 | buildScan {
25 | val isCI = providers.environmentVariable("CI").orNull.toBoolean()
26 |
27 | tag(if (isCI) "CI" else "local")
28 | tag(providers.systemProperty("os.name").orNull)
29 | tag(providers.systemProperty("os.arch").orNull)
30 |
31 | if (isCI) {
32 | // only automatically enable build scan on CI
33 | termsOfUseUrl = "https://gradle.com/terms-of-service"
34 | termsOfUseAgree = "yes"
35 | uploadInBackground.set(false)
36 |
37 | val ghServer = providers.environmentVariable("GITHUB_SERVER_URL").orNull
38 | val ghRepo = providers.environmentVariable("GITHUB_REPOSITORY").orNull
39 | val giRunId = providers.environmentVariable("GITHUB_RUN_ID").orNull
40 | link("GitHub Workflow run", "$ghServer/$ghRepo/actions/runs/$giRunId")
41 | }
42 | }
43 | }
44 |
--------------------------------------------------------------------------------