• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * Copyright 2017-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
3  */
4 
5 package kotlinx.serialization.features
6 
7 import kotlinx.coroutines.Dispatchers
8 import kotlinx.coroutines.flow.*
9 import kotlinx.coroutines.runBlocking
10 import kotlinx.serialization.*
11 import kotlinx.serialization.builtins.serializer
12 import kotlinx.serialization.features.sealed.SealedChild
13 import kotlinx.serialization.features.sealed.SealedParent
14 import kotlinx.serialization.json.*
15 import kotlinx.serialization.test.assertFailsWithMessage
16 import kotlinx.serialization.test.assertFailsWithSerial
17 import org.junit.Test
18 import java.io.*
19 import kotlin.test.*
20 
21 class JsonLazySequenceTest {
22     val json = Json
23 
24     private suspend inline fun <reified T> Flow<T>.writeToStream(os: OutputStream) {
25         collect {
26             json.encodeToStream(it, os)
27         }
28     }
29 
30     private suspend inline fun <reified T> Json.readFromStream(iss: InputStream): Flow<T> = flow {
31         val serial = serializer<T>()
32         val iter = iterateOverStream(iss, serial)
33         while (iter.hasNext()) {
34             emit(iter.next())
35         }
36     }.flowOn(Dispatchers.IO)
37 
38     private val inputStringWsSeparated = """{"data":"a"}{"data":"b"}{"data":"c"}"""
39     private val inputStringWrapped = """[{"data":"a"},{"data":"b"},{"data":"c"}]"""
40     private val inputList = listOf(StringData("a"), StringData("b"), StringData("c"))
41 
42     @Test
43     fun testEncodeSeveralItems() {
44         val items = inputList
45         val os = ByteArrayOutputStream()
46         runBlocking {
47             val f = flow<StringData> { items.forEach { emit(it) } }
48             f.writeToStream(os)
49         }
50 
51         assertEquals(inputStringWsSeparated, os.toString(Charsets.UTF_8.name()))
52     }
53 
54     @Test
55     fun testDecodeSeveralItems() {
56         val ins = ByteArrayInputStream(inputStringWsSeparated.encodeToByteArray())
57         assertFailsWithMessage<SerializationException>("EOF") {
58             json.decodeFromStream<StringData>(ins)
59         }
60     }
61 
62     private inline fun <reified T> Iterator<T>.assertNext(expected: T) {
63         assertTrue(hasNext())
64         assertEquals(expected, next())
65     }
66 
67     private fun <T> Json.iterateOverStream(stream: InputStream, deserializer: DeserializationStrategy<T>): Iterator<T> =
68         decodeToSequence(stream, deserializer).iterator()
69 
70     private fun withInputs(vararg inputs: String = arrayOf(inputStringWsSeparated, inputStringWrapped), block: (InputStream) -> Unit) {
71         for (input in inputs) {
72             val res = runCatching { block(input.asInputStream()) }
73             if (res.isFailure) throw AssertionError("Failed test with input $input", res.exceptionOrNull())
74         }
75     }
76 
77     private fun String.asInputStream() = ByteArrayInputStream(this.encodeToByteArray())
78 
79     @Test
80     fun testIterateSeveralItems() = withInputs { ins ->
81         val iter = json.iterateOverStream(ins, StringData.serializer())
82         iter.assertNext(StringData("a"))
83         iter.assertNext(StringData("b"))
84         iter.assertNext(StringData("c"))
85         assertFalse(iter.hasNext())
86         assertFalse(iter.hasNext()) // Subsequent calls to .hasNext() should not throw EOF or anything
87         assertFailsWithMessage<SerializationException>("EOF") {
88             iter.next()
89         }
90     }
91 
92     @Test
93     fun testDecodeToSequence() = withInputs { ins ->
94         val sequence = json.decodeToSequence(ins, StringData.serializer())
95         assertEquals(inputList, sequence.toList(), "For input $inputStringWsSeparated")
96         assertFailsWith<IllegalStateException> { sequence.toList() } // assert constrained once
97     }
98 
99     @Test
100     fun testDecodeAsFlow() = withInputs { ins ->
101         val list = runBlocking {
102             buildList { json.readFromStream<StringData>(ins).toCollection(this) }
103         }
104         assertEquals(inputList, list)
105     }
106 
107     @Test
108     fun testItemsSeparatedByWs() {
109         val input = "{\"data\":\"a\"}   {\"data\":\"b\"}\n\t{\"data\":\"c\"}"
110         val ins = ByteArrayInputStream(input.encodeToByteArray())
111         assertEquals(inputList, json.decodeToSequence(ins, StringData.serializer()).toList())
112     }
113 
114     @Test
115     fun testJsonElement() {
116         val list = listOf<JsonElement>(
117             buildJsonObject { put("foo", "bar") },
118             buildJsonObject { put("foo", "baz") },
119             JsonPrimitive(10),
120             JsonPrimitive("abacaba"),
121             buildJsonObject { put("foo", "qux") }
122         )
123         val inputWs = """${list[0]} ${list[1]} ${list[2]}    ${list[3]}    ${list[4]}"""
124         val decodedWs = json.decodeToSequence<JsonElement>(inputWs.asInputStream()).toList()
125         assertEquals(list, decodedWs, "Failed whitespace-separated test")
126         val inputArray = """[${list[0]}, ${list[1]},${list[2]}  ,  ${list[3]}    ,${list[4]}]"""
127         val decodedArrayWrap = json.decodeToSequence<JsonElement>(inputArray.asInputStream()).toList()
128         assertEquals(list, decodedArrayWrap, "Failed array-wrapped test")
129     }
130 
131 
132     @Test
133     fun testSealedClasses() {
134         val input = """{"type":"first child","i":1,"j":10} {"type":"first child","i":1,"j":11}"""
135         val iter = json.iterateOverStream(input.asInputStream(), SealedParent.serializer())
136         iter.assertNext(SealedChild(10))
137         iter.assertNext(SealedChild(11))
138     }
139 
140     @Test
141     fun testMalformedArray() {
142         val input1 = """[1, 2, 3"""
143         val input2 = """[1, 2, 3]qwert"""
144         val input3 = """[1,2 3]"""
145         withInputs(input1, input2, input3) {
146             assertFailsWithSerial("JsonDecodingException") {
147                 json.decodeToSequence(it, Int.serializer()).toList()
148             }
149         }
150     }
151 
152     @Test
153     fun testMultilineArrays() {
154         val input = "[1,2,3]\n[4,5,6]\n[7,8,9]"
155         assertFailsWithSerial("JsonDecodingException") {
156             json.decodeToSequence<List<Int>>(input.asInputStream(), DecodeSequenceMode.AUTO_DETECT).toList()
157         }
158         assertFailsWithSerial("JsonDecodingException") {
159             json.decodeToSequence<Int>(input.asInputStream(), DecodeSequenceMode.AUTO_DETECT).toList()
160         }
161         assertFailsWithSerial("JsonDecodingException") { // we do not merge lists
162             json.decodeToSequence<Int>(input.asInputStream(), DecodeSequenceMode.ARRAY_WRAPPED).toList()
163         }
164         val parsed = json.decodeToSequence<List<Int>>(input.asInputStream(), DecodeSequenceMode.WHITESPACE_SEPARATED).toList()
165         val expected = listOf(listOf(1,2,3), listOf(4,5,6), listOf(7,8,9))
166         assertEquals(expected, parsed)
167     }
168 
169     @Test
170     fun testStrictArrayCheck() {
171         assertFailsWithSerial("JsonDecodingException") {
172             json.decodeToSequence<StringData>(inputStringWsSeparated.asInputStream(), DecodeSequenceMode.ARRAY_WRAPPED)
173         }
174     }
175 
176     @Test
177     fun testPaddedWs() {
178         val paddedWs = "  $inputStringWsSeparated  "
179         assertEquals(inputList, json.decodeToSequence(paddedWs.asInputStream(), StringData.serializer()).toList())
180         assertEquals(inputList, json.decodeToSequence(paddedWs.asInputStream(), StringData.serializer(), DecodeSequenceMode.WHITESPACE_SEPARATED).toList())
181     }
182 
183     @Test
184     fun testPaddedArray() {
185         val paddedWs = "  $inputStringWrapped  "
186         assertEquals(inputList, json.decodeToSequence(paddedWs.asInputStream(), StringData.serializer()).toList())
187         assertEquals(inputList, json.decodeToSequence(paddedWs.asInputStream(), StringData.serializer(), DecodeSequenceMode.ARRAY_WRAPPED).toList())
188     }
189 
190     @Test
191     fun testToIteratorAndBack() = withInputs { ins ->
192         val iterator = Json.decodeToSequence(ins, StringData.serializer()).iterator()
193         val values = iterator.asSequence().take(3).toList()
194         assertEquals(inputList, values)
195         assertFalse(iterator.hasNext())
196     }
197 }
198