├── .editorconfig ├── .github ├── ci-gradle.properties └── workflows │ ├── build.yaml │ ├── publish-docs.yaml │ └── publish.yaml ├── .gitignore ├── README.md ├── build.gradle.kts ├── composeApp ├── build.gradle.kts └── src │ ├── androidMain │ ├── AndroidManifest.xml │ ├── kotlin │ │ └── com │ │ │ └── paligot │ │ │ └── jsonforms │ │ │ └── kotlin │ │ │ └── android │ │ │ └── MainActivity.kt │ └── res │ │ └── values │ │ └── styles.xml │ ├── commonMain │ ├── composeResources │ │ └── drawable │ │ │ ├── de.svg │ │ │ ├── es.svg │ │ │ └── fr.svg │ └── kotlin │ │ └── com │ │ └── paligot │ │ └── jsonforms │ │ └── kotlin │ │ ├── App.kt │ │ ├── FormDescriptionNavGraph.kt │ │ ├── panes │ │ ├── AccountCreationFormPane.kt │ │ ├── AppleForm.kt │ │ ├── ContactFormPane.kt │ │ ├── FormListPane.kt │ │ ├── LoginFormPane.kt │ │ └── address │ │ │ ├── AddressFormPane.kt │ │ │ ├── AddressFormVM.kt │ │ │ ├── AddressFormViewModel.kt │ │ │ ├── AddressUiModel.kt │ │ │ └── geocode │ │ │ ├── GeocodeApi.kt │ │ │ └── GeocodeNet.kt │ │ └── ui │ │ ├── FlagDropdownField.kt │ │ ├── FormScaffold.kt │ │ └── MyApplicationTheme.kt │ └── desktopMain │ └── kotlin │ └── com │ └── paligot │ └── jsonforms │ └── kotlin │ └── desktop │ └── main.kt ├── docs ├── create-renderer.md ├── custom-rendering.md ├── index.md ├── state-management.md └── usage.md ├── gradle.properties ├── gradle ├── libs.versions.toml └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── mkdocs.yml ├── renderers ├── cupertino │ ├── README.md │ ├── api │ │ ├── android │ │ │ └── cupertino.api │ │ └── desktop │ │ │ └── cupertino.api │ ├── build.gradle.kts │ └── src │ │ └── commonMain │ │ └── kotlin │ │ └── com │ │ └── paligot │ │ └── jsonforms │ │ └── cupertino │ │ ├── CupertinoRenderer.kt │ │ ├── layout │ │ ├── Column.kt │ │ ├── CupertinoSection.kt │ │ └── Row.kt │ │ └── ui │ │ ├── Checkbox.kt │ │ ├── OutlinedTextField.kt │ │ ├── SegmentedControl.kt │ │ ├── Switch.kt │ │ └── WheelPicker.kt └── material3 │ ├── README.md │ ├── api │ ├── android │ │ └── material3.api │ └── desktop │ │ └── material3.api │ ├── build.gradle.kts │ └── src │ └── commonMain │ └── kotlin │ └── com │ └── paligot │ └── jsonforms │ └── material3 │ ├── MaterialRenderer.kt │ ├── layout │ ├── Column.kt │ └── Row.kt │ └── ui │ ├── Checkbox.kt │ ├── OutlinedTextField.kt │ ├── OutlinedTextFieldDropdown.kt │ ├── RadioButton.kt │ └── Switch.kt ├── renovate.json ├── scripts └── build_docs.sh ├── settings.gradle.kts ├── shared ├── README.md ├── api │ ├── android │ │ └── shared.api │ └── desktop │ │ └── shared.api ├── build.gradle.kts └── src │ ├── commonMain │ └── kotlin │ │ └── com │ │ └── paligot │ │ └── jsonforms │ │ └── kotlin │ │ ├── SchemaProvider.kt │ │ ├── internal │ │ ├── FieldError.kt │ │ ├── checks │ │ │ ├── BooleanProperty.ext.kt │ │ │ ├── NumberProperty.ext.kt │ │ │ ├── ObjectProperty.ext.kt │ │ │ ├── Property.ext.kt │ │ │ ├── StringProperty.ext.kt │ │ │ └── ValidationCheck.kt │ │ ├── ext │ │ │ ├── BooleanProperty.ext.kt │ │ │ ├── Control.ext.kt │ │ │ ├── JsonPrimitive.ext.kt │ │ │ ├── Property.ext.kt │ │ │ ├── Rule.ext.kt │ │ │ └── StringProperty.ext.kt │ │ └── queries │ │ │ ├── Control.ext.kt │ │ │ ├── ObjectProperty.ext.kt │ │ │ └── UiSchema.ext.kt │ │ └── models │ │ ├── schema │ │ ├── ArrayProperty.kt │ │ ├── BooleanProperty.kt │ │ ├── NumberProperty.kt │ │ ├── ObjectProperty.kt │ │ ├── Property.kt │ │ ├── Schema.kt │ │ └── StringProperty.kt │ │ ├── serializers │ │ ├── ImmutableListSerializer.kt │ │ ├── ImmutableMapSerializer.kt │ │ ├── ObjectPropertyListSerializer.kt │ │ └── RegexSerializer.kt │ │ └── uischema │ │ ├── Condition.kt │ │ ├── Control.kt │ │ ├── ControlOptions.kt │ │ ├── Effect.kt │ │ ├── Format.kt │ │ ├── GroupLayout.kt │ │ ├── HorizontalLayout.kt │ │ ├── LayoutOptions.kt │ │ ├── Orientation.kt │ │ ├── Rule.kt │ │ ├── UiSchema.kt │ │ └── VerticalLayout.kt │ └── commonTest │ └── kotlin │ └── com │ └── paligot │ └── jsonforms │ └── kotlin │ ├── internal │ ├── checks │ │ ├── BooleanPropertyValidateTest.kt │ │ ├── NumberPropertyValidateTest.kt │ │ ├── ObjectPropertyIsRequiredTest.kt │ │ ├── ObjectPropertyValidateTest.kt │ │ ├── PropertyValidatePropertyTest.kt │ │ ├── StringPropertyValidateTest.kt │ │ └── ValidationCheckTest.kt │ ├── ext │ │ ├── BooleanPropertyIsToggleTest.kt │ │ ├── ControlPropertyKeyTest.kt │ │ ├── ControlPropertyPathTest.kt │ │ ├── PropertyIsEnabledTest.kt │ │ ├── PropertyLabelTest.kt │ │ ├── PropertyMaxCounterTest.kt │ │ ├── RuleEvaluateEnabledTest.kt │ │ ├── RuleEvaluateShowTest.kt │ │ ├── StringPropertyIsDropdownTest.kt │ │ ├── StringPropertyIsEmailTest.kt │ │ ├── StringPropertyIsPasswordTest.kt │ │ ├── StringPropertyIsPhoneTest.kt │ │ └── StringPropertyIsRadioTest.kt │ └── queries │ │ ├── ControlIsLastFieldTest.kt │ │ ├── ObjectPropertyGetPropertyByControlTest.kt │ │ └── UiSchemaFindVisibleControlsTest.kt │ └── models │ ├── schema │ ├── PropertyPatternSerializationTest.kt │ └── SchemaTest.kt │ └── uischema │ └── UiSchemaTest.kt └── ui ├── README.md ├── api ├── android │ └── ui.api └── desktop │ └── ui.api ├── build.gradle.kts └── src ├── commonMain └── kotlin │ └── com │ └── paligot │ └── jsonforms │ └── ui │ ├── JsonForm.kt │ ├── JsonFormState.kt │ ├── Layout.kt │ ├── Property.kt │ ├── RendererBooleanScope.kt │ ├── RendererLayoutScope.kt │ ├── RendererNumberScope.kt │ └── RendererStringScope.kt └── desktopTest └── kotlin └── com └── paligot └── jsonforms └── ui ├── JsonFormStateTest.kt ├── JsonFormTest.kt ├── LayoutTest.kt ├── PropertyTest.kt ├── RendererBooleanScopeTest.kt ├── RendererLayoutScopeTest.kt ├── RendererNumberScopeTest.kt └── RendererStringScopeTest.kt /.editorconfig: -------------------------------------------------------------------------------- 1 | [*.{kt,kts}] 2 | ktlint_function_naming_ignore_when_annotated_with=Composable 3 | -------------------------------------------------------------------------------- /.github/ci-gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.daemon=false 2 | org.gradle.parallel=true 3 | org.gradle.workers.max=2 4 | 5 | kotlin.incremental=false 6 | kotlin.compiler.execution.strategy=in-process 7 | -------------------------------------------------------------------------------- /.github/workflows/build.yaml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | pull_request: 5 | concurrency: 6 | group: build-${{ github.ref }} 7 | cancel-in-progress: true 8 | 9 | jobs: 10 | composeApp: 11 | runs-on: macos-14 12 | timeout-minutes: 60 13 | steps: 14 | - uses: actions/checkout@v4 15 | 16 | - uses: actions/setup-java@v4 17 | with: 18 | distribution: temurin 19 | java-version: 21 20 | 21 | - uses: gradle/actions/setup-gradle@v4 22 | with: 23 | gradle-version: wrapper 24 | build-scan-publish: true 25 | build-scan-terms-of-use-url: "https://gradle.com/terms-of-service" 26 | build-scan-terms-of-use-agree: "yes" 27 | 28 | - name: Copy CI gradle.properties 29 | run: mkdir -p ~/.gradle ; cp .github/ci-gradle.properties ~/.gradle/gradle.properties 30 | 31 | - name: Assemble project 32 | run: ./gradlew :composeApp:assemble 33 | 34 | - name: Upload build outputs 35 | if: always() 36 | uses: actions/upload-artifact@v4 37 | with: 38 | name: build-outputs 39 | path: composeApp/build/outputs 40 | linter: 41 | runs-on: ubuntu-latest 42 | timeout-minutes: 60 43 | steps: 44 | - uses: actions/checkout@v4 45 | 46 | - uses: actions/setup-java@v4 47 | with: 48 | distribution: temurin 49 | java-version: 21 50 | 51 | - uses: gradle/actions/setup-gradle@v4 52 | with: 53 | gradle-version: wrapper 54 | build-scan-publish: true 55 | build-scan-terms-of-use-url: "https://gradle.com/terms-of-service" 56 | build-scan-terms-of-use-agree: "yes" 57 | 58 | - name: Copy CI gradle.properties 59 | run: mkdir -p ~/.gradle ; cp .github/ci-gradle.properties ~/.gradle/gradle.properties 60 | 61 | - name: Check Kotlin lint 62 | run: ./gradlew ktlintCheck 63 | 64 | - name: Check api 65 | run: ./gradlew apiCheck 66 | test: 67 | runs-on: ubuntu-latest 68 | timeout-minutes: 60 69 | steps: 70 | - uses: actions/checkout@v4 71 | 72 | - uses: actions/setup-java@v4 73 | with: 74 | distribution: temurin 75 | java-version: 21 76 | 77 | - uses: gradle/actions/setup-gradle@v4 78 | with: 79 | gradle-version: wrapper 80 | build-scan-publish: true 81 | build-scan-terms-of-use-url: "https://gradle.com/terms-of-service" 82 | build-scan-terms-of-use-agree: "yes" 83 | 84 | - name: Copy CI gradle.properties 85 | run: mkdir -p ~/.gradle ; cp .github/ci-gradle.properties ~/.gradle/gradle.properties 86 | 87 | - name: Run desktop tests 88 | run: ./gradlew desktopTest 89 | 90 | - name: Upload reports 91 | if: always() 92 | uses: actions/upload-artifact@v4 93 | with: 94 | name: reports 95 | path: | 96 | **/build/reports/* 97 | -------------------------------------------------------------------------------- /.github/workflows/publish-docs.yaml: -------------------------------------------------------------------------------- 1 | name: Publish Documentation 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | 9 | permissions: 10 | contents: read 11 | pages: write 12 | id-token: write 13 | 14 | jobs: 15 | build: 16 | permissions: 17 | checks: write # for actions/upload-artifact 18 | runs-on: ubuntu-latest 19 | steps: 20 | - uses: actions/checkout@v4 21 | 22 | - uses: actions/setup-java@v4 23 | with: 24 | distribution: temurin 25 | java-version: 21 26 | 27 | - uses: actions/setup-python@v5 28 | with: 29 | python-version: '3.x' 30 | 31 | - uses: gradle/actions/setup-gradle@v4 32 | with: 33 | gradle-version: wrapper 34 | build-scan-publish: true 35 | build-scan-terms-of-use-url: "https://gradle.com/terms-of-service" 36 | build-scan-terms-of-use-agree: "yes" 37 | 38 | - name: Install dependencies 39 | run: | 40 | python3 -m pip install --upgrade pip 41 | python3 -m pip install mkdocs mkdocs-material 42 | 43 | - name: Generate Docs 44 | run: ./scripts/build_docs.sh 45 | 46 | - uses: actions/upload-pages-artifact@v3 47 | id: deployment 48 | with: 49 | path: site/ 50 | 51 | deploy: 52 | if: github.ref == 'refs/heads/main' 53 | needs: build 54 | 55 | permissions: 56 | pages: write # to deploy to Pages 57 | id-token: write # to verify the deployment originates from an appropriate source 58 | 59 | # Deploy to the github-pages environment 60 | environment: 61 | name: github-pages 62 | url: ${{ steps.deployment.outputs.page_url }} 63 | 64 | runs-on: ubuntu-latest 65 | steps: 66 | - name: Deploy to GitHub Pages 67 | id: deployment 68 | uses: actions/deploy-pages@v4 69 | -------------------------------------------------------------------------------- /.github/workflows/publish.yaml: -------------------------------------------------------------------------------- 1 | name: Publish 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | publish: 10 | runs-on: macos-14 11 | timeout-minutes: 60 12 | steps: 13 | - uses: actions/checkout@v4 14 | 15 | - uses: actions/setup-java@v4 16 | with: 17 | distribution: temurin 18 | java-version: 21 19 | 20 | - uses: gradle/actions/setup-gradle@v4 21 | with: 22 | gradle-version: wrapper 23 | build-scan-publish: true 24 | build-scan-terms-of-use-url: "https://gradle.com/terms-of-service" 25 | build-scan-terms-of-use-agree: "yes" 26 | 27 | - name: Copy CI gradle.properties 28 | run: mkdir -p ~/.gradle ; cp .github/ci-gradle.properties ~/.gradle/gradle.properties 29 | 30 | - name: Publishing to Maven Central 31 | # FIXME --no-configuration-cache flag https://github.com/vanniktech/gradle-maven-publish-plugin/issues/259 32 | run: ./gradlew publishAllPublicationsToMavenCentralRepository --no-configuration-cache 33 | env: 34 | ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.ORG_GRADLE_PROJECT_MAVENCENTRALUSERNAME }} 35 | ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.ORG_GRADLE_PROJECT_MAVENCENTRALPASSWORD }} 36 | ORG_GRADLE_PROJECT_signingInMemoryKey: ${{ secrets.ORG_GRADLE_PROJECT_SIGNINGINMEMORYKEY }} 37 | ORG_GRADLE_PROJECT_signingInMemoryKeyPassword: ${{ secrets.ORG_GRADLE_PROJECT_SIGNINGINMEMORYKEYPASSWORD }} 38 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | .kotlin 4 | .idea 5 | .DS_Store 6 | build 7 | .externalNativeBuild 8 | .cxx 9 | local.properties 10 | xcuserdata 11 | site -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) 2 | 3 | ## jsonforms-kotlin 4 | 5 | `jsonforms-kotlin` is a Kotlin Multiplatform implementation of the [JSONForms](https://jsonforms.io/) 6 | standard from the Eclipse Foundation. It leverages the power of [Compose Multiplatform](https://www.jetbrains.com/lp/compose-multiplatform/) 7 | to render dynamic forms based on JSON Schemas and UI Schemas across various platforms, 8 | including Android, iOS, and the JVM. 9 | 10 | Check out the website for more information: https://gerard.paligot.com/jsonforms-kotlin/ 11 | 12 | ## License 13 | 14 | ``` 15 | Copyright 2025 Gérard Paligot. 16 | 17 | Licensed under the Apache License, Version 2.0 (the "License"); 18 | you may not use this file except in compliance with the License. 19 | You may obtain a copy of the License at 20 | 21 | http://www.apache.org/licenses/LICENSE-2.0 22 | 23 | Unless required by applicable law or agreed to in writing, software 24 | distributed under the License is distributed on an "AS IS" BASIS, 25 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 26 | See the License for the specific language governing permissions and 27 | limitations under the License. 28 | ``` 29 | -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | import org.jlleitschuh.gradle.ktlint.KtlintExtension 2 | 3 | plugins { 4 | alias(libs.plugins.android.application).apply(false) 5 | alias(libs.plugins.android.library).apply(false) 6 | alias(libs.plugins.jetbrains.compose).apply(false) 7 | alias(libs.plugins.jetbrains.compose.compiler).apply(false) 8 | alias(libs.plugins.jetbrains.kotlin.multiplatform).apply(false) 9 | alias(libs.plugins.jetbrains.kotlin.serialization).apply(false) 10 | alias(libs.plugins.jetbrains.kotlinx.binary.compatibility.validator).apply(false) 11 | alias(libs.plugins.ktlint).apply(false) 12 | alias(libs.plugins.vanniktech.maven.publish).apply(false) 13 | alias(libs.plugins.jetbrains.dokka).apply(true) 14 | } 15 | 16 | subprojects { 17 | if (pluginManager.hasPlugin("org.jlleitschuh.gradle.ktlint")) { 18 | configure { 19 | debug.set(false) 20 | version.set("0.47.1") 21 | verbose.set(true) 22 | android.set(false) 23 | outputToConsole.set(true) 24 | ignoreFailures.set(false) 25 | enableExperimentalRules.set(true) 26 | filter { 27 | exclude { projectDir.toURI().relativize(it.file.toURI()).path.contains("/generated/") } 28 | exclude { it.file.path.contains("/build/") } 29 | include("**/kotlin/**") 30 | } 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /composeApp/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import org.jetbrains.compose.desktop.application.dsl.TargetFormat 2 | import org.jetbrains.kotlin.gradle.tasks.KotlinCompile 3 | 4 | plugins { 5 | alias(libs.plugins.android.application) 6 | alias(libs.plugins.jetbrains.kotlin.multiplatform) 7 | alias(libs.plugins.jetbrains.kotlin.serialization) 8 | alias(libs.plugins.jetbrains.compose) 9 | alias(libs.plugins.jetbrains.compose.compiler) 10 | } 11 | 12 | android { 13 | namespace = "com.paligot.jsonforms.kotlin.android" 14 | compileSdk = 35 15 | defaultConfig { 16 | applicationId = "com.paligot.jsonforms.kotlin.android" 17 | minSdk = 26 18 | targetSdk = 35 19 | versionCode = 1 20 | versionName = "1.0" 21 | } 22 | packaging { 23 | resources { 24 | excludes += "/META-INF/{AL2.0,LGPL2.1}" 25 | } 26 | } 27 | buildTypes { 28 | getByName("release") { 29 | isMinifyEnabled = false 30 | } 31 | } 32 | compileOptions { 33 | sourceCompatibility = JavaVersion.VERSION_21 34 | targetCompatibility = JavaVersion.VERSION_21 35 | } 36 | } 37 | 38 | compose.desktop { 39 | application { 40 | mainClass = "com.paligot.jsonforms.kotlin.desktop.MainKt" 41 | 42 | nativeDistributions { 43 | targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Deb) 44 | packageName = "com.paligot.jsonforms.kotlin.desktop" 45 | packageVersion = "1.0.0" 46 | } 47 | } 48 | } 49 | 50 | kotlin { 51 | androidTarget() 52 | jvm("desktop") 53 | 54 | sourceSets { 55 | val desktopMain by getting 56 | commonMain.dependencies { 57 | implementation(projects.renderers.material3) 58 | implementation(projects.renderers.cupertino) 59 | implementation(projects.shared) 60 | implementation(compose.material3) 61 | implementation(compose.materialIconsExtended) 62 | implementation(compose.ui) 63 | implementation(compose.components.resources) 64 | implementation(compose.preview) 65 | implementation(libs.cupertino) 66 | implementation(libs.jetbrains.androidx.viewmodel.compose) 67 | implementation(libs.jetbrains.androidx.navigation.compose) 68 | implementation(libs.jetbrains.kotlinx.serialization.json) 69 | implementation(libs.bundles.io.ktor.client) 70 | } 71 | androidMain.dependencies { 72 | implementation(libs.androidx.activity.compose) 73 | } 74 | desktopMain.dependencies { 75 | implementation(compose.desktop.currentOs) 76 | implementation(libs.jetbrains.kotlinx.coroutines) 77 | implementation(libs.jetbrains.kotlinx.coroutines.swing) 78 | } 79 | } 80 | } 81 | 82 | tasks { 83 | withType { 84 | kotlinOptions { 85 | freeCompilerArgs = freeCompilerArgs + listOf("-opt-in=kotlin.RequiresOptIn") 86 | jvmTarget = JavaVersion.toVersion(JavaVersion.VERSION_21).toString() 87 | } 88 | } 89 | 90 | withType { 91 | val javaToolchains = project.extensions.getByType() 92 | javaCompiler.set( 93 | javaToolchains.compilerFor { 94 | languageVersion.set(JavaLanguageVersion.of(JavaVersion.VERSION_21.toString())) 95 | }, 96 | ) 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /composeApp/src/androidMain/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /composeApp/src/androidMain/kotlin/com/paligot/jsonforms/kotlin/android/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.paligot.jsonforms.kotlin.android 2 | 3 | import android.os.Bundle 4 | import androidx.activity.ComponentActivity 5 | import androidx.activity.compose.setContent 6 | import com.paligot.jsonforms.kotlin.App 7 | import com.paligot.jsonforms.kotlin.ui.MyApplicationTheme 8 | 9 | class MainActivity : ComponentActivity() { 10 | override fun onCreate(savedInstanceState: Bundle?) { 11 | super.onCreate(savedInstanceState) 12 | setContent { 13 | MyApplicationTheme { 14 | App() 15 | } 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /composeApp/src/androidMain/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 |