Skip to content

Commit be4c422

Browse files
authored
fix: correct self reference serialization (#135)
* fix: correct self reference serialization * style: format * chore: output errors to sw log * chore: delegate can be capped type adapter
1 parent d81ed81 commit be4c422

File tree

3 files changed

+105
-1
lines changed

3 files changed

+105
-1
lines changed

services/src/main/kotlin/spp/probe/services/common/ModelSerializer.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@ import com.google.gson.*
2020
import com.google.gson.reflect.TypeToken
2121
import com.google.gson.stream.JsonReader
2222
import com.google.gson.stream.JsonWriter
23-
import spp.probe.services.common.serialize.RuntimeClassNameTypeAdapterFactory
2423
import spp.probe.services.common.serialize.CappedTypeAdapterFactory
24+
import spp.probe.services.common.serialize.RuntimeClassNameTypeAdapterFactory
2525
import java.io.IOException
2626
import java.io.OutputStream
2727
import java.util.*

services/src/main/kotlin/spp/probe/services/common/serialize/RuntimeClassNameTypeAdapterFactory.kt

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,11 @@ package spp.probe.services.common.serialize
1818

1919
import com.google.gson.*
2020
import com.google.gson.internal.Streams
21+
import com.google.gson.internal.bind.JsogRegistry
2122
import com.google.gson.reflect.TypeToken
2223
import com.google.gson.stream.JsonReader
2324
import com.google.gson.stream.JsonWriter
25+
import org.apache.skywalking.apm.agent.core.logging.api.LogManager
2426
import java.io.IOException
2527

2628
/**
@@ -113,6 +115,9 @@ import java.io.IOException
113115
*/
114116
class RuntimeClassNameTypeAdapterFactory<T> private constructor(baseType: Class<*>?, typeFieldName: String?) :
115117
TypeAdapterFactory {
118+
119+
private val log = LogManager.getLogger(RuntimeClassNameTypeAdapterFactory::class.java)
120+
116121
private val baseType: Class<*>
117122
private val typeFieldName: String
118123
private val labelToSubtype: MutableMap<String, Class<*>> = LinkedHashMap()
@@ -224,6 +229,23 @@ class RuntimeClassNameTypeAdapterFactory<T> private constructor(baseType: Class<
224229
Streams.write(jsonObject, out)
225230
} else {
226231
val clone = JsonObject()
232+
233+
//search for self-references
234+
try {
235+
srcType.declaredFields.forEach {
236+
it.isAccessible = true
237+
val fieldValue = it.get(value)
238+
if (fieldValue === value) {
239+
val selfRef = JsonObject()
240+
selfRef.addProperty("@ref", JsogRegistry.get().geId(value))
241+
selfRef.addProperty("@class", value.javaClass.name)
242+
clone.add(it.name, selfRef)
243+
}
244+
}
245+
} catch (e: Throwable) {
246+
log.error("Error while serializing self-references", e)
247+
}
248+
227249
clone.add(typeFieldName, JsonPrimitive(label))
228250
for ((key, value1) in jsonObject.entrySet()) {
229251
clone.add(key, value1)
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
/*
2+
* Source++, the open-source live coding platform.
3+
* Copyright (C) 2022 CodeBrig, Inc.
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package spp.probe.services.common.serialize
18+
19+
import io.vertx.core.json.JsonObject
20+
import org.junit.Assert.assertEquals
21+
import org.junit.Assert.assertTrue
22+
import org.junit.Test
23+
import spp.probe.services.common.ModelSerializer
24+
25+
class JsogTest {
26+
27+
class RootSelfRef {
28+
var self: RootSelfRef? = null
29+
}
30+
31+
@Test
32+
fun testRootSelfRef() {
33+
val rootSelfRef = RootSelfRef()
34+
rootSelfRef.self = rootSelfRef
35+
val json = ModelSerializer.INSTANCE.toExtendedJson(rootSelfRef)
36+
val jsonObject = JsonObject(json)
37+
38+
assertEquals(3, jsonObject.size())
39+
assertTrue(jsonObject.containsKey("@class"))
40+
assertTrue(jsonObject.containsKey("@id"))
41+
assertTrue(jsonObject.containsKey("self"))
42+
43+
val self = jsonObject.getJsonObject("self")
44+
assertEquals(2, self.size())
45+
assertTrue(self.containsKey("@class"))
46+
assertTrue(self.containsKey("@ref"))
47+
assertEquals(self.getString("@ref"), jsonObject.getString("@id"))
48+
}
49+
50+
class InnerSelfRef {
51+
class InnerSelfRef2 {
52+
var selfRef: InnerSelfRef? = null
53+
}
54+
55+
var self2: InnerSelfRef2? = null
56+
}
57+
58+
@Test
59+
fun testInnerSelfRef() {
60+
val innerSelfRef = InnerSelfRef()
61+
innerSelfRef.self2 = InnerSelfRef.InnerSelfRef2().apply { this.selfRef = innerSelfRef }
62+
val json = ModelSerializer.INSTANCE.toExtendedJson(innerSelfRef)
63+
val jsonObject = JsonObject(json)
64+
65+
assertEquals(3, jsonObject.size())
66+
assertTrue(jsonObject.containsKey("@class"))
67+
assertTrue(jsonObject.containsKey("@id"))
68+
assertTrue(jsonObject.containsKey("self2"))
69+
70+
val self2 = jsonObject.getJsonObject("self2")
71+
assertEquals(3, self2.size())
72+
assertTrue(self2.containsKey("@class"))
73+
assertTrue(self2.containsKey("@id"))
74+
assertTrue(self2.containsKey("selfRef"))
75+
76+
val selfRef = self2.getJsonObject("selfRef")
77+
assertEquals(2, selfRef.size())
78+
assertTrue(selfRef.containsKey("@class"))
79+
assertTrue(selfRef.containsKey("@ref"))
80+
assertEquals(selfRef.getString("@ref"), jsonObject.getString("@id"))
81+
}
82+
}

0 commit comments

Comments
 (0)