diff --git a/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/JsonLd.kt b/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/JsonLd.kt index 74b0459e..da4e2def 100644 --- a/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/JsonLd.kt +++ b/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/JsonLd.kt @@ -19,10 +19,17 @@ open class JsonLd { @JsonSerialize(include = JsonSerialize.Inclusion.NON_EMPTY, using = ContextSerializer::class) @JsonInclude(JsonInclude.Include.NON_EMPTY) var context: List = emptyList() + set(value) { + field = value.filterNotNull().filter { it.isNotBlank() } + } @JsonCreator - constructor(context: List) { - this.context = context + constructor(context: List?) { + if (context != null) { + this.context = context.filterNotNull().filter { it.isNotBlank() } + } else { + this.context = emptyList() + } } protected constructor() @@ -47,10 +54,10 @@ class ContextDeserializer : JsonDeserializer() { p1: com.fasterxml.jackson.databind.DeserializationContext? ): String { val readTree: JsonNode = p0?.codec?.readTree(p0) ?: return "" - if (readTree.isObject) { - return "" + if (readTree.isValueNode) { + return readTree.textValue() } - return readTree.asText() + return "" } } diff --git a/src/main/kotlin/dev/usbharu/hideout/application/config/ActivityPubConfig.kt b/src/main/kotlin/dev/usbharu/hideout/application/config/ActivityPubConfig.kt index 70d35829..02678334 100644 --- a/src/main/kotlin/dev/usbharu/hideout/application/config/ActivityPubConfig.kt +++ b/src/main/kotlin/dev/usbharu/hideout/application/config/ActivityPubConfig.kt @@ -1,6 +1,8 @@ package dev.usbharu.hideout.application.config import com.fasterxml.jackson.annotation.JsonInclude +import com.fasterxml.jackson.annotation.JsonSetter +import com.fasterxml.jackson.annotation.Nulls import com.fasterxml.jackson.databind.DeserializationFeature import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper @@ -21,6 +23,7 @@ class ActivityPubConfig { val objectMapper = jacksonObjectMapper() .enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY) .setSerializationInclusion(JsonInclude.Include.NON_EMPTY) + .setDefaultSetterInfo(JsonSetter.Value.forContentNulls(Nulls.AS_EMPTY)) .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) return objectMapper } diff --git a/src/test/kotlin/dev/usbharu/hideout/activitypub/domain/model/JsonLdSerializeTest.kt b/src/test/kotlin/dev/usbharu/hideout/activitypub/domain/model/JsonLdSerializeTest.kt new file mode 100644 index 00000000..7672f429 --- /dev/null +++ b/src/test/kotlin/dev/usbharu/hideout/activitypub/domain/model/JsonLdSerializeTest.kt @@ -0,0 +1,136 @@ +package dev.usbharu.hideout.activitypub.domain.model + +import com.fasterxml.jackson.module.kotlin.readValue +import dev.usbharu.hideout.application.config.ActivityPubConfig +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test + +class JsonLdSerializeTest { + @Test + fun contextが文字列のときデシリアライズできる() { + //language=JSON + val json = """{"@context":"https://example.com"}""" + + val objectMapper = ActivityPubConfig().objectMapper() + + val readValue = objectMapper.readValue(json) + + assertEquals(JsonLd(listOf("https://example.com")), readValue) + } + + @Test + fun contextが文字列の配列のときデシリアライズできる() { + //language=JSON + val json = """{"@context":["https://example.com","https://www.w3.org/ns/activitystreams"]}""" + + val objectMapper = ActivityPubConfig().objectMapper() + + val readValue = objectMapper.readValue(json) + + assertEquals(JsonLd(listOf("https://example.com", "https://www.w3.org/ns/activitystreams")), readValue) + } + + @Test + fun contextがnullのとき空のlistとして解釈してデシリアライズする() { + //language=JSON + val json = """{"@context":null}""" + + val objectMapper = ActivityPubConfig().objectMapper() + + val readValue = objectMapper.readValue(json) + + assertEquals(JsonLd(emptyList()), readValue) + } + + @Test + fun contextがnullを含む文字列の配列のときnullを無視してデシリアライズできる() { + //language=JSON + val json = """{"@context":["https://example.com",null,"https://www.w3.org/ns/activitystreams"]}""" + + val objectMapper = ActivityPubConfig().objectMapper() + + val readValue = objectMapper.readValue(json) + + assertEquals(JsonLd(listOf("https://example.com", "https://www.w3.org/ns/activitystreams")), readValue) + } + + @Test + fun contextがオブジェクトのとき無視してデシリアライズする() { + //language=JSON + val json = """{"@context":{"hoge": "fuga"}}""" + + val objectMapper = ActivityPubConfig().objectMapper() + + val readValue = objectMapper.readValue(json) + + assertEquals(JsonLd(emptyList()), readValue) + } + + @Test + fun contextがオブジェクトを含む文字列の配列のときオブジェクトを無視してデシリアライズする() { + //language=JSON + val json = """{"@context":["https://example.com",{"hoge": "fuga"},"https://www.w3.org/ns/activitystreams"]}""" + + val objectMapper = ActivityPubConfig().objectMapper() + + val readValue = objectMapper.readValue(json) + + assertEquals(JsonLd(listOf("https://example.com", "https://www.w3.org/ns/activitystreams")), readValue) + } + + @Test + fun contextが配列の配列のとき無視してデシリアライズする() { + //language=JSON + val json = """{"@context":[["a","b"],["c","d"]]}""" + + val objectMapper = ActivityPubConfig().objectMapper() + + val readValue = objectMapper.readValue(json) + + assertEquals(JsonLd(emptyList()), readValue) + } + + @Test + fun contextが空のとき無視してシリアライズする() { + val jsonLd = JsonLd(emptyList()) + + val objectMapper = ActivityPubConfig().objectMapper() + + val actual = objectMapper.writeValueAsString(jsonLd) + + assertEquals("{}", actual) + } + + @Test + fun contextがnullのとき無視してシリアライズする() { + val jsonLd = JsonLd(listOf(null)) + + val objectMapper = ActivityPubConfig().objectMapper() + + val actual = objectMapper.writeValueAsString(jsonLd) + + assertEquals("{}", actual) + } + + @Test + fun contextが文字列のとき文字列としてシリアライズされる() { + val jsonLd = JsonLd(listOf("https://example.com")) + + val objectMapper = ActivityPubConfig().objectMapper() + + val actual = objectMapper.writeValueAsString(jsonLd) + + assertEquals("""{"@context":"https://example.com"}""", actual) + } + + @Test + fun contextが文字列の配列のとき配列としてシリアライズされる() { + val jsonLd = JsonLd(listOf("https://example.com", "https://www.w3.org/ns/activitystreams")) + + val objectMapper = ActivityPubConfig().objectMapper() + + val actual = objectMapper.writeValueAsString(jsonLd) + + assertEquals("""{"@context":["https://example.com","https://www.w3.org/ns/activitystreams"]}""", actual) + } +}