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