diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
new file mode 100644
index 0000000..b2f6900
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -0,0 +1,38 @@
+---
+name: Bug report
+about: Create a report to help us improve
+title: ''
+labels: ''
+assignees: ''
+
+---
+
+**Describe the bug**
+A clear and concise description of what the bug is.
+
+**To Reproduce**
+Steps to reproduce the behavior:
+1. Go to '...'
+2. Click on '....'
+3. Scroll down to '....'
+4. See error
+
+**Expected behavior**
+A clear and concise description of what you expected to happen.
+
+**Screenshots**
+If applicable, add screenshots to help explain your problem.
+
+**Desktop (please complete the following information):**
+- OS: [e.g. iOS]
+- Browser [e.g. chrome, safari]
+- Version [e.g. 22]
+
+**Smartphone (please complete the following information):**
+- Device: [e.g. iPhone6]
+- OS: [e.g. iOS8.1]
+- Browser [e.g. stock browser, safari]
+- Version [e.g. 22]
+
+**Additional context**
+Add any other context about the problem here.
diff --git a/.github/issue-branch.yml b/.github/issue-branch.yml
new file mode 100644
index 0000000..0ac686b
--- /dev/null
+++ b/.github/issue-branch.yml
@@ -0,0 +1,19 @@
+# Automatically close issue after a pull request merge
+autoCloseIssue: true
+
+# Override the source branch
+defaultBranch: 'development'
+
+#Skip branch creation based on issue label 'question'
+branches:
+ - label: question
+ skip: true
+
+# Automatically open a Pull Request
+openPR: true
+
+# Copy attributes from issue
+copyIssueDescriptionToPR: true
+copyIssueLabelsToPR: true
+copyIssueAssigneeToPR: true
+copyIssueMilestoneToPR: true
diff --git a/.github/workflows/branch-on-issue.yml b/.github/workflows/branch-on-issue.yml
new file mode 100644
index 0000000..99a8f81
--- /dev/null
+++ b/.github/workflows/branch-on-issue.yml
@@ -0,0 +1,16 @@
+name: Branch on Issue
+
+on:
+ issues:
+ types: [ assigned ]
+ pull_request:
+ types: [ closed ]
+
+jobs:
+ create_issue_branch_job:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Create Issue Branch
+ uses: robvanderleek/create-issue-branch@main
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
\ No newline at end of file
diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
new file mode 100644
index 0000000..9375a16
--- /dev/null
+++ b/.github/workflows/codeql-analysis.yml
@@ -0,0 +1,123 @@
+# For most projects, this workflow file will not need changing; you simply need
+# to commit it to your repository.
+#
+# You may wish to alter this file to override the set of languages analyzed,
+# or to provide custom queries or build logic.
+#
+# ******** NOTE ********
+# We have attempted to detect the languages in your repository. Please check
+# the `language` matrix defined below to confirm you have the correct set of
+# supported CodeQL languages.
+#
+name: "CodeQL"
+
+on:
+ push:
+ branches: [ main, development ]
+ pull_request:
+ # The branches below must be a subset of the branches above
+ branches: [ main, development ]
+ schedule:
+ - cron: '42 9 * * 4'
+
+jobs:
+ analyze:
+ name: Analyze
+ runs-on: ubuntu-latest
+ permissions:
+ actions: read
+ contents: write
+ security-events: write
+
+ strategy:
+ fail-fast: false
+ matrix:
+ language: [ 'java' ]
+ # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
+ # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support
+
+ steps:
+ - uses: actions/checkout@v6
+ - name: Set up Python3
+ uses: actions/setup-python@v6
+ with:
+ python-version: '3.14'
+ - name: Set up OpenJDK
+ uses: actions/setup-java@v5
+ with:
+ distribution: 'zulu'
+ java-version: 21
+
+ # Initializes the CodeQL tools for scanning.
+ - name: Initialize CodeQL
+ uses: github/codeql-action/init@v4
+ with:
+ languages: ${{ matrix.language }}
+ # If you wish to specify custom queries, you can do so here or in a config file.
+ # By default, queries listed here will override any specified in a config file.
+ # Prefix the list here with "+" to use these queries and those in the config file.
+
+ # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
+ # queries: security-extended,security-and-quality
+
+
+ # Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
+ # If this step fails, then you should remove it and run the build manually (see below)
+ # - name: Autobuild
+ # uses: github/codeql-action/autobuild@v2
+ # with:
+ # java-version: 17
+
+ # âšī¸ Command-line programs to run using the OS shell.
+ # đ See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
+
+ # If the Autobuild fails above, remove it and uncomment the following three lines.
+ # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance.
+
+ - run: |
+ echo "Run, Build Application using script"
+ ./gradlew build -DapplicationProperties="src/test/resources/test-config/application-test.properties"
+
+ - name: Perform CodeQL Analysis
+ uses: github/codeql-action/analyze@v4
+ with:
+ category: "/language:java"
+
+ review:
+ name: Review Dependencies and create SBOM
+ runs-on: ubuntu-latest
+ permissions:
+ actions: read
+ contents: write
+ security-events: write
+
+ strategy:
+ fail-fast: false
+ matrix:
+ language: [ 'java' ]
+ steps:
+ - uses: actions/checkout@v6
+
+ - name: Set up Python3
+ uses: actions/setup-python@v6
+ with:
+ python-version: '3.14'
+
+ - name: Set up OpenJDK
+ uses: actions/setup-java@v5
+ with:
+ distribution: 'zulu'
+ java-version: 21
+
+ - name: 'Dependency Review'
+ uses: actions/dependency-review-action@v4
+ with:
+ allow-licenses: MIT, Apache-2.0, ISC, BSD-2-Clause, 0BSD
+ base-ref: ${{ github.event.pull_request.base.sha || 'main' }}
+ head-ref: ${{ github.event.pull_request.head.sha || github.ref }}
+
+ - name: Scan the image and upload dependency results
+ uses: anchore/sbom-action@v0
+ with:
+ artifact-name: image.spdx.json
+ dependency-snapshot: true
diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml
new file mode 100644
index 0000000..ba9ee9f
--- /dev/null
+++ b/.github/workflows/gradle.yml
@@ -0,0 +1,64 @@
+# This workflow will build a Java project with Gradle
+# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-gradle
+
+name: build with gradle
+
+on:
+ push:
+ branches: [ main ]
+ pull_request:
+ branches: [ main, development ]
+
+env:
+ # JDK version used for building jar file
+ currentBuildVersion: 21
+jobs:
+ build:
+ runs-on: ${{ matrix.operating-system }}
+ strategy:
+ matrix:
+ operating-system: [ubuntu-latest, macOS-latest, windows-latest]
+ # Use both LTS releases and latest one for tests
+ jdk: [ 21, 25 ]
+ steps:
+ - name: Checkout repo
+ uses: actions/checkout@v6
+ - name: Set up OpenJDK version ...
+ uses: actions/setup-java@v5
+ with:
+ distribution: 'zulu'
+ java-version: ${{ matrix.jdk }}
+ - name: Grant execute permission for gradlew
+ run: chmod +x gradlew
+ - name: Build with Gradle
+ run: |
+ if [ "$RUNNER_OS" == "Linux" ]; then
+ ./gradlew clean build
+ elif [ "$RUNNER_OS" == "macOS" ]; then
+ ./gradlew clean build
+ elif [ "$RUNNER_OS" == "Windows" ]; then
+ ./gradlew.bat clean build
+ else
+ echo "$RUNNER_OS not supported"
+ exit 1
+ fi
+ shell: bash
+ coverage:
+ needs: build
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout repo
+ uses: actions/checkout@v6
+ - name: Set up OpenJDK version ...
+ uses: actions/setup-java@v5
+ with:
+ distribution: 'zulu'
+ java-version: ${{ env.currentBuildVersion }}
+ - name: Grant execute permission for gradlew
+ run: chmod +x gradlew
+ - name: Build with Gradle (JDK ${{ env.currentBuildVersion }})
+ run: ./gradlew clean check jacocoTestReport
+ - name: Codecov
+ uses: codecov/codecov-action@v5
+ with:
+ files: ./build/reports/jacoco/test/jacocoTestReport.xml #optional
\ No newline at end of file
diff --git a/.github/workflows/publish-plugin-core.yml b/.github/workflows/publish-plugin-core.yml
new file mode 100644
index 0000000..e093f39
--- /dev/null
+++ b/.github/workflows/publish-plugin-core.yml
@@ -0,0 +1,21 @@
+name: Publish mapping-plugin-core to the Maven Central Repository
+on:
+ release:
+ types: [published]
+jobs:
+ publish:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v6
+ - name: Set up Java
+ uses: actions/setup-java@v5.1.0
+ with:
+ java-version: 21
+ distribution: 'zulu' # openjdk
+ - name: Publish package
+ run: ./gradlew -PbuildProfile=deploy publishToSonatype closeAndReleaseSonatypeStagingRepository
+ env:
+ ORG_GRADLE_PROJECT_sonatypeUsername : ${{ secrets.OSSRH_USERNAME }}
+ ORG_GRADLE_PROJECT_sonatypePassword : ${{ secrets.OSSRH_PASSWORD }}
+ ORG_GRADLE_PROJECT_signingKey : ${{ secrets.SIGNING_KEY }}
+ ORG_GRADLE_PROJECT_signingPassword : ${{ secrets.SIGNING_SECRET }}
diff --git a/.gitignore b/.gitignore
index 5b0af70..79b6346 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,4 @@
-cp .gradle
+.gradle
build/
!gradle/wrapper/gradle-wrapper.jar
!**/src/main/**/build/
@@ -37,4 +37,4 @@ bin/
.vscode/
### Mac OS ###
-.DS_Store
\ No newline at end of file
+.DS_Store
diff --git a/.idea/.gitignore b/.idea/.gitignore
deleted file mode 100644
index 13566b8..0000000
--- a/.idea/.gitignore
+++ /dev/null
@@ -1,8 +0,0 @@
-# Default ignored files
-/shelf/
-/workspace.xml
-# Editor-based HTTP Client requests
-/httpRequests/
-# Datasource local storage ignored files
-/dataSources/
-/dataSources.local.xml
diff --git a/.idea/copilot.data.migration.agent.xml b/.idea/copilot.data.migration.agent.xml
deleted file mode 100644
index 4ea72a9..0000000
--- a/.idea/copilot.data.migration.agent.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/copilot.data.migration.ask.xml b/.idea/copilot.data.migration.ask.xml
deleted file mode 100644
index 7ef04e2..0000000
--- a/.idea/copilot.data.migration.ask.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/copilot.data.migration.ask2agent.xml b/.idea/copilot.data.migration.ask2agent.xml
deleted file mode 100644
index 1f2ea11..0000000
--- a/.idea/copilot.data.migration.ask2agent.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/copilot.data.migration.edit.xml b/.idea/copilot.data.migration.edit.xml
deleted file mode 100644
index 8648f94..0000000
--- a/.idea/copilot.data.migration.edit.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/gradle.xml b/.idea/gradle.xml
deleted file mode 100644
index 9d62f93..0000000
--- a/.idea/gradle.xml
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
deleted file mode 100644
index 5cd9a10..0000000
--- a/.idea/misc.xml
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
deleted file mode 100644
index 94a25f7..0000000
--- a/.idea/vcs.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..94b3316
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,25 @@
+# Changelog
+All notable changes to this project will be documented in this file.
+
+The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
+and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+
+## [Unreleased]
+
+### Changed
+
+### Added
+
+### Fixed
+
+### Libs
+
+### Plugins
+
+### Github Actions
+
+## [0.9.0] - date 2026-01-20
+First release of JsonPatch library.
+
+[Unreleased]: https://github.com/kit-data-manager/metastore2/compare/v0.9.0...HEAD
+[0.9.0]: https://github.com/kit-data-manager/metastore2/releases/tag/v0.9.0
\ No newline at end of file
diff --git a/CITATION.cff b/CITATION.cff
new file mode 100644
index 0000000..3567a44
--- /dev/null
+++ b/CITATION.cff
@@ -0,0 +1,19 @@
+cff-version: 1.2.0
+message: "If you use this software, please cite it as below."
+authors:
+- family-names: "Hartmann"
+ given-names: "Volker"
+ affiliation: Karlsruhe Institute of Technology
+ email: volker.hartmann@kit.edu
+ orcid: "https://orcid.org/0000-0001-6383-5214"
+- family-names: "Jejkal"
+ given-names: "Thomas"
+ affiliation: Karlsruhe Institute of Technology
+ email: thomas.jejkal@kit.edu
+ orcid: "https://orcid.org/0000-0003-2804-688X"
+title: "JSONPatch"
+type: software
+abstract: "JSONPatch is a Java library that implements the JSON Patch standard (RFC 6902) for applying partial modifications to JSON documents. It provides functionality to create, apply, and validate JSON Patch operations, making it easier to manage and manipulate JSON data structures in Java applications."
+url: "https://github.com/kit-data-manager/JsonPatch"
+repository-code: "https://github.com/kit-data-manager/JsonPatch"
+license: Apache-2.0
diff --git a/README.md b/README.md
index 32ed8f2..63eb26d 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,9 @@
+[](https://github.com/kit-data-manager/JsonPatch/actions/workflows/gradle.yml)
+[](https://codecov.io/gh/kit-data-manager/JsonPatch)
+[](https://github.com/kit-data-manager/JsonPatch/actions/workflows/codeql-analysis.yml)
+
+
+
# JSON Patch for Jackson 3
A small Java library that applies JSON Patch (RFC 6902) and JSON Merge Patch (RFC 7396) using Jakarta JSON-P and maps the results to POJOs with Jackson 3 (for example in Spring Boot 4).
@@ -70,7 +76,9 @@ import edu.kit.datamanager.util.json.JsonPatchUtil;
public static class Person { public String name; public int age; }
JsonMapper mapper = JsonMapper.builder().build();
-Person original = new Person(); original.name = "Alice"; original.age = 30;
+Person original = new Person();
+original.name = "Alice";
+original.age = 30;
String patchJson = "[ { \"op\": \"replace\", \"path\": \"/name\", \"value\": \"Bob\" } ]";
diff --git a/build.gradle b/build.gradle
index 892fd97..06e0e94 100644
--- a/build.gradle
+++ b/build.gradle
@@ -5,12 +5,14 @@ plugins {
id 'net.researchgate.release' version '3.1.0'
id 'io.github.gradle-nexus.publish-plugin' version '2.0.0'
id 'maven-publish'
+ id 'jacoco'
}
+description = 'A small utility library for applying JSON Patches to JSON documents.'
group = 'edu.kit.datamanager'
-println "Building ${name} version: ${version}"
+println "Building '${name}' version: '${version}'"
println "Running gradle version: $gradle.gradleVersion"
println "JDK version: ${JavaVersion.current()}"
@@ -20,39 +22,96 @@ repositories {
}
java {
- sourceCompatibility = JavaVersion.VERSION_17
- targetCompatibility = JavaVersion.VERSION_17
+ sourceCompatibility = JavaVersion.VERSION_21
+ targetCompatibility = JavaVersion.VERSION_21
}
-dependencies {
- implementation 'org.slf4j:slf4j-api:2.0.9'
+def buildPofile = project.findProperty("buildProfile") ?: "minimal"
- api 'tools.jackson.core:jackson-databind:3.0.3'
- api 'jakarta.json:jakarta.json-api:2.0.1'
+if (buildPofile == "deploy") {
+ println "Using release profile for building '${name}'"
+ apply from: '$rootDir/gradle/profile-deploy.gradle'
+}
- api 'jakarta.json:jakarta.json-api:2.1.3' // API definition
-// implementation 'org.glassfish:jakarta.json:2.0.1' // reference implementation
+//check if release build is requested
+def requested = gradle.startParameter.taskNames
+if (requested.any { it.toLowerCase().endsWith("release") }) {
+ apply from: "$rootDir/gradle/profile-release.gradle"
+}
+dependencies {
implementation 'org.eclipse.parsson:parsson:1.1.4'
+ implementation 'org.slf4j:slf4j-api:2.0.9'
+
+ api 'jakarta.json:jakarta.json-api:2.1.3' // API definition
+ api 'tools.jackson.core:jackson-databind:3.0.3'
testImplementation 'org.junit.jupiter:junit-jupiter:5.10.2'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher:1.10.2'
}
-
+/////////////////////////////////////////////////////////////////////////////////
+// Code coverage configuration with JaCoCo
+/////////////////////////////////////////////////////////////////////////////////
test {
+ finalizedBy jacocoTestReport
useJUnitPlatform()
}
+jacoco {
+ toolVersion = "0.8.14"
+}
+
+jacocoTestReport {
+ dependsOn test
+ reports {
+ xml.required = true
+ csv.required = false
+ html.outputLocation = layout.buildDirectory.dir("jacocoHtml")
+ }
+}
+///////////////////////////////////////////////////////////////////////////////
+//for plugin maven-publish
+//see https://docs.gradle.org/current/userguide/publishing_maven.html
+///////////////////////////////////////////////////////////////////////////////
publishing {
publications {
mavenJava(MavenPublication) {
from components.java
- artifactId = 'JsonPatch'
+ artifactId = 'json-patch-util'
+ version = project.version
+ pom {
+ name = 'JSON Patch Utility Library'
+ description = 'A small utility library for applying JSON Patches to JSON documents.'
+ url = 'https://datamanager.kit.edu'
+ licenses {
+ license {
+ name = 'The Apache License, Version 2.0'
+ url = 'https://www.apache.org/licenses/LICENSE-2.0.txt'
+ }
+ }
+ developers {
+ developer {
+ id = 'Jejkal'
+ name = 'Thomas Jejkal'
+ email = 'webmaster@datamanager.kit.edu'
+ roles = ['developer', 'maintainer']
+ }
+ developer {
+ id = 'HartmannV'
+ name = 'Volker Hartmann'
+ roles = ['developer', 'maintainer']
+ }
+ }
+ scm {
+ connection = 'scm:git:github.com/kit-data-manager/JsonPatch'
+ developerConnection = 'scm:git:github.com/kit-data-manager/JsonPatch'
+ url = 'https://github.com/kit-data-manager/JsonPatch'
+ }
+ }
}
}
repositories {
mavenLocal()
-
}
}
diff --git a/codemeta.json b/codemeta.json
new file mode 100644
index 0000000..17414d9
--- /dev/null
+++ b/codemeta.json
@@ -0,0 +1,89 @@
+{
+ "@context": "https://w3id.org/codemeta/3.0",
+ "type": "SoftwareSourceCode",
+ "applicationCategory": "DeveloperApplication",
+ "applicationSubCategory": "JSON Processing",
+ "codeRepository": "https://github.com/kit-data-manager/JSONPatch.git",
+ "name": "JsonPatch",
+ "dateCreated": "2025-12-17",
+ "description": "JSONPatch is a Java library that implements the JSON Patch standard (RFC 6902) for applying partial modifications to JSON documents. It provides functionality to create, apply, and validate JSON Patch operations, making it easier to manage and manipulate JSON data structures in Java applications.",
+ "author": [
+ {
+ "@type": "Person",
+ "givenName": "Volker",
+ "familyName": "Hartmann",
+ "email": "volker.hartmann@kit.edu",
+ "@id": "https://orcid.org/0000-0001-6383-5214",
+ "identifier": "https://orcid.org/0000-0001-6383-5214",
+ "affiliation": "Karlsruhe Institute of Technology (KIT)"
+ },
+ {
+ "@type": "Person",
+ "givenName": "Thomas",
+ "familyName": "Jejkal",
+ "email": "thomas.jejkal@kit.edu",
+ "@id": "https://orcid.org/0000-0003-2804-688X",
+ "identifier": "https://orcid.org/0000-0003-2804-688X",
+ "affiliation": "Karlsruhe Institute of Technology (KIT)"
+ }
+ ],
+ "maintainer": [
+ {
+ "@type": "Person",
+ "givenName": "Volker",
+ "familyName": "Hartmann",
+ "email": "volker.hartmann@kit.edu",
+ "@id": "https://orcid.org/0000-0001-6383-5214",
+ "identifier": "https://orcid.org/0000-0001-6383-5214",
+ "affiliation": "Karlsruhe Institute of Technology (KIT)"
+ },
+ {
+ "@type": "Person",
+ "givenName": "Thomas",
+ "familyName": "Jejkal",
+ "email": "thomas.jejkal@kit.edu",
+ "@id": "https://orcid.org/0000-0003-2804-688X",
+ "identifier": "https://orcid.org/0000-0003-2804-688X",
+ "affiliation": "Karlsruhe Institute of Technology (KIT)"
+ }
+ ],
+
+ "funder": [
+ {
+ "@type": "Organization",
+ "name": "Helmholtz Association of German Research Centers",
+ "identifier": {
+ "@type": "PropertyValue",
+ "propertyID": "ROR",
+ "value": "https://ror.org/0281dp749"
+ },
+ "sameAs": "https://www.helmholtz.de/"
+ },
+ {
+ "@type": "Organization",
+ "name": "Helmholtz Metadata Collaboration (HMC)",
+ "identifier": {
+ "@type": "PropertyValue",
+ "propertyID": "ROR",
+ "value": "https://ror.org/04v4h0v24"
+ },
+ "sameAs": "https://helmholtz-metadaten.de/"
+ }
+ ],
+ "keywords": [
+ "Java",
+ "JSON",
+ "JSON Patch",
+ "RFC 6902",
+ "RFC 7396",
+ "Jackson 3",
+ "Library"
+ ],
+ "license": "https://spdx.org/licenses/Apache-2.0",
+ "runtimePlatform": "JVM",
+ "softwareRequirements": [
+ "Java 17 or higher"
+ ],
+ "developmentStatus": "active",
+ "issueTracker": "https://github.com/kit-data-manager/JSONPatch/issues"
+}
diff --git a/gradle.properties b/gradle.properties
index a74a7d8..1ae0a94 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -1,6 +1,6 @@
-systemProp.jdk.tls.client.protocols="TLSv1,TLSv1.1,TLSv1.2"
+systemProp.jdk.tls.client.protocols=TLSv1.2,TLSv1.3
-//-----------------------------
-// Properties for build.gradle
-//-----------------------------
+# -----------------------------
+# Properties for build.gradle
+# -----------------------------
version=1.0.0-SNAPSHOT
diff --git a/gradle/profile-deploy.gradle b/gradle/profile-deploy.gradle
new file mode 100644
index 0000000..04713cb
--- /dev/null
+++ b/gradle/profile-deploy.gradle
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2026 Karlsruhe Institute of Technology.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+////////////////////////////////////////////////////////////////////////////////
+//for java plugin (package JavaDoc and Sources jars)
+//see https://docs.gradle.org/current/userguide/java_plugin.html
+////////////////////////////////////////////////////////////////////////////////
+java {
+ sourceCompatibility = JavaVersion.VERSION_21
+ targetCompatibility = JavaVersion.VERSION_21
+ withSourcesJar()
+ withJavadocJar()
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//for plugin io.github.gradle-nexus.publish-plugin
+//see https://github.com/gradle-nexus/publish-plugin
+////////////////////////////////////////////////////////////////////////////////
+nexusPublishing {
+ repositories {
+ // Migration to Sontatype Central
+ // see https://github.com/gradle-nexus/publish-plugin?tab=readme-ov-file#publishing-to-maven-central-via-sonatype-central
+ // see https://central.sonatype.org/publish/publish-portal-ossrh-staging-api/#configuration
+ sonatype {
+ nexusUrl.set(uri("https://ossrh-staging-api.central.sonatype.com/service/local/"))
+ snapshotRepositoryUrl.set(uri("https://central.sonatype.com/repository/maven-snapshots/"))
+ }
+ // You need to set your Central credentials (those are different from the legacy OSSRH ones).
+ // To increase security, it is advised to use the user token's username and password pair
+ // (instead of regular username and password).
+ // Those values should be set as the
+ // sonatypeUsername and
+ // sonatypePassword project properties, e.g. in ~/.gradle/gradle.properties or
+ // via the
+ // ORG_GRADLE_PROJECT_sonatypeUsername and
+ // ORG_GRADLE_PROJECT_sonatypePassword environment variables.
+ // For generating the user token, see https://central.sonatype.org/publish/generate-portal-token/
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//for plugin signing
+//see https://docs.gradle.org/current/userguide/signing_plugin.html
+////////////////////////////////////////////////////////////////////////////////
+signing {
+ //make signing required unless for SNAPSHOT releases or if signing is explicitly skipped
+ required { !project.version.endsWith("-SNAPSHOT") && !project.hasProperty("skipSigning") }
+
+ //look for property 'signingKey'
+ if (project.findProperty("signingKey")) {
+ //If required, read a sub-key specified by its ID in property signingKeyId
+ //def signingKeyId = findProperty("signingKeyId")
+ //read property 'signingKey'
+ def signingKey = findProperty("signingKey")
+ //read property 'signingPassword'
+ def signingPassword = findProperty("signingPassword")
+ //Select to use in-memory ascii-armored keys
+ useInMemoryPgpKeys(signingKey, signingPassword)
+ //Only if also using signingKeyId
+ //useInMemoryPgpKeys(signingKeyId, signingKey, signingPassword)
+
+ //Apply signing to publication identity 'publishing.publications.maven'
+ sign publishing.publications.mavenJava
+ }else {
+ println 'WARNING: No property \'signingKey\' found. Artifact signing will be skipped.'
+ }
+}
\ No newline at end of file
diff --git a/gradle/profile-release.gradle b/gradle/profile-release.gradle
new file mode 100644
index 0000000..31805b7
--- /dev/null
+++ b/gradle/profile-release.gradle
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2026 Karlsruhe Institute of Technology.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+////////////////////////////////////////////////////////////////////////////////
+// Version tags start with 'v' e.g. v1.0.0
+// codemeta.json and CITATION.cff are updated before release
+////////////////////////////////////////////////////////////////////////////////
+import java.time.LocalDate
+import java.time.format.DateTimeFormatter
+import groovy.json.JsonSlurper
+import groovy.json.JsonOutput
+
+
+def todayDate = LocalDate.now().format(DateTimeFormatter.ISO_DATE)
+
+////////////////////////////////////////////////////////////////////////////////
+//for plugin net.researchgate.release
+//see https://github.com/researchgate/gradle-release
+////////////////////////////////////////////////////////////////////////////////
+release {
+ //define template for tagging, e.g. v1.0.0
+ tagTemplate = 'v${version}'
+ //set source file of version property
+ versionPropertyFile = 'gradle.properties'
+ //set possible properties which may contain the version
+ versionProperties = ['version', 'mainversion']
+ git {
+ //branch from where to release (default: main)
+ requireBranch.set('main')
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// update CITATION.cff file (just date and version)
+////////////////////////////////////////////////////////////////////////////////
+tasks.register('updateCitationFile') {
+ doLast {
+ def citationFile = file('CITATION.cff')
+ def lines = citationFile.readLines()
+ def map = ['version': project.version, 'date-released': todayDate]
+ map.each { key, value ->
+ lines = lines.collect { line ->
+ if (line.trim().startsWith("${key}:")) {
+ return "${key}: ${value}"
+ } else {
+ return line
+ }
+ }
+ }
+ citationFile.text = lines.join(System.lineSeparator())
+ println "Updated CITATION.cff with version ${project.version} and date ${todayDate}"
+ }
+}
+////////////////////////////////////////////////////////////////////////////////
+// update codemeta.json file (just date and version)
+////////////////////////////////////////////////////////////////////////////////
+tasks.register('updateCodemetaFile') {
+ doLast {
+ def codemetaFile = file('codemeta.json')
+ def codemeta = new JsonSlurper().parseText(codemetaFile.text)
+ codemeta.version = project.version
+ codemeta.softwareVersion = project.version
+ codemeta.dateModified = todayDate
+
+ codemetaFile.text = JsonOutput.prettyPrint(JsonOutput.toJson(codemeta))
+ println "Updated codemeta.json with (software)version ${project.version} and date ${todayDate}"
+ }
+}
+////////////////////////////////////////////////////////////////////////////////
+// Hook the update tasks into the release process
+////////////////////////////////////////////////////////////////////////////////
+tasks.named('beforeReleaseBuild') {
+ dependsOn 'updateCitationFile', 'updateCodemetaFile'
+}
diff --git a/renovate.json b/renovate.json
new file mode 100644
index 0000000..2ae7365
--- /dev/null
+++ b/renovate.json
@@ -0,0 +1,4 @@
+{
+ "labels": ["dependencies"],
+ "baseBranches": ["development"]
+}
diff --git a/settings.gradle b/settings.gradle
index 94ebf86..5d7de83 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -1 +1 @@
-rootProject.name = 'JsonPatch'
\ No newline at end of file
+rootProject.name = 'json-patch-util'
\ No newline at end of file
diff --git a/src/main/java/edu/kit/datamanager/util/json/JsonPatch.java b/src/main/java/edu/kit/datamanager/util/json/JsonPatch.java
index 1d1075a..cdfc9ac 100644
--- a/src/main/java/edu/kit/datamanager/util/json/JsonPatch.java
+++ b/src/main/java/edu/kit/datamanager/util/json/JsonPatch.java
@@ -21,9 +21,11 @@
import java.util.List;
/**
- * Json structure holding an array of patches.
+ * JSON structure holding an array of patches.
+ *
+ * @param operations List of operations in this patch.
*/
-public class JsonPatch {
+public record JsonPatch(List operations) {
/**
* Enumeration of supported JSON Patch operations.
*/
@@ -38,13 +40,17 @@ public enum OperationType {
/**
* Use op for (JSON) serialization.
+ *
* @return String representation of the operation
*/
@JsonValue
- public String jsonValue() { return op; }
+ public String jsonValue() {
+ return op;
+ }
/**
* Ignore case for deserializing JSON value.
+ *
* @param value JSON value
* @return Corresponding OperationType
*/
@@ -66,26 +72,23 @@ public String toString() {
}
}
- /**
- * List of operations in this patch.
- */
- private final List operations;
/**
* Constructor for JSON deserialization.
*
* @param operations List of operations in this patch.
*/
@JsonCreator(mode = JsonCreator.Mode.DELEGATING)
- public JsonPatch(List operations) {
- this.operations = operations;
+ public JsonPatch {
}
+
/**
* Get the list of operations in this patch.
*
* @return List of operations.
*/
+ @Override
@JsonValue
- public List getOperations() {
+ public List operations() {
return operations;
}
@@ -93,5 +96,6 @@ public List getOperations() {
* Representation of a single JSON Patch operation as a nested record.
* Accessible from outside as `JsonPatch.Operation`.
*/
- public record Operation(OperationType op, String path, String from, Object value) {}
+ public record Operation(OperationType op, String path, String from, Object value) {
+ }
}
\ No newline at end of file
diff --git a/src/main/java/edu/kit/datamanager/util/json/JsonPatchUtil.java b/src/main/java/edu/kit/datamanager/util/json/JsonPatchUtil.java
index 372110e..f7fda23 100644
--- a/src/main/java/edu/kit/datamanager/util/json/JsonPatchUtil.java
+++ b/src/main/java/edu/kit/datamanager/util/json/JsonPatchUtil.java
@@ -116,7 +116,7 @@ public static String applyPatch(String originalJson, String patchJson) throws Js
* @throws JsonPatchProcessingException in case of processing errors
*/
public static String applyPatch(String originalJson, JsonPatch jsonPatch) throws JsonPatchProcessingException {
- return applyPatch(originalJson, jsonPatch, (PatchOptions) null);
+ return applyPatch(originalJson, jsonPatch, null);
}
/** Applies a JSON Patch (RFC 6902) to a JSON string.
* @param originalJson Original JSON as String
@@ -125,7 +125,7 @@ public static String applyPatch(String originalJson, JsonPatch jsonPatch) throws
* @throws JsonPatchProcessingException in case of processing errors
*/
public static String applyPatch(String originalJson, JsonNode patchJson) throws JsonPatchProcessingException {
- return applyPatch(originalJson, patchJson, (PatchOptions) null);
+ return applyPatch(originalJson, patchJson, null);
}
/** Applies a JSON Patch (RFC 6902) to a JsonNode.
*
@@ -146,7 +146,7 @@ public static JsonNode applyPatch(JsonNode original, String jsonPatch) throws J
* @throws JsonPatchProcessingException in case of processing errors
*/
public static JsonNode applyPatch(JsonNode original, JsonPatch jsonPatch) throws JsonPatchProcessingException {
- return applyPatch(original, jsonPatch, (PatchOptions) null);
+ return applyPatch(original, jsonPatch, null);
}
/**
* Applies a JSON Patch (RFC 6902) to a JsonNode.
@@ -157,7 +157,7 @@ public static JsonNode applyPatch(JsonNode original, JsonPatch jsonPatch) throws
* @throws JsonPatchProcessingException in case of processing errors
*/
public static JsonNode applyPatch(JsonNode original, JsonNode patch) throws JsonPatchProcessingException {
- return applyPatch(original, patch, (PatchOptions) null);
+ return applyPatch(original, patch, null);
}
/**
* Applies a JSON Patch (RFC 6902) to an object.
@@ -174,8 +174,8 @@ public static T applyPatch(T original, String patchJson, Class type) thr
/**
* Applies a JSON Patch (RFC 6902) to an object represented by a string object.
* (This could be used if you are still using Jackson 2.)
- * @param originalJsonAsString Original objekt
- * @param patchJson JSON Patch (Aaray of operations)
+ * @param originalJsonAsString Original object
+ * @param patchJson JSON Patch (Array of operations)
* @param options optional Patch-Options (e.g. blocked paths), null values allowed
* @return Patched JSON as String
* @throws JsonPatchProcessingException in case of processing errors
@@ -216,8 +216,8 @@ public static String applyPatch(String originalJsonAsString, String patchJson, P
/**
* Applies a JSON Patch (RFC 6902) to an object represented by a string object.
* (This could be used if you are still using Jackson 2.)
- * @param originalJsonAsString Original objekt
- * @param patchJson JSON Patch (Aaray of operations)
+ * @param originalJsonAsString Original object
+ * @param patchJson JSON Patch (Array of operations)
* @param options optional Patch-Options (e.g. blocked paths), null values allowed
* @return Patched JSON as String
* @throws JsonPatchProcessingException in case of processing errors
@@ -228,8 +228,8 @@ public static String applyPatch(String originalJsonAsString, JsonPatch patchJson
/**
* Applies a JSON Patch (RFC 6902) to an object represented by a string object.
* (This could be used if you are still using Jackson 2.)
- * @param originalJsonAsString Original objekt
- * @param patchJson JSON Patch (Aaray of operations)
+ * @param originalJsonAsString Original object
+ * @param patchJson JSON Patch (Array of operations)
* @param options optional Patch-Options (e.g. blocked paths), null values allowed
* @return Patched JSON as String
* @throws JsonPatchProcessingException in case of processing errors
@@ -277,8 +277,8 @@ public static JsonNode applyPatch(JsonNode original, JsonNode patch, PatchOption
/**
* Applies a JSON Patch (RFC 6902) to an object.
*
- * @param original Original objekt
- * @param patchJson JSON Patch (Aaray of operations)
+ * @param original Original object
+ * @param patchJson JSON Patch (Array of operations)
* @param type target type
* @param options optional Patch-Options (e.g. blocked paths), null values allowed
* @return Patched object
@@ -405,7 +405,10 @@ public static T jsonStringToObject(String json, Class type) throws Json
throw new JsonPatchProcessingException("Error while parsing JSON string to object!", e);
}
}
-
+ /** Reset the JsonPatchUtil for testing purposes.
+ * This method clears any configured mapper settings and allows
+ * re-configuration. It should only be used in testing!
+ */
static void resetForTesting() {
if (!Boolean.getBoolean("json.patch.resetForTesting")) {
throw new IllegalStateException("Resetting JsonPatchUtil is only allowed in testing!");
diff --git a/src/test/java/edu/kit/datamanager/util/json/JsonPatchAdditionalOpsTest.java b/src/test/java/edu/kit/datamanager/util/json/JsonPatchAdditionalOpsTest.java
index 7798d2b..281f6d7 100644
--- a/src/test/java/edu/kit/datamanager/util/json/JsonPatchAdditionalOpsTest.java
+++ b/src/test/java/edu/kit/datamanager/util/json/JsonPatchAdditionalOpsTest.java
@@ -27,7 +27,7 @@
public class JsonPatchAdditionalOpsTest {
@Test
- public void moveOperationMovesValue() throws Exception {
+ public void moveOperationMovesValue() {
JsonMapper mapper = JsonMapper.builder().build();
JsonNode original = mapper.readTree("{\"a\":{\"value\":\"v\"},\"b\":{}}");
@@ -36,12 +36,12 @@ public void moveOperationMovesValue() throws Exception {
JsonNode patched = JsonPatchUtil.applyPatch(original, patch);
- assertEquals("v", patched.at("/b/value").asText());
+ assertEquals("v", patched.at("/b/value").asString());
assertTrue(patched.at("/a/value").isMissingNode());
}
@Test
- public void copyOperationCopiesValue() throws Exception {
+ public void copyOperationCopiesValue() {
JsonMapper mapper = JsonMapper.builder().build();
JsonNode original = mapper.readTree("{\"a\":{\"value\":\"v\"},\"b\":{}}");
@@ -50,12 +50,12 @@ public void copyOperationCopiesValue() throws Exception {
JsonNode patched = JsonPatchUtil.applyPatch(original, patch);
- assertEquals("v", patched.at("/b/value").asText());
- assertEquals("v", patched.at("/a/value").asText()); // original remains
+ assertEquals("v", patched.at("/b/value").asString());
+ assertEquals("v", patched.at("/a/value").asString()); // original remains
}
@Test
- public void testOperationSucceedsAndFails() throws Exception {
+ public void testOperationSucceedsAndFails() {
JsonMapper mapper = JsonMapper.builder().build();
JsonNode original = mapper.readTree("{\"x\":1}");
@@ -72,7 +72,7 @@ public void testOperationSucceedsAndFails() throws Exception {
}
@Test
- public void addToArrayWithDashAppends() throws Exception {
+ public void addToArrayWithDashAppends() {
JsonMapper mapper = JsonMapper.builder().build();
JsonNode original = mapper.readTree("{\"arr\":[1,2]}\n");
@@ -85,7 +85,7 @@ public void addToArrayWithDashAppends() throws Exception {
}
@Test
- public void removeArrayIndexRemovesElement() throws Exception {
+ public void removeArrayIndexRemovesElement() {
JsonMapper mapper = JsonMapper.builder().build();
JsonNode original = mapper.readTree("{\"arr\":[1,2,3]}\n");
@@ -99,7 +99,7 @@ public void removeArrayIndexRemovesElement() throws Exception {
}
@Test
- public void replaceNestedValue() throws Exception {
+ public void replaceNestedValue() {
JsonMapper mapper = JsonMapper.builder().build();
JsonNode original = mapper.readTree("{\"obj\":{\"nested\":\"old\"}}\n");
@@ -107,7 +107,7 @@ public void replaceNestedValue() throws Exception {
JsonPatch patch = new JsonPatch(ops);
JsonNode patched = JsonPatchUtil.applyPatch(original, patch);
- assertEquals("new", patched.at("/obj/nested").asText());
+ assertEquals("new", patched.at("/obj/nested").asString());
}
}
diff --git a/src/test/java/edu/kit/datamanager/util/json/JsonPatchIntegrationTest.java b/src/test/java/edu/kit/datamanager/util/json/JsonPatchIntegrationTest.java
index 0fa19dd..d2d010a 100644
--- a/src/test/java/edu/kit/datamanager/util/json/JsonPatchIntegrationTest.java
+++ b/src/test/java/edu/kit/datamanager/util/json/JsonPatchIntegrationTest.java
@@ -25,24 +25,25 @@
public class JsonPatchIntegrationTest {
- // Static nested test POJOs so Jackson can construct/deserialise them
+ // Static nested test POJOs so Jackson can construct/deserialize them
public static class InnerClass { public String name; public int[] numbers; }
public static class Container { public InnerClass inner; }
@Test
- public void deepNestedReplaceAddMoveCopy() throws Exception {
+ public void deepNestedReplaceAddMoveCopy() {
JsonMapper mapper = JsonMapper.builder().build();
- String originalJson = "{\n" +
- " \"root\": {\n" +
- " \"level1\": {\n" +
- " \"level2\": {\n" +
- " \"value\": \"old\",\n" +
- " \"arr\": [ { \"id\": 1, \"items\": [\"a\",\"b\"] }, { \"id\": 2 } ]\n" +
- " }\n" +
- " },\n" +
- " \"other\": { \"copyMe\": { \"f\": true } }\n" +
- " }\n" +
- "}";
+ String originalJson = """
+ {
+ "root": {
+ "level1": {
+ "level2": {
+ "value": "old",
+ "arr": [ { "id": 1, "items": ["a","b"] }, { "id": 2 } ]
+ }
+ },
+ "other": { "copyMe": { "f": true } }
+ }
+ }""";
JsonNode original = mapper.readTree(originalJson);
@@ -63,11 +64,11 @@ public void deepNestedReplaceAddMoveCopy() throws Exception {
JsonNode patched = JsonPatchUtil.applyPatch(original, patch);
// assertions
- assertEquals("new", patched.at("/root/level1/level2/value").asText());
+ assertEquals("new", patched.at("/root/level1/level2/value").asString());
// after adding at index 1, items should be ["a","x","b"]
JsonNode items = patched.at("/root/level1/level2/arr/0/items");
assertEquals(3, items.size());
- assertEquals("x", items.get(1).asText());
+ assertEquals("x", items.get(1).asString());
// moved id should exist
assertEquals(1, patched.at("/root/other/movedId").asInt());
@@ -79,16 +80,17 @@ public void deepNestedReplaceAddMoveCopy() throws Exception {
}
@Test
- public void applyPatchToPojoWithNestedObjects() throws Exception {
+ public void applyPatchToPojoWithNestedObjects() {
Container original = new Container();
original.inner = new InnerClass();
original.inner.name = "foo";
original.inner.numbers = new int[]{1,2};
- String patchJson = "["
- + "{ \"op\": \"replace\", \"path\": \"/inner/name\", \"value\": \"bar\" },"
- + "{ \"op\": \"add\", \"path\": \"/inner/numbers/-\", \"value\": 3 }"
- + "]";
+ String patchJson = """
+ [
+ { "op": "replace", "path": "/inner/name", "value": "bar" },
+ { "op": "add", "path": "/inner/numbers/-", "value": 3 }
+ ]""";
Container patched = JsonPatchUtil.applyPatch(original, patchJson, Container.class);
@@ -97,7 +99,7 @@ public void applyPatchToPojoWithNestedObjects() throws Exception {
}
@Test
- public void complexSequenceWithArraysAndObjects() throws Exception {
+ public void complexSequenceWithArraysAndObjects() {
JsonMapper mapper = JsonMapper.builder().build();
String originalJson = "{\"list\":[{\"k\":1},{\"k\":2},{\"k\":3}],\"map\":{\"a\":{\"v\":10}}}";
JsonNode original = mapper.readTree(originalJson);
diff --git a/src/test/java/edu/kit/datamanager/util/json/JsonPatchOperationTypeTest.java b/src/test/java/edu/kit/datamanager/util/json/JsonPatchOperationTypeTest.java
index f8ec130..1ffe382 100644
--- a/src/test/java/edu/kit/datamanager/util/json/JsonPatchOperationTypeTest.java
+++ b/src/test/java/edu/kit/datamanager/util/json/JsonPatchOperationTypeTest.java
@@ -1,3 +1,18 @@
+/*
+ * Copyright 2025 Karlsruhe Institute of Technology.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
package edu.kit.datamanager.util.json;
import org.junit.jupiter.api.Test;
@@ -34,9 +49,7 @@ public void testFromJson() {
}
@Test
public void testFromJsonWithInvalidValue() {
- Exception exception = assertThrows(IllegalArgumentException.class, () -> {
- JsonPatch.OperationType.fromJson("invalid");
- });
+ Exception exception = assertThrows(IllegalArgumentException.class, () -> JsonPatch.OperationType.fromJson("invalid"));
assertEquals("Unknown operation type: invalid", exception.getMessage());
}
@Test
diff --git a/src/test/java/edu/kit/datamanager/util/json/JsonPatchTest.java b/src/test/java/edu/kit/datamanager/util/json/JsonPatchTest.java
index ab4aef1..db7ba06 100644
--- a/src/test/java/edu/kit/datamanager/util/json/JsonPatchTest.java
+++ b/src/test/java/edu/kit/datamanager/util/json/JsonPatchTest.java
@@ -19,8 +19,8 @@
import tools.jackson.databind.json.JsonMapper;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.List;
+import java.util.Map;
import static org.junit.jupiter.api.Assertions.*;
@@ -29,7 +29,7 @@ public class JsonPatchTest {
@Test
public void defaultConstructorShouldHaveNullOperations() {
JsonPatch p = new JsonPatch(null);
- assertNull(p.getOperations(), "operations should be null by default");
+ assertNull(p.operations(), "operations should be null by default");
}
@Test
@@ -38,8 +38,8 @@ public void parameterizedConstructorAndGetters() {
ops.add(new JsonPatch.Operation(JsonPatch.OperationType.ADD, "/a", null, "value"));
JsonPatch p = new JsonPatch(ops);
- assertSame(ops, p.getOperations());
- assertEquals(1, p.getOperations().size());
+ assertSame(ops, p.operations());
+ assertEquals(1, p.operations().size());
}
@Test
@@ -47,9 +47,9 @@ public void parameterizedConstructorAndGetters2() {
JsonPatch.Operation op = new JsonPatch.Operation(JsonPatch.OperationType.ADD, "/a", null, "value");
List ops = new ArrayList<>();
ops.add(op);
- JsonPatch p = new JsonPatch(Arrays.asList(op));
- assertSame(ops.getFirst(), p.getOperations().getFirst());
- assertEquals(1, p.getOperations().size());
+ JsonPatch p = new JsonPatch(ops);
+ assertSame(ops.getFirst(), p.operations().getFirst());
+ assertEquals(1, p.operations().size());
}
@Test
@@ -59,27 +59,27 @@ public void settersShouldUpdateOperationsAndAllowNulls() {
ops.add(new JsonPatch.Operation(JsonPatch.OperationType.REMOVE, "/b", null, null));
p = new JsonPatch(ops);
- assertSame(ops, p.getOperations());
+ assertSame(ops, p.operations());
}
@Test
public void operationsListCanBeEmpty() {
JsonPatch p = new JsonPatch(new ArrayList<>());
- assertNotNull(p.getOperations());
- assertTrue(p.getOperations().isEmpty());
+ assertNotNull(p.operations());
+ assertTrue(p.operations().isEmpty());
}
@Test
public void testSerializationAndDeserialization() {
String json = """
[{"op":"replace","path":"/name","from":null,"value":"Bob"},
- {"op":"remove","path":"/address","from":null,"value":null},
+ {"op":"remove","path":"/address","from":null,"value":null},
{"op":"add","path":"/age","from":null,"value":"30"}]
""";
JsonMapper mapper = JsonMapper.builder().build();
JsonPatch patch = mapper.readValue(json, JsonPatch.class);
- assertNotNull(patch.getOperations());
- assertEquals(3, patch.getOperations().size());
+ assertNotNull(patch.operations());
+ assertEquals(3, patch.operations().size());
String jsonPatch = mapper.writeValueAsString(patch);
assertNotNull(jsonPatch);
assertEquals( json.replaceAll("\\s",""),jsonPatch);
@@ -91,14 +91,14 @@ public void testAddingAnObject() {
""";
JsonMapper mapper = JsonMapper.builder().build();
JsonPatch patch = mapper.readValue(json, JsonPatch.class);
- assertNotNull(patch.getOperations());
- assertEquals(1, patch.getOperations().size());
- JsonPatch.Operation op = patch.getOperations().get(0);
+ assertNotNull(patch.operations());
+ assertEquals(1, patch.operations().size());
+ JsonPatch.Operation op = patch.operations().get(0);
assertEquals(JsonPatch.OperationType.ADD, op.op());
assertEquals("/person", op.path());
assertNull(op.from());
assertNotNull(op.value());
- assertTrue(op.value() instanceof java.util.Map);
+ assertInstanceOf(Map.class, op.value());
@SuppressWarnings("unchecked")
java.util.Map valueMap = (java.util.Map)op.value();
assertEquals("Alice", valueMap.get("name"));
diff --git a/src/test/java/edu/kit/datamanager/util/json/JsonPatchUtilErrorTest.java b/src/test/java/edu/kit/datamanager/util/json/JsonPatchUtilErrorTest.java
index f729bdb..0c899ca 100644
--- a/src/test/java/edu/kit/datamanager/util/json/JsonPatchUtilErrorTest.java
+++ b/src/test/java/edu/kit/datamanager/util/json/JsonPatchUtilErrorTest.java
@@ -65,13 +65,14 @@ void testConstructorWithInitializeMapperAfterwards_throwsJsonProcessingException
void testResetForTestingWhileNotAllowed_throwsIllegalStateException() {
System.setProperty("json.patch.resetForTesting", "false");
JsonMapper defaultMapper = JsonPatchUtil.getDefaultMapper();
- assertThrows(IllegalStateException.class, () ->JsonPatchUtil.resetForTesting());
+ assertNotNull(defaultMapper);
+ assertThrows(IllegalStateException.class, JsonPatchUtil::resetForTesting);
System.setProperty("json.patch.resetForTesting", "true");
}
@Test
void testApplyJsonPatch_malformedJson_throwsPatchProcessingException() {
- var original = new Customer("1", java.util.List.of("Milk"), "001-555-1234");
+ var original = new Customer("1", java.util.List.of("Milk"), "001-111-1234");
String badPatch = "not-a-json";
assertThrows(JsonPatchProcessingException.class,
@@ -80,7 +81,7 @@ void testApplyJsonPatch_malformedJson_throwsPatchProcessingException() {
@Test
void testApplyJsonPatch_patchNotArray_throwsPatchProcessingException() {
- var original = new Customer("1", java.util.List.of("Milk"), "001-555-1234");
+ var original = new Customer("2", java.util.List.of("Milk"), "001-222-1234");
// Object instead of array => RFC 6902 requires an array of operations
String patchObject = "{\"op\":\"replace\",\"path\":\"/telephone\",\"value\":\"000\"}";
@@ -90,22 +91,22 @@ void testApplyJsonPatch_patchNotArray_throwsPatchProcessingException() {
@Test
void testApplyJsonPatch_addNonExistingField_throwsPatchProcessingException() {
- var original = new Customer("1", java.util.List.of("Milk", "Eggs"), "001-555-1234");
+ var original = new Customer("3", java.util.List.of("Milk", "Eggs"), "001-333-1234");
// Try to add a non-existing field
- String patch = "[{ \"op\": \"add\", \"path\": \"/unkownField\", \"value\": \"SomeValue\" }]";
+ String patch = "[{ \"op\": \"add\", \"path\": \"/unknownField\", \"value\": \"SomeValue\" }]";
// Should work with strings
String patchedJson = JsonPatchUtil.applyPatch(JsonPatchUtil.jsonObjectToString(original), patch);
- assertTrue(patchedJson.contains("\"unkownField\":\"SomeValue\""));
+ assertTrue(patchedJson.contains("\"unknownField\":\"SomeValue\""));
assertThrows(JsonPatchProcessingException.class,
() -> JsonPatchUtil.applyPatch(original, patch, Customer.class));
- String validPatch = "[{ \"op\": \"add\", \"path\": \"/telephone\", \"value\": \"001-555-6789\" }]";
+ String validPatch = "[{ \"op\": \"add\", \"path\": \"/telephone\", \"value\": \"001-333-6789\" }]";
Customer patchedCustomer = JsonPatchUtil.applyPatch(original, validPatch, Customer.class);
- assertEquals("001-555-6789", patchedCustomer.telephone());
+ assertEquals("001-333-6789", patchedCustomer.telephone());
}
@Test
void testApplyJsonPatch_removeNonExistingPath_throwsPatchProcessingException() {
- var original = new Customer("1", java.util.List.of("Milk", "Eggs"), "001-555-1234");
+ var original = new Customer("4", java.util.List.of("Milk", "Eggs"), "001-444-1234");
// Try to remove non-existing index 10
String patch = "[{ \"op\": \"remove\", \"path\": \"/favorites/10\" }]";
@@ -115,7 +116,7 @@ void testApplyJsonPatch_removeNonExistingPath_throwsPatchProcessingException() {
@Test
void testApplyJsonPatch_fromBlockedPath_throwsPatchProcessingException() {
- var original = new Customer("1", java.util.List.of("Milk"), "001-555-1234");
+ var original = new Customer("5", java.util.List.of("Milk"), "001-555-1234");
// Try to move between paths, where "from" is blocked
String patch = "[{ \"op\": \"move\", \"from\": \"/id\", \"path\": \"/telephone\" }]";
var options = PatchOptions.ofBlockedPaths(Set.of("/id"));
@@ -126,7 +127,7 @@ void testApplyJsonPatch_fromBlockedPath_throwsPatchProcessingException() {
@Test
void testApplyJsonMergePatch_notObject_throwsPathProcessingException() {
- var original = new Customer("1", java.util.List.of("Milk"), "001-555-1234");
+ var original = new Customer("6", java.util.List.of("Milk"), "001-666-1234");
// Invalid merge patch
String mergePatchArray = "[ \"telephone\", \"000\" ]";
@@ -136,7 +137,7 @@ void testApplyJsonMergePatch_notObject_throwsPathProcessingException() {
@Test
void testApplyPatch_addToNullArray_createsList() {
- var original = new Customer("1", null, "001-555-1234");
+ var original = new Customer("7", null, "001-777-1234");
String patch = "[{ \"op\": \"add\", \"path\": \"/favorites/-\", \"value\": \"Milk\" }]";
// Can't add to null array
assertThrows(JsonPatchProcessingException.class,
@@ -145,7 +146,7 @@ void testApplyPatch_addToNullArray_createsList() {
@Test
void testApplyPatch_addToNullArray_createsList1() {
- var original = new Customer("1", null, "001-555-1234");
+ var original = new Customer("8", null, "001-888-1234");
String patch = "[{ \"op\": \"add\", \"path\": \"/favorites/0\", \"value\": \"Milk\" }]";
// Can't add to null array
assertThrows(JsonPatchProcessingException.class,
@@ -154,7 +155,7 @@ void testApplyPatch_addToNullArray_createsList1() {
@Test
void testApplyMergePatch_setFieldToNull_resultsNull() {
- var original = new Customer("1", java.util.List.of("Milk"), "001-555-1234");
+ var original = new Customer("9", java.util.List.of("Milk"), "001-999-1234");
String mergePatch = "{\"telephone\":null}";
var result = JsonPatchUtil.applyMergePatch(original, mergePatch, Customer.class);
@@ -166,18 +167,22 @@ void testApplyMergePatch_setFieldToNull_resultsNull() {
void testApplyPatch_optionsWithEmptyBlockedPaths_allowsChange() {
record CustomerMinimal(String id, String telephone) {}
- var original = new CustomerMinimal("1", "001-555-1234");
+ var original = new CustomerMinimal("1", "001-111-1234");
String patch = "[{\"op\":\"replace\",\"path\":\"/id\",\"value\":\"2\"}]";
- var options = PatchOptions.ofBlockedPaths(java.util.Set.of()); // leere Menge
+ var options = PatchOptions.ofBlockedPaths(java.util.Set.of()); // empty set
var result = JsonPatchUtil.applyPatch(original, patch, CustomerMinimal.class, options);
assertEquals("2", result.id());
+
+ var original2 = new CustomerMinimal("3", "001-222-1234");
+ var result2 = JsonPatchUtil.applyPatch(original2, patch, CustomerMinimal.class, options);
+ assertEquals("2", result2.id());
}
@Test
void testGetJsonObjectToString_withInvalidObject_throwsJsonProcessingException() {
- class EmptyClass {};
+ class EmptyClass {}
JsonPatchUtil.configureMapper(builder -> builder.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, true));
assertThrows(JsonPatchProcessingException.class,
() -> JsonPatchUtil.jsonObjectToString(new EmptyClass()));
diff --git a/src/test/java/edu/kit/datamanager/util/json/JsonPatchUtilTest.java b/src/test/java/edu/kit/datamanager/util/json/JsonPatchUtilTest.java
index 9bd86c9..943d5ee 100644
--- a/src/test/java/edu/kit/datamanager/util/json/JsonPatchUtilTest.java
+++ b/src/test/java/edu/kit/datamanager/util/json/JsonPatchUtilTest.java
@@ -26,27 +26,47 @@
import static org.junit.jupiter.api.Assertions.*;
public class JsonPatchUtilTest {
+ // simple POJO
+ record Person(String name, int age) {}
@Test
- public void applyPatchToJsonNode_happyPath() throws Exception {
+ public void applyPatchToJsonNodeWithJsonPatch() {
JsonMapper mapper = JsonMapper.builder().build();
JsonNode original = mapper.readTree("{\"name\":\"Alice\",\"age\":30}");
List ops = List.of(
- new JsonPatch.Operation(JsonPatch.OperationType.REPLACE, "/name", null, "Bob"),
- new JsonPatch.Operation(JsonPatch.OperationType.ADD, "/city", null, "Karlsruhe")
+ new JsonPatch.Operation(JsonPatch.OperationType.REPLACE, "/name", null, "Bob"),
+ new JsonPatch.Operation(JsonPatch.OperationType.ADD, "/city", null, "Karlsruhe")
);
JsonPatch patch = new JsonPatch(ops);
JsonNode patched = JsonPatchUtil.applyPatch(original, patch);
- assertEquals("Bob", patched.get("name").asText());
+ assertEquals("Bob", patched.get("name").asString());
assertEquals(30, patched.get("age").asInt());
- assertEquals("Karlsruhe", patched.get("city").asText());
+ assertEquals("Karlsruhe", patched.get("city").asString());
}
@Test
- public void applyPatchWithNonArrayPatchShouldThrow() throws Exception {
+ public void applyPatchToJsonNodeWithJsonNode() {
+ JsonMapper mapper = JsonMapper.builder().build();
+ JsonNode original = mapper.readTree("{\"name\":\"Alice\",\"age\":30}");
+
+ List ops = List.of(
+ new JsonPatch.Operation(JsonPatch.OperationType.REPLACE, "/name", null, "Bob"),
+ new JsonPatch.Operation(JsonPatch.OperationType.ADD, "/city", null, "Karlsruhe")
+ );
+ JsonNode patchNode = mapper.valueToTree(new JsonPatch(ops));
+
+ JsonNode patched = JsonPatchUtil.applyPatch(original, patchNode);
+
+ assertEquals("Bob", patched.get("name").asString());
+ assertEquals(30, patched.get("age").asInt());
+ assertEquals("Karlsruhe", patched.get("city").asString());
+ }
+
+ @Test
+ public void applyPatchWithNonArrayPatchShouldThrow() {
JsonMapper mapper = JsonMapper.builder().build();
JsonNode original = mapper.readTree("{\"name\":\"Alice\"}");
@@ -58,40 +78,34 @@ public void applyPatchWithNonArrayPatchShouldThrow() throws Exception {
@Test
public void applyPatchPojo_blockedPathShouldThrow() {
- // simple POJO
- class Person { public String name; public int age; }
-
- Person original = new Person();
- original.name = "Alice";
- original.age = 30;
-
+ String name = "Betty";
+ int age = 31;
+ Person original = new Person(name, age);
String patchJson = "[ { \"op\": \"replace\", \"path\": \"/name\", \"value\": \"Bob\" } ]";
PatchOptions options = PatchOptions.ofBlockedPaths(Set.of("/name"));
assertThrows(JsonPatchProcessingException.class, () -> JsonPatchUtil.applyPatch(original, patchJson, Person.class, options));
+ assertEquals(name, original.name);
+ assertEquals(age, original.age);
}
@Test
public void applyPatchPojo() {
- // simple POJO
- record Person (String name, int age) {}
-
- Person original = new Person("Alice", 30);
+ String name = "Caroline";
+ int age = 32;
+ Person original = new Person(name, age);
String patchJson = "[ { \"op\": \"replace\", \"path\": \"/name\", \"value\": \"Bob\" } ]";
Person updatedPerson = JsonPatchUtil.applyPatch(original, patchJson, Person.class);
assertEquals("Bob", updatedPerson.name);
- assertEquals(30, updatedPerson.age);
+ assertEquals(32, updatedPerson.age);
}
@Test
- public void applyPatchPojo_withUnnownField() {
- // simple POJO
- record Person (String name, int age) {}
-
- Person original = new Person("Alice", 30);
+ public void applyPatchPojo_withUnknownField() {
+ Person original = new Person("Doreen", 33);
String patchJson = "[ { \"op\": \"replace\", \"path\": \"/address\", \"value\": \"new address\" } ]";
@@ -144,7 +158,7 @@ public void applyPatch_jsonNode_string() {
String patch = "[{ \"op\": \"replace\", \"path\": \"/b\", \"value\": 3 }]";
JsonNode jsonNode = JsonPatchUtil.applyPatch(JsonPatchUtil.jsonStringToNode(original), patch);
- assertTrue(jsonNode.get("b").intValue() == 3);
+ assertEquals(3, jsonNode.get("b").intValue());
}
@Test
public void applyPatch_jsonNode_jsonPatch() {
@@ -152,7 +166,7 @@ public void applyPatch_jsonNode_jsonPatch() {
String patch = "[{ \"op\": \"replace\", \"path\": \"/b\", \"value\": 3 }]";
JsonNode jsonNode = JsonPatchUtil.applyPatch(JsonPatchUtil.jsonStringToNode(original), JsonPatchUtil.jsonStringToObject(patch, JsonPatch.class));
- assertTrue(jsonNode.get("b").intValue() == 3);
+ assertEquals(3, jsonNode.get("b").intValue());
}