diff options
author | Leonid Startsev <sandwwraith@users.noreply.github.com> | 2023-01-24 18:07:33 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-01-24 20:07:33 +0300 |
commit | 60c632c970d7da02151fd7c920f40ebc269c0f29 (patch) | |
tree | 2ce7ab3b0ac5c71b872730e1c30291ad61e8b63e /formats/json-tests | |
parent | 694e2f73f984c31918e8129b35fdf618fb7938ab (diff) | |
download | kotlinx.serialization-60c632c970d7da02151fd7c920f40ebc269c0f29.tar.gz |
Provide support for JsonNamingStrategy to be used in Json for properties' names. (#2111)
Provide a basic implementation of SnakeCase strategy
Fixes #33
Diffstat (limited to 'formats/json-tests')
3 files changed, 294 insertions, 0 deletions
diff --git a/formats/json-tests/commonTest/src/kotlinx/serialization/features/JsonNamingStrategyExclusionTest.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/features/JsonNamingStrategyExclusionTest.kt new file mode 100644 index 00000000..b9974543 --- /dev/null +++ b/formats/json-tests/commonTest/src/kotlinx/serialization/features/JsonNamingStrategyExclusionTest.kt @@ -0,0 +1,60 @@ +package kotlinx.serialization.features + +import kotlinx.serialization.* +import kotlinx.serialization.json.* +import kotlin.test.* + +class JsonNamingStrategyExclusionTest : JsonTestBase() { + @SerialInfo + @Target(AnnotationTarget.CLASS, AnnotationTarget.PROPERTY) + annotation class OriginalSerialName + + private fun List<Annotation>.hasOriginal() = filterIsInstance<OriginalSerialName>().isNotEmpty() + + private val myStrategy = JsonNamingStrategy { descriptor, index, serialName -> + if (descriptor.annotations.hasOriginal() || descriptor.getElementAnnotations(index).hasOriginal()) serialName + else JsonNamingStrategy.SnakeCase.serialNameForJson(descriptor, index, serialName) + } + + @Serializable + @OriginalSerialName + data class Foo(val firstArg: String = "a", val secondArg: String = "b") + + enum class E { + @OriginalSerialName + FIRST_E, + SECOND_E + } + + @Serializable + data class Bar( + val firstBar: String = "a", + @OriginalSerialName val secondBar: String = "b", + val fooBar: Foo = Foo(), + val enumBarOne: E = E.FIRST_E, + val enumBarTwo: E = E.SECOND_E + ) + + private fun doTest(json: Json) { + val j = Json(json) { + namingStrategy = myStrategy + } + val bar = Bar() + assertJsonFormAndRestored( + Bar.serializer(), + bar, + """{"first_bar":"a","secondBar":"b","foo_bar":{"firstArg":"a","secondArg":"b"},"enum_bar_one":"FIRST_E","enum_bar_two":"SECOND_E"}""", + j + ) + } + + @Test + fun testJsonNamingStrategyWithAlternativeNames() = doTest(Json(default) { + useAlternativeNames = true + }) + + @Test + fun testJsonNamingStrategyWithoutAlternativeNames() = doTest(Json(default) { + useAlternativeNames = false + }) +} diff --git a/formats/json-tests/commonTest/src/kotlinx/serialization/features/JsonNamingStrategyTest.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/features/JsonNamingStrategyTest.kt new file mode 100644 index 00000000..330d5d2b --- /dev/null +++ b/formats/json-tests/commonTest/src/kotlinx/serialization/features/JsonNamingStrategyTest.kt @@ -0,0 +1,195 @@ +package kotlinx.serialization.features + +import kotlinx.serialization.* +import kotlinx.serialization.builtins.* +import kotlinx.serialization.json.* +import kotlinx.serialization.test.* +import kotlin.test.* + + +class JsonNamingStrategyTest : JsonTestBase() { + @Serializable + data class Foo( + val simple: String = "a", + val oneWord: String = "b", + val already_in_snake: String = "c", + val aLotOfWords: String = "d", + val FirstCapitalized: String = "e", + val hasAcronymURL: Bar = Bar.BAZ, + val hasDigit123AndPostfix: Bar = Bar.QUX, + val coercionTest: Bar = Bar.QUX + ) + + enum class Bar { BAZ, QUX } + + val jsonWithNaming = Json(default) { + namingStrategy = JsonNamingStrategy.SnakeCase + } + + @Test + fun testJsonNamingStrategyWithAlternativeNames() = doTest(Json(jsonWithNaming) { + useAlternativeNames = true + }) + + @Test + fun testJsonNamingStrategyWithoutAlternativeNames() = doTest(Json(jsonWithNaming) { + useAlternativeNames = false + }) + + private fun doTest(json: Json) { + val foo = Foo() + assertJsonFormAndRestored( + Foo.serializer(), + foo, + """{"simple":"a","one_word":"b","already_in_snake":"c","a_lot_of_words":"d","first_capitalized":"e","has_acronym_url":"BAZ","has_digit123_and_postfix":"QUX","coercion_test":"QUX"}""", + json + ) + } + + @Test + fun testNamingStrategyWorksWithCoercing() { + val j = Json(jsonWithNaming) { + coerceInputValues = true + useAlternativeNames = false + } + assertEquals( + Foo(), + j.decodeFromString("""{"simple":"a","one_word":"b","already_in_snake":"c","a_lot_of_words":"d","first_capitalized":"e","has_acronym_url":"baz","has_digit123_and_postfix":"qux","coercion_test":"invalid"}""") + ) + } + + @Test + fun testSnakeCaseStrategy() { + fun apply(name: String) = + JsonNamingStrategy.SnakeCase.serialNameForJson(String.serializer().descriptor, 0, name) + + val cases = mapOf<String, String>( + "" to "", + "_" to "_", + "___" to "___", + "a" to "a", + "A" to "a", + "_1" to "_1", + "_a" to "_a", + "_A" to "_a", + "property" to "property", + "twoWords" to "two_words", + "threeDistinctWords" to "three_distinct_words", + "ThreeDistinctWords" to "three_distinct_words", + "Oneword" to "oneword", + "camel_Case_Underscores" to "camel_case_underscores", + "_many____underscores__" to "_many____underscores__", + "URLmapping" to "ur_lmapping", + "URLMapping" to "url_mapping", + "IOStream" to "io_stream", + "IOstream" to "i_ostream", + "myIo2Stream" to "my_io2_stream", + "myIO2Stream" to "my_io2_stream", + "myIO2stream" to "my_io2stream", + "myIO2streamMax" to "my_io2stream_max", + "InURLBetween" to "in_url_between", + "myHTTP2APIKey" to "my_http2_api_key", + "myHTTP2fastApiKey" to "my_http2fast_api_key", + "myHTTP23APIKey" to "my_http23_api_key", + "myHttp23ApiKey" to "my_http23_api_key", + "theWWW" to "the_www", + "theWWW_URL_xxx" to "the_www_url_xxx", + "hasDigit123AndPostfix" to "has_digit123_and_postfix" + ) + + cases.forEach { (input, expected) -> + assertEquals(expected, apply(input)) + } + } + + @Serializable + data class DontUseOriginal(val testCase: String) + + @Test + fun testNamingStrategyOverridesOriginal() { + val json = Json(jsonWithNaming) { + ignoreUnknownKeys = true + } + parametrizedTest { mode -> + assertEquals(DontUseOriginal("a"), json.decodeFromString("""{"test_case":"a","testCase":"b"}""", mode)) + } + + val jsonThrows = Json(jsonWithNaming) { + ignoreUnknownKeys = false + } + parametrizedTest { mode -> + assertFailsWithMessage<SerializationException>("Encountered an unknown key 'testCase'") { + jsonThrows.decodeFromString<DontUseOriginal>("""{"test_case":"a","testCase":"b"}""", mode) + } + } + } + + @Serializable + data class CollisionCheckPrimary(val testCase: String, val test_case: String) + + @Serializable + data class CollisionCheckAlternate(val testCase: String, @JsonNames("test_case") val testCase2: String) + + @Test + fun testNamingStrategyPrioritizesOverAlternative() = noLegacyJs { // @JsonNames not supported on legacy + val json = Json(jsonWithNaming) { + ignoreUnknownKeys = true + } + parametrizedTest { mode -> + assertFailsWithMessage<SerializationException>("The suggested name 'test_case' for property test_case is already one of the names for property testCase") { + json.decodeFromString<CollisionCheckPrimary>("""{"test_case":"a"}""", mode) + } + } + parametrizedTest { mode -> + assertFailsWithMessage<SerializationException>("The suggested name 'test_case' for property testCase2 is already one of the names for property testCase") { + json.decodeFromString<CollisionCheckAlternate>("""{"test_case":"a"}""", mode) + } + } + } + + + @Serializable + data class OriginalAsFallback(@JsonNames("testCase") val testCase: String) + + @Test + fun testCanUseOriginalNameAsAlternative() = noLegacyJs { // @JsonNames not supported on legacy + val json = Json(jsonWithNaming) { + ignoreUnknownKeys = true + } + parametrizedTest { mode -> + assertEquals(OriginalAsFallback("b"), json.decodeFromString("""{"testCase":"b"}""", mode)) + } + } + + @Serializable + sealed interface SealedBase { + @Serializable + @JsonClassDiscriminator("typeSub") + sealed class SealedMid : SealedBase { + @Serializable + @SerialName("SealedSub1") + object SealedSub1 : SealedMid() + } + + @Serializable + @SerialName("SealedSub2") + data class SealedSub2(val testCase: Int = 0) : SealedBase + } + + @Serializable + data class Holder(val testBase: SealedBase, val testMid: SealedBase.SealedMid) + + @Test + fun testNamingStrategyDoesNotAffectPolymorphism() = noLegacyJs { // @JsonClassDiscriminator + val json = Json(jsonWithNaming) { + classDiscriminator = "typeBase" + } + val holder = Holder(SealedBase.SealedSub2(), SealedBase.SealedMid.SealedSub1) + assertJsonFormAndRestored( + Holder.serializer(), + holder, + """{"test_base":{"typeBase":"SealedSub2","test_case":0},"test_mid":{"typeSub":"SealedSub1"}}""", + json + ) + } +} diff --git a/formats/json-tests/jsTest/src/kotlinx/serialization/json/JsonNamingStrategyDynamicTest.kt b/formats/json-tests/jsTest/src/kotlinx/serialization/json/JsonNamingStrategyDynamicTest.kt new file mode 100644 index 00000000..a1f7b0e6 --- /dev/null +++ b/formats/json-tests/jsTest/src/kotlinx/serialization/json/JsonNamingStrategyDynamicTest.kt @@ -0,0 +1,39 @@ +package kotlinx.serialization.json + +import kotlinx.serialization.* +import kotlinx.serialization.features.* +import kotlin.test.* + +class JsonNamingStrategyDynamicTest: JsonTestBase() { + private val jsForm = js("""{"simple":"a","one_word":"b","already_in_snake":"c","a_lot_of_words":"d","first_capitalized":"e","has_acronym_url":"BAZ","has_digit123_and_postfix":"QUX","coercion_test":"QUX"}""") + private val jsFormNeedsCoercing = js("""{"simple":"a","one_word":"b","already_in_snake":"c","a_lot_of_words":"d","first_capitalized":"e","has_acronym_url":"BAZ","has_digit123_and_postfix":"QUX","coercion_test":"invalid"}""") + + private fun doTest(json: Json) { + val j = Json(json) { + namingStrategy = JsonNamingStrategy.SnakeCase + } + val foo = JsonNamingStrategyTest.Foo() + assertDynamicForm(foo) + assertEquals(foo, j.decodeFromDynamic(jsForm)) + } + + @Test + fun testNamingStrategyWorksWithCoercing() { + val j = Json(default) { + coerceInputValues = true + useAlternativeNames = false + namingStrategy = JsonNamingStrategy.SnakeCase + } + assertEquals(JsonNamingStrategyTest.Foo(), j.decodeFromDynamic(jsFormNeedsCoercing)) + } + + @Test + fun testJsonNamingStrategyWithAlternativeNames() = doTest(Json(default) { + useAlternativeNames = true + }) + + @Test + fun testJsonNamingStrategyWithoutAlternativeNames() = doTest(Json(default) { + useAlternativeNames = false + }) +} |