├── src └── iris │ ├── json │ ├── JsonString.kt │ ├── JsonValue.kt │ ├── JsonNull.kt │ ├── serialization │ │ ├── DeserializerClass.kt │ │ ├── DeserializerMap.kt │ │ ├── DeserializerCollection.kt │ │ ├── Deserializer.kt │ │ ├── DeserializerPrimitive.kt │ │ ├── DeserializerJsonItem.kt │ │ ├── DeserializerCollectionImpl.kt │ │ ├── annotations.kt │ │ ├── DeserializerMapImpl.kt │ │ ├── DeserializerFactory.kt │ │ ├── DeserializerPrimitiveImpl.kt │ │ ├── DeserializerClassBuilder.kt │ │ └── DeserializerClassImpl.kt │ ├── proxy │ │ ├── JsonProxyValue.kt │ │ ├── JsonProxyString.kt │ │ ├── JsonProxyUtil.kt │ │ ├── JsonProxyItem.kt │ │ ├── JsonProxyArray.kt │ │ └── JsonProxyObject.kt │ ├── JsonArray.kt │ ├── JsonObject.kt │ ├── flow │ │ ├── FlowItem.kt │ │ ├── Tokener.kt │ │ ├── TokenerContentBuildReader.kt │ │ ├── TokenerBufferedReader.kt │ │ ├── TokenerSimpleReader.kt │ │ ├── TokenerAbstractWithPointer.kt │ │ ├── TokenerAbstractWithSequence.kt │ │ ├── JsonFlowParser.kt │ │ ├── FlowString.kt │ │ ├── TokenerString.kt │ │ ├── FlowValue.kt │ │ ├── FlowObject.kt │ │ └── FlowArray.kt │ ├── Configuration.kt │ ├── plain │ │ ├── IrisJsonNull.kt │ │ ├── IrisJsonArray.kt │ │ ├── IrisJsonObject.kt │ │ ├── IrisJsonValue.kt │ │ ├── IrisJsonString.kt │ │ ├── IrisJsonItem.kt │ │ └── JsonPlainParser.kt │ ├── Util.kt │ ├── JsonItem.kt │ └── JsonEncoder.kt │ └── sequence │ ├── IrisSequence.kt │ ├── CharArraySource.kt │ ├── CharArraySourceAbstract.kt │ ├── IrisSequenceSingleChar.kt │ ├── IrisSubSequence.kt │ ├── IrisSequenceCharList.kt │ ├── IrisSequenceCharArray.kt │ ├── SimpleStringReader.kt │ ├── CharArrayBuilder.kt │ └── NumberConversions.kt ├── test └── iris │ └── json │ ├── PureJavaUser.java │ ├── test │ ├── usage_test_flow.kt │ ├── usage_test_plain.kt │ ├── performance_object_tree.kt │ └── performance_array.kt │ ├── flow │ └── serialization.kt │ └── plain │ └── serialization.kt ├── CHANGELOG.md ├── test.json ├── README.md └── test_array.json /src/iris/json/JsonString.kt: -------------------------------------------------------------------------------- 1 | package iris.json 2 | 3 | /** 4 | * @created 26.09.2020 5 | * @author [Ivan Ivanov](https://vk.com/irisism) 6 | */ 7 | interface JsonString : JsonValue -------------------------------------------------------------------------------- /src/iris/json/JsonValue.kt: -------------------------------------------------------------------------------- 1 | package iris.json 2 | 3 | /** 4 | * @created 26.09.2020 5 | * @author [Ivan Ivanov](https://vk.com/irisism) 6 | */ 7 | interface JsonValue : JsonItem -------------------------------------------------------------------------------- /src/iris/json/JsonNull.kt: -------------------------------------------------------------------------------- 1 | package iris.json 2 | 3 | /** 4 | * @created 26.09.2020 5 | * @author [Ivan Ivanov](https://vk.com/irisism) 6 | */ 7 | interface JsonNull : JsonItem { 8 | 9 | } -------------------------------------------------------------------------------- /src/iris/json/serialization/DeserializerClass.kt: -------------------------------------------------------------------------------- 1 | package iris.json.serialization 2 | 3 | import iris.json.JsonEntry 4 | 5 | interface DeserializerClass : Deserializer { 6 | fun getObject(entries: Collection): T 7 | } -------------------------------------------------------------------------------- /src/iris/sequence/IrisSequence.kt: -------------------------------------------------------------------------------- 1 | package iris.sequence 2 | 3 | /** 4 | * @created 25.09.2020 5 | * @author [Ivan Ivanov](https://vk.com/irisism) 6 | */ 7 | interface IrisSequence: CharSequence { 8 | fun joinTo(buffer: A): A 9 | } -------------------------------------------------------------------------------- /src/iris/json/proxy/JsonProxyValue.kt: -------------------------------------------------------------------------------- 1 | package iris.json.proxy 2 | 3 | import iris.json.JsonValue 4 | 5 | /** 6 | * @created 26.09.2020 7 | * @author [Ivan Ivanov](https://vk.com/irisism) 8 | */ 9 | class JsonProxyValue(value: Any?) : JsonProxyItem(value), JsonValue { 10 | override fun isPrimitive() = true 11 | } -------------------------------------------------------------------------------- /src/iris/json/JsonArray.kt: -------------------------------------------------------------------------------- 1 | package iris.json 2 | 3 | /** 4 | * @created 26.09.2020 5 | * @author [Ivan Ivanov](https://vk.com/irisism) 6 | */ 7 | interface JsonArray: JsonItem, Iterable { 8 | fun getList(): List 9 | val size: Int 10 | fun isEmpty(): Boolean 11 | fun isNotEmpty(): Boolean 12 | } -------------------------------------------------------------------------------- /src/iris/json/JsonObject.kt: -------------------------------------------------------------------------------- 1 | package iris.json 2 | 3 | /** 4 | * @created 26.09.2020 5 | * @author [Ivan Ivanov](https://vk.com/irisism) 6 | */ 7 | 8 | typealias JsonEntry = Pair 9 | 10 | interface JsonObject: JsonItem, Iterable { 11 | fun getEntries(): Collection 12 | } -------------------------------------------------------------------------------- /src/iris/json/serialization/DeserializerMap.kt: -------------------------------------------------------------------------------- 1 | package iris.json.serialization 2 | 3 | import iris.json.JsonEntry 4 | 5 | /** 6 | * @created 09.10.2020 7 | * @author [Ivan Ivanov](https://vk.com/irisism) 8 | */ 9 | interface DeserializerMap : Deserializer { 10 | fun getMap(entries: Collection): Map 11 | } -------------------------------------------------------------------------------- /src/iris/json/serialization/DeserializerCollection.kt: -------------------------------------------------------------------------------- 1 | package iris.json.serialization 2 | 3 | import iris.json.JsonItem 4 | 5 | /** 6 | * @created 09.10.2020 7 | * @author [Ivan Ivanov](https://vk.com/irisism) 8 | */ 9 | interface DeserializerCollection : Deserializer { 10 | fun getObject(items: Collection): Collection<*> 11 | } -------------------------------------------------------------------------------- /src/iris/json/serialization/Deserializer.kt: -------------------------------------------------------------------------------- 1 | package iris.json.serialization 2 | 3 | import iris.json.JsonItem 4 | import kotlin.reflect.KClass 5 | 6 | /** 7 | * @created 08.10.2020 8 | * @author [Ivan Ivanov](https://vk.com/irisism) 9 | */ 10 | interface Deserializer { 11 | fun deserialize(item: JsonItem): T 12 | fun forSubclass(d: KClass<*>): Deserializer 13 | } 14 | 15 | -------------------------------------------------------------------------------- /src/iris/json/flow/FlowItem.kt: -------------------------------------------------------------------------------- 1 | package iris.json.flow 2 | 3 | import iris.json.plain.IrisJsonItem 4 | 5 | /** 6 | * @created 20.09.2020 7 | * @author [Ivan Ivanov](https://vk.com/irisism) 8 | */ 9 | abstract class FlowItem(protected val tokener: Tokener) : IrisJsonItem() { 10 | abstract fun parse() 11 | fun contentSource(start: Int = 0 , end: Int = 0) = tokener.getSourceSequence(start, end) 12 | } -------------------------------------------------------------------------------- /src/iris/json/serialization/DeserializerPrimitive.kt: -------------------------------------------------------------------------------- 1 | package iris.json.serialization 2 | 3 | import iris.json.JsonItem 4 | 5 | /** 6 | * @created 09.10.2020 7 | * @author [Ivan Ivanov](https://vk.com/irisism) 8 | */ 9 | interface DeserializerPrimitive : Deserializer { 10 | fun getValue(item: JsonItem): Any? 11 | 12 | enum class Type { 13 | ANY, INTEGER, LONG, DOUBLE, FLOAT, BOOLEAN, STRING, DATE 14 | } 15 | } -------------------------------------------------------------------------------- /src/iris/json/proxy/JsonProxyString.kt: -------------------------------------------------------------------------------- 1 | package iris.json.proxy 2 | 3 | import iris.json.JsonString 4 | 5 | /** 6 | * @created 26.09.2020 7 | * @author [Ivan Ivanov](https://vk.com/irisism) 8 | */ 9 | class JsonProxyString(private val str: String?) : JsonProxyItem(str), JsonString { 10 | override fun asStringOrNull(): String? { 11 | return str 12 | } 13 | 14 | override fun asString(): String { 15 | return str!! 16 | } 17 | 18 | override fun isPrimitive() = true 19 | } -------------------------------------------------------------------------------- /src/iris/json/serialization/DeserializerJsonItem.kt: -------------------------------------------------------------------------------- 1 | package iris.json.serialization 2 | 3 | import iris.json.JsonItem 4 | import kotlin.reflect.KClass 5 | 6 | /** 7 | * @created 10.10.2020 8 | * @author [Ivan Ivanov](https://vk.com/irisism) 9 | */ 10 | class DeserializerJsonItem : Deserializer { 11 | override fun deserialize(item: JsonItem): T { 12 | return item as T 13 | } 14 | 15 | override fun forSubclass(d: KClass<*>): Deserializer { 16 | return this 17 | } 18 | } -------------------------------------------------------------------------------- /src/iris/sequence/CharArraySource.kt: -------------------------------------------------------------------------------- 1 | package iris.sequence 2 | 3 | /** 4 | * @created 25.09.2020 5 | * @author [Ivan Ivanov](https://vk.com/irisism) 6 | */ 7 | interface CharArraySource { 8 | 9 | val length: Int 10 | operator fun get(index: Int): Char 11 | 12 | fun toCharArray(): CharArray 13 | fun toCharArray(start: Int = 0, len: Int = length): CharArray 14 | 15 | fun toCharArray(dest: CharArray): CharArray 16 | fun toCharArray(dest: CharArray, destOffset: Int = 0, start: Int = 0, len: Int = dest.size): CharArray 17 | } 18 | 19 | -------------------------------------------------------------------------------- /src/iris/json/proxy/JsonProxyUtil.kt: -------------------------------------------------------------------------------- 1 | package iris.json.proxy 2 | 3 | import iris.json.plain.IrisJsonItem 4 | import iris.json.plain.IrisJsonNull 5 | 6 | /** 7 | * @created 26.09.2020 8 | * @author [Ivan Ivanov](https://vk.com/irisism) 9 | */ 10 | object JsonProxyUtil { 11 | fun wrap(value: Any?): IrisJsonItem { 12 | return when (value) { 13 | is Map<*, *> -> JsonProxyObject(value as Map) 14 | is List<*> -> JsonProxyArray(value as List) 15 | null -> IrisJsonNull.Null 16 | else -> JsonProxyValue(value) 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /src/iris/sequence/CharArraySourceAbstract.kt: -------------------------------------------------------------------------------- 1 | package iris.sequence 2 | 3 | import kotlin.math.min 4 | 5 | abstract class CharArraySourceAbstract : CharArraySource { 6 | 7 | override fun toCharArray(): CharArray { 8 | val len = length 9 | return toCharArray(CharArray(len), 0, 0, len) 10 | } 11 | 12 | override fun toCharArray(start: Int, len: Int): CharArray { 13 | val len = min(length, len) 14 | return toCharArray(CharArray(len), 0, start, len) 15 | } 16 | 17 | override fun toCharArray(dest: CharArray): CharArray { 18 | return toCharArray(dest, 0, 0, length) 19 | } 20 | } -------------------------------------------------------------------------------- /src/iris/json/Configuration.kt: -------------------------------------------------------------------------------- 1 | package iris.json 2 | 3 | /** 4 | * @created 30.12.2020 5 | * @author [Ivan Ivanov](https://vk.com/irisism) 6 | */ 7 | class Configuration { 8 | 9 | companion object { 10 | val globalConfiguration = Configuration() 11 | } 12 | 13 | interface MapObjectFactory { 14 | fun getMap(elementsAmount: Int): MutableMap 15 | } 16 | 17 | var trailingCommaAllowed: Boolean = true 18 | 19 | var mapObjectFactory: MapObjectFactory = object : MapObjectFactory { 20 | override fun getMap(elementsAmount: Int): MutableMap { 21 | return HashMap(elementsAmount) 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /src/iris/json/flow/Tokener.kt: -------------------------------------------------------------------------------- 1 | package iris.json.flow 2 | 3 | import iris.json.Util 4 | 5 | /** 6 | * @created 20.09.2020 7 | * @author [Ivan Ivanov](https://vk.com/irisism) 8 | */ 9 | interface Tokener { 10 | 11 | fun nextChar(): Char 12 | 13 | fun exception(s: String): IllegalArgumentException 14 | 15 | fun getSourceSequence(start: Int = 0, end: Int = 0): CharSequence 16 | 17 | fun readString(quote: Char): CharSequence 18 | 19 | fun readFieldName(quote: Char?): CharSequence 20 | 21 | fun readPrimitive(): PrimitiveData 22 | 23 | class PrimitiveData(val sequence: CharSequence, val type: Util.ValueType) 24 | 25 | fun back() 26 | 27 | } -------------------------------------------------------------------------------- /src/iris/json/proxy/JsonProxyItem.kt: -------------------------------------------------------------------------------- 1 | package iris.json.proxy 2 | 3 | import iris.json.plain.IrisJsonItem 4 | import iris.json.plain.IrisJsonNull 5 | 6 | /** 7 | * @created 26.09.2020 8 | * @author [Ivan Ivanov](https://vk.com/irisism) 9 | */ 10 | abstract class JsonProxyItem(private val obj: Any?) : IrisJsonItem() { 11 | override fun get(ind: Int): IrisJsonItem { 12 | return IrisJsonNull.Null 13 | } 14 | 15 | override fun get(key: String): IrisJsonItem { 16 | return IrisJsonNull.Null 17 | } 18 | 19 | override fun obj(): Any? { 20 | return obj 21 | } 22 | 23 | override fun appendToJsonString(buffer: A): A { 24 | buffer.append(obj.toString()) 25 | return buffer 26 | } 27 | 28 | 29 | } -------------------------------------------------------------------------------- /src/iris/json/plain/IrisJsonNull.kt: -------------------------------------------------------------------------------- 1 | package iris.json.plain 2 | 3 | import iris.json.JsonNull 4 | 5 | /** 6 | * @created 14.04.2020 7 | * @author [Ivan Ivanov](https://vk.com/irisism) 8 | */ 9 | open class IrisJsonNull : IrisJsonItem(), JsonNull { 10 | 11 | companion object { 12 | val Null = IrisJsonNull() 13 | } 14 | 15 | override fun get(ind: Int): IrisJsonItem { 16 | return this 17 | } 18 | 19 | override fun get(key: String): IrisJsonItem { 20 | return this 21 | } 22 | 23 | override fun obj(): Any? { 24 | return null 25 | } 26 | 27 | override fun appendToJsonString(buffer: A): A { 28 | buffer.append("null") 29 | return buffer 30 | } 31 | 32 | override fun toString(): String { 33 | return "null" 34 | } 35 | 36 | override fun isNull() = true 37 | } -------------------------------------------------------------------------------- /src/iris/json/Util.kt: -------------------------------------------------------------------------------- 1 | package iris.json 2 | 3 | /** 4 | * @created 02.01.2021 5 | * @author [Ivan Ivanov](https://vk.com/irisism) 6 | */ 7 | object Util { 8 | 9 | enum class Type { 10 | Object 11 | , Array 12 | , String 13 | , Value 14 | , Null 15 | } 16 | 17 | enum class ValueType { 18 | Integer, 19 | Float, 20 | Constant // among them: true, false, null 21 | } 22 | 23 | 24 | private val digitRange = '0'..'9' 25 | private val alphaRange = 'a'..'z' 26 | private val alphaUpperRange = 'A'..'Z' 27 | 28 | fun isDigit(char: Char): Boolean { 29 | return digitRange.contains(char) 30 | } 31 | 32 | fun isAlpha(char: Char): Boolean { 33 | return alphaRange.contains(char) || alphaUpperRange.contains(char) 34 | } 35 | 36 | fun isDigitOrAlpha(char: Char): Boolean { 37 | return isDigit(char) || isAlpha(char) 38 | } 39 | } -------------------------------------------------------------------------------- /src/iris/json/serialization/DeserializerCollectionImpl.kt: -------------------------------------------------------------------------------- 1 | package iris.json.serialization 2 | 3 | import iris.json.JsonArray 4 | import iris.json.JsonItem 5 | import kotlin.reflect.KClass 6 | 7 | /** 8 | * @created 08.10.2020 9 | * @author [Ivan Ivanov](https://vk.com/irisism) 10 | */ 11 | 12 | class DeserializerCollectionImpl : DeserializerCollection { 13 | 14 | lateinit var typeDeserializer: Deserializer 15 | 16 | override fun getObject(items: Collection): Collection<*> { 17 | val res = ArrayList(items.size) 18 | for (item in items) 19 | res.add(typeDeserializer.deserialize(item)) 20 | return res 21 | } 22 | 23 | override fun deserialize(item: JsonItem): T { 24 | return getObject((item as JsonArray).getList()) as T 25 | } 26 | 27 | override fun forSubclass(d: KClass<*>): Deserializer { 28 | return this 29 | } 30 | } -------------------------------------------------------------------------------- /src/iris/json/serialization/annotations.kt: -------------------------------------------------------------------------------- 1 | package iris.json.serialization 2 | 3 | import kotlin.reflect.KClass 4 | 5 | /** 6 | * @created 08.10.2020 7 | * @author [Ivan Ivanov](https://vk.com/irisism) 8 | */ 9 | @Target(AnnotationTarget.PROPERTY, AnnotationTarget.FIELD) 10 | annotation class JsonField(val name: String = "", val type: String = "") 11 | 12 | @Target(AnnotationTarget.PROPERTY, AnnotationTarget.FIELD) 13 | annotation class PolymorphCaseString(val label: String, val instance: KClass<*>) 14 | 15 | @Target(AnnotationTarget.PROPERTY, AnnotationTarget.FIELD) 16 | annotation class PolymorphCaseInt(val label: Int, val instance: KClass<*>) 17 | 18 | @Target(AnnotationTarget.PROPERTY, AnnotationTarget.FIELD) 19 | annotation class PolymorphData(val sourceField: String, val strings: Array = [], val ints: Array = []) -------------------------------------------------------------------------------- /src/iris/json/serialization/DeserializerMapImpl.kt: -------------------------------------------------------------------------------- 1 | package iris.json.serialization 2 | 3 | import iris.json.JsonEntry 4 | import iris.json.JsonItem 5 | import iris.json.JsonObject 6 | import kotlin.reflect.KClass 7 | 8 | /** 9 | * @created 09.10.2020 10 | * @author [Ivan Ivanov](https://vk.com/irisism) 11 | */ 12 | class DeserializerMapImpl : DeserializerMap { 13 | 14 | lateinit var valueDeserializer: Deserializer 15 | 16 | override fun getMap(entries: Collection): Map { 17 | val res = HashMap(entries.size) 18 | val valueDeserializer = this.valueDeserializer 19 | for ((key, value) in entries) 20 | res[key.toString()] = (valueDeserializer.deserialize(value) as T) 21 | return res 22 | } 23 | 24 | override fun deserialize(item: JsonItem): T { 25 | return getMap>((item as JsonObject).getEntries()) as T 26 | } 27 | 28 | override fun forSubclass(d: KClass<*>): Deserializer { 29 | return this 30 | } 31 | } -------------------------------------------------------------------------------- /src/iris/sequence/IrisSequenceSingleChar.kt: -------------------------------------------------------------------------------- 1 | package iris.sequence 2 | 3 | /** 4 | * @created 01.08.2020 5 | * @author [Ivan Ivanov](https://vk.com/irisism) 6 | */ 7 | class IrisSequenceSingleChar(private val ch: Char) : IrisSequence { 8 | override val length: Int 9 | get() = 1 10 | 11 | override fun get(index: Int): Char { 12 | if (index != 0) throw IndexOutOfBoundsException("$index is not 0 for single char sequence") 13 | return ch 14 | } 15 | 16 | override fun subSequence(startIndex: Int, endIndex: Int): CharSequence { 17 | if (startIndex != 0 && endIndex != 1) 18 | throw IndexOutOfBoundsException("Single char sequence can only be [0 to 1]") 19 | return this 20 | } 21 | 22 | override fun joinTo(buffer: A): A { 23 | buffer.append(ch) 24 | return buffer 25 | } 26 | 27 | override fun equals(other: Any?): Boolean { 28 | if (this === other) return true 29 | if (javaClass != other?.javaClass) return false 30 | other as IrisSequenceSingleChar 31 | if (ch != other.ch) return false 32 | return true 33 | } 34 | 35 | override fun hashCode(): Int { 36 | return ch.hashCode() 37 | } 38 | 39 | 40 | } -------------------------------------------------------------------------------- /test/iris/json/PureJavaUser.java: -------------------------------------------------------------------------------- 1 | package iris.json; 2 | 3 | import iris.json.plain.Female; 4 | import iris.json.plain.Human; 5 | import iris.json.plain.Male; 6 | import iris.json.serialization.PolymorphCaseString; 7 | import iris.json.serialization.PolymorphData; 8 | 9 | import java.util.ArrayList; 10 | import java.util.List; 11 | 12 | /** 13 | * @author [Ivan Ivanov](https://vk.com/irisism) 14 | * @created 15.10.2020 15 | */ 16 | public final class PureJavaUser { 17 | 18 | public int id; 19 | public String type; 20 | 21 | @PolymorphData( 22 | sourceField = "type", 23 | strings = {@PolymorphCaseString( 24 | instance = Male.class, 25 | label = "MaleFirst" 26 | ), @PolymorphCaseString( 27 | instance = Female.class, 28 | label = "FemaleFirst" 29 | )} 30 | ) 31 | public Human person1; 32 | @PolymorphData( 33 | sourceField = "type", 34 | strings = {@PolymorphCaseString( 35 | instance = Male.class, 36 | label = "FemaleFirst" 37 | ), @PolymorphCaseString( 38 | instance = Female.class, 39 | label = "MaleFirst" 40 | )} 41 | ) 42 | public Human person2; 43 | 44 | public PureJavaUser(int id, String type, Human person1, Human person2) { 45 | this.id = id; 46 | this.type = type; 47 | this.person1 = person1; 48 | this.person2 = person2; 49 | } 50 | } -------------------------------------------------------------------------------- /test/iris/json/test/usage_test_flow.kt: -------------------------------------------------------------------------------- 1 | package iris.json.test 2 | 3 | import iris.json.flow.JsonFlowParser 4 | import java.io.File 5 | 6 | /** Created 20.09.2020 */ 7 | fun main() { 8 | val testString = File("test.json").readText() 9 | 10 | // Demonstration of functional abilities 11 | val obj = JsonFlowParser.start(testString) // parsed to IrisJsonItem's 12 | 13 | // stringifies result objects 14 | println("IrisJsonItem.toString/JSON string: $obj") 15 | 16 | // stringifies objects to Appendable buffer 17 | val b = StringBuilder() 18 | obj.appendToJsonString(b) 19 | println("IrisJsonItem.joinTo/JSON string: $b") 20 | 21 | // Simple access to required object on objects tree 22 | println("IrisJsonItem toString/JSON string: " + obj["object"]["message"]["attachments"][0]["wall"]["id"]) 23 | 24 | // Converting to required types 25 | println("To Long: " + obj["object"]["message"]["attachments"][0]["wall"]["id"].asLong()) 26 | 27 | // Access by string path 28 | println("To Int: " + obj.find("object message attachments 0 wall id").asInt()) 29 | 30 | // Stylized to Java/JavaScript properties access 31 | println("To Double: " + obj.find("object.message.attachments[0].wall.id").asDouble()) 32 | 33 | obj["object"]["message"]["attachments"] = 12 34 | 35 | println(obj["object"]["message"]["attachments"].asInt()) 36 | } -------------------------------------------------------------------------------- /test/iris/json/test/usage_test_plain.kt: -------------------------------------------------------------------------------- 1 | package iris.json.test 2 | 3 | import iris.json.plain.JsonPlainParser 4 | import java.io.File 5 | 6 | /** 7 | * @created 06.09.2020 8 | * @author [Ivan Ivanov](https://vk.com/irisism) 9 | * 10 | */ 11 | 12 | fun main() { 13 | val testString = File("test.json").readText() 14 | 15 | // basic start 16 | // Demonstration of functional abilities 17 | val parser = JsonPlainParser(testString) 18 | val res = parser.parse() // parsed to IrisJsonItem's 19 | 20 | // stringifies result objects 21 | println("IrisJsonItem.toString/JSON string: $res") 22 | 23 | // stringifies objects to Appendable buffer 24 | val b = StringBuilder() 25 | res.appendToJsonString(b) 26 | println("IrisJsonItem.joinTo/JSON string: $b") 27 | 28 | // Simple access to required object on objects tree 29 | println("IrisJsonItem toString/JSON string: " + res["object"]["message"]["attachments"][0]["wall"]["id"]) 30 | 31 | // Converting to required types 32 | println("To Long: " + res["object"]["message"]["attachments"][0]["wall"]["id"].asLong()) 33 | 34 | // Access by string path 35 | println("To Int: " + res.find("object message attachments 0 wall id").asInt()) 36 | 37 | // Stylized to Java/JavaScript properties access 38 | println("To Double: " + res.find("object.message.attachments[0].wall.id").asDouble()) 39 | // basic end 40 | } -------------------------------------------------------------------------------- /src/iris/sequence/IrisSubSequence.kt: -------------------------------------------------------------------------------- 1 | package iris.sequence 2 | 3 | /** 4 | * @created 01.08.2020 5 | * @author [Ivan Ivanov](https://vk.com/irisism) 6 | */ 7 | class IrisSubSequence(private val source: CharSequence, val start: Int, val end: Int) : IrisSequence { 8 | 9 | private val len = end - start 10 | override val length: Int 11 | get() = len 12 | 13 | override fun get(index: Int): Char { 14 | return source[start + index] 15 | } 16 | 17 | override fun subSequence(startIndex: Int, endIndex: Int): CharSequence { 18 | return IrisSubSequence(source, this.start + startIndex, this.start + endIndex) 19 | } 20 | 21 | override fun toString(): String { 22 | return source.subSequence(start, end).toString() 23 | } 24 | 25 | override fun joinTo(buffer: A): A { 26 | buffer.append(source, start, end) 27 | return buffer 28 | } 29 | 30 | private var hash = 0 31 | private var hashed = false 32 | override fun hashCode(): Int { 33 | if (hashed) 34 | return hash 35 | var res = 0 36 | for (i in 0 until length) 37 | res = (res * 33) + source[start + i].toInt() 38 | hash = res 39 | hashed = true 40 | return res 41 | } 42 | 43 | override fun equals(other: Any?): Boolean { 44 | if (this === other) return true 45 | if (other !is CharSequence) return false 46 | if (length != other.length) return false 47 | for (i in 0 until length) 48 | if (source[start + i] != other[i]) 49 | return false 50 | return true 51 | } 52 | } -------------------------------------------------------------------------------- /src/iris/json/proxy/JsonProxyArray.kt: -------------------------------------------------------------------------------- 1 | package iris.json.proxy 2 | 3 | import iris.json.JsonArray 4 | import iris.json.JsonItem 5 | import iris.json.plain.IrisJsonItem 6 | 7 | /** 8 | * @created 26.09.2020 9 | * @author [Ivan Ivanov](https://vk.com/irisism) 10 | */ 11 | class JsonProxyArray(private val items: List) : JsonProxyItem(items), JsonArray { 12 | 13 | override fun get(ind: Int): IrisJsonItem { 14 | return JsonProxyUtil.wrap(items[ind]) 15 | } 16 | 17 | override fun get(key: String): IrisJsonItem { 18 | return get(key.toInt()) 19 | } 20 | 21 | override fun getList(): List { 22 | return items.map { JsonProxyUtil.wrap(it) } 23 | } 24 | 25 | override fun set(ind: Int, value: JsonItem): JsonItem { 26 | (items as MutableList)[ind] = value.obj() 27 | return this 28 | } 29 | 30 | override fun set(key: String, value: JsonItem): JsonItem { 31 | return set(key.toInt(), value) 32 | } 33 | 34 | override fun asList(): List { 35 | return items 36 | } 37 | 38 | override val size: Int 39 | get() = getList().size 40 | 41 | override fun isEmpty() = items.isEmpty() 42 | 43 | override fun isNotEmpty() = items.isNotEmpty() 44 | 45 | override fun isArray() = true 46 | 47 | override fun asTypedList(): List { 48 | return items as List 49 | } 50 | 51 | override fun iterator() = Iter() 52 | 53 | inner class Iter : Iterator { 54 | 55 | private val iterator = items.iterator() 56 | 57 | override fun hasNext(): Boolean { 58 | return iterator.hasNext() 59 | } 60 | 61 | override fun next(): JsonItem { 62 | return JsonProxyUtil.wrap(iterator.next()) 63 | } 64 | } 65 | } -------------------------------------------------------------------------------- /src/iris/json/flow/TokenerContentBuildReader.kt: -------------------------------------------------------------------------------- 1 | package iris.json.flow 2 | 3 | import iris.sequence.CharArrayBuilder 4 | import iris.sequence.IrisSubSequence 5 | import java.io.Reader 6 | import kotlin.math.max 7 | import kotlin.math.min 8 | 9 | /** 10 | * @created 20.09.2020 11 | * @author [Ivan Ivanov](https://vk.com/irisism) 12 | */ 13 | class TokenerContentBuildReader(private val reader: Reader, private val buffer: CharArray = CharArray(DEFAULT_BUFFER_SIZE)): TokenerAbstractWithPointer() { 14 | 15 | companion object { 16 | const val DEFAULT_BUFFER_SIZE = 4*1024 17 | } 18 | private var pointer = 0 19 | private val content = CharArrayBuilder(buffer.size) 20 | 21 | override fun curChar(): Char? { 22 | if (content.length <= pointer) { 23 | val am = reader.read(buffer) 24 | if (am == -1) 25 | return null 26 | content.append(buffer, 0, am) 27 | } 28 | return content[pointer] 29 | } 30 | 31 | override fun curCharInc(): Char? { 32 | val ch = curChar() 33 | pointer++ 34 | return ch 35 | } 36 | 37 | override fun moveNext() { 38 | pointer++ 39 | } 40 | 41 | override fun pointer(): Int { 42 | return pointer 43 | } 44 | 45 | override fun charSequence(start: Int, end: Int): CharSequence { 46 | return IrisSubSequence(content, start, end) 47 | } 48 | 49 | override fun exception(s: String): IllegalArgumentException { 50 | return IllegalArgumentException(s + " in position $pointer\n" + getPlace()) 51 | } 52 | 53 | private fun getPlace(): String { 54 | return '"' + content.substring(max(0, pointer - 10), min(pointer + 10, content.length - 1))+'"' 55 | } 56 | 57 | override fun back() { 58 | pointer-- 59 | } 60 | 61 | override fun getSourceSequence(start: Int, end: Int): CharSequence { 62 | return "" 63 | } 64 | } -------------------------------------------------------------------------------- /src/iris/json/flow/TokenerBufferedReader.kt: -------------------------------------------------------------------------------- 1 | package iris.json.flow 2 | 3 | import iris.sequence.CharArrayBuilder 4 | import java.io.Reader 5 | 6 | /** 7 | * @created 20.09.2020 8 | * @author [Ivan Ivanov](https://vk.com/irisism) 9 | */ 10 | class TokenerBufferedReader(private val reader: Reader, private val buffer: CharArray = CharArray(4*1024)): TokenerAbstractWithSequence() { 11 | 12 | private var isEof = false 13 | private var pointer = 0 14 | private var allocated = 0 15 | private var prevChar: Char? = null 16 | 17 | override fun curChar(): Char? { 18 | if (isEof) return null 19 | if (pointer == -1) 20 | return prevChar 21 | 22 | if (pointer >= allocated) { 23 | if (allocated != 0) 24 | prevChar = buffer[allocated-1] 25 | allocated = reader.read(buffer) 26 | if (allocated == -1) { 27 | isEof = true 28 | return null 29 | } 30 | pointer = 0 31 | } 32 | return buffer[pointer] 33 | } 34 | 35 | override fun curCharInc(): Char? { 36 | val ch = curChar()?: return null 37 | pointer++ 38 | return ch 39 | } 40 | 41 | override fun moveNext() { 42 | curCharInc() 43 | } 44 | 45 | override fun exception(s: String): IllegalArgumentException { 46 | TODO(s) 47 | } 48 | 49 | override fun back() { 50 | pointer-- 51 | } 52 | 53 | fun close() { 54 | reader.close() 55 | } 56 | 57 | override fun sequenceStart(): TokenSequence { 58 | return TImpl() 59 | } 60 | 61 | private class TImpl : TokenSequence { 62 | 63 | private val buff = CharArrayBuilder(16) 64 | 65 | override fun finish(shift: Int): CharSequence { 66 | return buff 67 | } 68 | 69 | override fun append(char: Char) { 70 | buff.append(char) 71 | } 72 | } 73 | 74 | override fun getSourceSequence(start: Int, end: Int): CharSequence { 75 | return "" 76 | } 77 | } -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## v0.6 4 | - Presence of `kotlin-reflect` library is now unnecessary. 5 | - Parse configuration added. `iris.json.Configuration` 6 | - Added toJsonString and appendToJsonString 7 | 8 | ## v0.5.2.1 9 | - Important bug fix: Recursive class references to themselves caused Stack overflow exception 10 | 11 | ## v0.5.2 12 | - `Deserializer` reformat. `JsonItem` instances now don't need to know deserializer types except `Deserializer` itself. 13 | - `@JsonField` support to let json-fields be named in other way than in deserialized object fields 14 | - Added `DeserializerJsonItem` to support instances of `JsonItem` 15 | - `DeserializerFactory.registerDeserializer()` added to register custom deserializers 16 | - Support of quoteless json object field names 17 | 18 | ## v0.5.1 19 | - Deserialization improvements 20 | - `.asObject()` instead of `.asObject(ClassName::class)` 21 | - Primitives, Collections, Maps support 22 | 23 | ## v0.5 24 | - Deserialization to objects 25 | - Performance improvements 26 | 27 | ## v0.4 28 | - JSON Proxy items added to imitate parsed JSON-nodes 29 | - Added setters 30 | - Added iterable items 31 | 32 | ## v0.3 33 | - JSON Flow Parser added (`iris.json.flow`) 34 | - `iris.sequence` package 35 | 36 | ## v0.2.1 37 | - `IrisJsonItem.asXxx()` optimization 38 | - `IrisJsonItem.asXxxOrNull()` added 39 | 40 | ## v0.2 41 | - `IrisJsonItem.find` selectors. 42 | - `IrisJsonItem.obj()` fixed. Now it generates full tree of target objects without proxies 43 | - `IrisJsonItem.asInt|asLong|asList|asDouble|asFloat|asBoolean|asString|asObject` methods added 44 | - `IrisJsonString.obj()` fixed handling of escape characters and speed improvement 45 | - `IrisJsonItem.joinTo` method 46 | 47 | ## v0.1 48 | - Iris JSON parser was created 49 | - Faster JSON-strings parse up to 4x times than `org.json` 50 | -------------------------------------------------------------------------------- /src/iris/json/JsonItem.kt: -------------------------------------------------------------------------------- 1 | package iris.json 2 | 3 | import iris.json.serialization.DeserializerFactory 4 | import kotlin.reflect.typeOf 5 | 6 | /** 7 | * @created 26.09.2020 8 | * @author [Ivan Ivanov](https://vk.com/irisism) 9 | */ 10 | interface JsonItem { 11 | operator fun get(ind: Int): JsonItem 12 | operator fun get(key: String): JsonItem 13 | 14 | operator fun set(ind: Int, value: JsonItem): JsonItem 15 | operator fun set(key: String, value: JsonItem): JsonItem 16 | operator fun set(ind: Int, value: Any?): JsonItem 17 | operator fun set(key: String, value: Any?): JsonItem 18 | 19 | fun obj(): Any? 20 | fun appendToJsonString(buffer: A): A 21 | fun toJsonString(): String 22 | 23 | fun iterable() : Iterable 24 | 25 | fun asIntOrNull(): Int? 26 | 27 | fun asInt(): Int 28 | 29 | fun asLongOrNull() : Long? 30 | 31 | fun asLong() : Long 32 | 33 | fun asDoubleOrNull() : Double? 34 | 35 | fun asDouble() : Double 36 | 37 | fun asFloatOrNull() : Float? 38 | 39 | fun asFloat() : Float 40 | 41 | fun asBooleanOrNull() : Boolean? 42 | 43 | fun asBoolean() : Boolean 44 | 45 | fun asList(): List 46 | 47 | fun asTypedList(): List 48 | 49 | fun asMap(): Map 50 | 51 | fun asStringOrNull(): String? 52 | 53 | fun asString(): String 54 | 55 | fun find(tree: Array): JsonItem 56 | 57 | fun find(tree: List): JsonItem 58 | 59 | fun isNull(): Boolean 60 | 61 | fun isNotNull() = !isNull() 62 | 63 | fun isPrimitive(): Boolean 64 | 65 | fun isArray(): Boolean 66 | 67 | fun isObject(): Boolean 68 | 69 | fun find(tree: String): JsonItem 70 | } 71 | 72 | inline fun JsonItem.cast(): T = this as T 73 | 74 | inline fun JsonItem.asObject(): T { 75 | val deserializer = DeserializerFactory.getDeserializer(typeOf()) 76 | return deserializer.deserialize(this) 77 | } -------------------------------------------------------------------------------- /src/iris/json/flow/TokenerSimpleReader.kt: -------------------------------------------------------------------------------- 1 | package iris.json.flow 2 | 3 | import iris.sequence.CharArrayBuilder 4 | import java.io.Reader 5 | 6 | /** 7 | * TODO: Works very slow. Need to get, how does [org.json.JSONTokener] works faster with same idea 8 | * 9 | * Use [iris.json.flow.TokenerBufferedReader] instead 10 | * @created 20.09.2020 11 | * @author [Ivan Ivanov](https://vk.com/irisism) 12 | * */ 13 | class TokenerSimpleReader(private val reader: Reader): TokenerAbstractWithSequence() { 14 | 15 | private var isEof = false 16 | private var curChar: Char? = null 17 | private var usePrev = false 18 | private var prevChar: Char? = null 19 | 20 | override fun curChar(): Char? { 21 | if (usePrev) 22 | return prevChar 23 | if (isEof) return null 24 | 25 | if (curChar != null) return curChar 26 | val value = reader.read() 27 | if (value == -1) { 28 | isEof = true 29 | usePrev = false 30 | curChar = null 31 | return null 32 | } 33 | curChar = value.toChar() 34 | return curChar 35 | } 36 | 37 | override fun curCharInc(): Char? { 38 | val ch = curChar()?: return null 39 | if (usePrev) { 40 | usePrev = false 41 | } else { 42 | prevChar = curChar 43 | curChar = null 44 | } 45 | return ch 46 | } 47 | 48 | override fun moveNext() { 49 | curCharInc() 50 | } 51 | 52 | override fun getSourceSequence(start: Int, end: Int): CharSequence { 53 | return "" 54 | } 55 | 56 | override fun exception(s: String): IllegalArgumentException { 57 | TODO(s) 58 | } 59 | 60 | override fun back() { 61 | usePrev = true 62 | } 63 | 64 | override fun sequenceStart(): TokenSequence { 65 | return TImpl() 66 | } 67 | 68 | fun close() { 69 | reader.close() 70 | } 71 | 72 | private class TImpl : TokenSequence { 73 | 74 | private val buff = CharArrayBuilder(16) 75 | 76 | override fun finish(shift: Int): CharSequence { 77 | return buff 78 | } 79 | 80 | override fun append(char: Char) { 81 | buff.append(char) 82 | } 83 | } 84 | } -------------------------------------------------------------------------------- /src/iris/json/proxy/JsonProxyObject.kt: -------------------------------------------------------------------------------- 1 | package iris.json.proxy 2 | 3 | import iris.json.JsonEntry 4 | import iris.json.JsonItem 5 | import iris.json.JsonObject 6 | import iris.json.plain.IrisJsonItem 7 | import iris.json.plain.IrisJsonNull 8 | 9 | /** 10 | * @created 26.09.2020 11 | * @author [Ivan Ivanov](https://vk.com/irisism) 12 | */ 13 | class JsonProxyObject(private val map: Map) : JsonProxyItem(map), JsonObject { 14 | 15 | constructor(vararg items: Pair) : this(LinkedHashMap(items.size).apply { this.putAll(items) }) 16 | 17 | override fun get(ind: Int): IrisJsonItem { 18 | return get(ind.toString()) 19 | } 20 | 21 | override operator fun set(key: String, value: JsonItem): JsonItem { 22 | if (map is MutableMap<*, *>) 23 | (map as MutableMap)[key] = value.obj() 24 | else 25 | throw IllegalStateException("Source map object is not mutable") 26 | return this 27 | } 28 | 29 | override fun set(ind: Int, value: JsonItem): JsonItem { 30 | return set(ind.toString(), value) 31 | } 32 | 33 | override fun get(key: String): IrisJsonItem { 34 | return JsonProxyUtil.wrap(map[key]) 35 | } 36 | 37 | override fun asMap(): Map { 38 | return map 39 | } 40 | 41 | override fun getEntries(): Collection { 42 | return map.map { it.key to JsonProxyUtil.wrap(it.value) } 43 | } 44 | 45 | override fun isObject() = true 46 | 47 | override fun iterator(): Iterator { 48 | return Iter() 49 | } 50 | 51 | inner class Iter: Iterator { 52 | 53 | private val iterator = map.iterator() 54 | 55 | override fun hasNext(): Boolean { 56 | return iterator.hasNext() 57 | } 58 | 59 | override fun next(): JsonEntry { 60 | val item = iterator.next() 61 | val value = when (item.value) { 62 | is Map<*, *> -> JsonProxyObject(item as Map) 63 | is List<*> -> JsonProxyArray(item as List) 64 | null -> IrisJsonNull.Null 65 | else -> JsonProxyValue(item) 66 | } 67 | return JsonEntry(item.key, value) 68 | } 69 | } 70 | } -------------------------------------------------------------------------------- /src/iris/json/flow/TokenerAbstractWithPointer.kt: -------------------------------------------------------------------------------- 1 | package iris.json.flow 2 | 3 | import iris.json.Util.ValueType 4 | 5 | /** 6 | * @created 20.09.2020 7 | * @author [Ivan Ivanov](https://vk.com/irisism) 8 | * 9 | */ 10 | abstract class TokenerAbstractWithPointer : Tokener { 11 | 12 | override fun nextChar(): Char { 13 | skipWhitespaces() 14 | return curCharInc() ?: throw exception("End of source data") 15 | } 16 | 17 | protected abstract fun curChar(): Char? 18 | protected abstract fun curCharInc(): Char? 19 | protected abstract fun moveNext() 20 | protected abstract fun pointer(): Int 21 | protected abstract fun charSequence(start: Int, end: Int): CharSequence 22 | 23 | private fun skipWhitespaces() { 24 | do { 25 | val char = curChar() ?: break 26 | if (!char.isWhitespace()) { 27 | break 28 | } 29 | moveNext() 30 | } while (true) 31 | } 32 | 33 | override fun readString(quote: Char): CharSequence { 34 | var escaping = false 35 | val start = this.pointer() 36 | do { 37 | val char = curCharInc() ?: break 38 | if (char == '\\') 39 | escaping = true 40 | else if (escaping) { 41 | escaping = false 42 | } else if (char == quote) { 43 | break 44 | } 45 | } while (true) 46 | return charSequence(start, pointer() - 1) 47 | } 48 | 49 | override fun readPrimitive(): Tokener.PrimitiveData { 50 | var curType = ValueType.Integer 51 | val first = pointer() 52 | var isFirst = true 53 | loop@ do { 54 | val char = curChar() ?: break 55 | when { 56 | char.isDigit() -> { 57 | } 58 | char == '-' -> if (!isFirst) curType = ValueType.Constant 59 | char == '.' -> if (curType == ValueType.Integer) curType = ValueType.Float 60 | char.isLetter() -> curType = ValueType.Constant 61 | else -> break@loop 62 | } 63 | if (isFirst) 64 | isFirst = false 65 | moveNext() 66 | } while (true) 67 | return Tokener.PrimitiveData(charSequence(first, pointer()), curType) 68 | } 69 | 70 | override fun readFieldName(quote: Char?): CharSequence { 71 | return if (quote==null) readPrimitive().sequence else readString(quote) 72 | } 73 | 74 | } -------------------------------------------------------------------------------- /src/iris/sequence/IrisSequenceCharList.kt: -------------------------------------------------------------------------------- 1 | package iris.sequence 2 | 3 | import kotlin.math.min 4 | 5 | /** 6 | * @created 01.08.2020 7 | * @author [Ivan Ivanov](https://vk.com/irisism) 8 | */ 9 | class IrisSequenceCharList(source: List, start: Int = 0, end: Int = source.size) : IrisSequence, CharArraySource { 10 | 11 | private val subList = if (start == 0 && end == source.size) source else source.subList(start, end) 12 | 13 | override val length: Int 14 | get() = subList.size 15 | 16 | override fun get(index: Int): Char { 17 | return subList[index] 18 | } 19 | 20 | override fun subSequence(startIndex: Int, endIndex: Int): CharSequence { 21 | return IrisSequenceCharList(subList, startIndex, endIndex) 22 | } 23 | 24 | override fun toString(): String { 25 | return String(charArray) 26 | } 27 | 28 | private val charArray by lazy(LazyThreadSafetyMode.NONE) { subList.toCharArray() } 29 | 30 | override fun joinTo(buffer: A): A { 31 | when (buffer) { 32 | is StringBuilder -> buffer.append(charArray) 33 | is StringBuffer -> buffer.append(charArray) 34 | else -> buffer.append(this.toString()) 35 | } 36 | return buffer 37 | } 38 | 39 | override fun hashCode(): Int { 40 | return subList.hashCode() 41 | } 42 | 43 | override fun equals(other: Any?): Boolean { 44 | if (this === other) return true 45 | if (other !is CharSequence) return false 46 | if (length != other.length) return false 47 | for (i in 0 until length) 48 | if (subList[i] != other[i]) 49 | return false 50 | return true 51 | } 52 | 53 | override fun toCharArray(): CharArray { 54 | val len = length 55 | return toCharArray(CharArray(len), 0, 0, len) 56 | } 57 | 58 | override fun toCharArray(start: Int, len: Int): CharArray { 59 | return toCharArray(CharArray(len), 0, start, len) 60 | } 61 | 62 | override fun toCharArray(dest: CharArray): CharArray { 63 | return toCharArray(dest, 0, 0, min(dest.size, subList.size)) 64 | } 65 | 66 | override fun toCharArray(dest: CharArray, destOffset: Int, start: Int, len: Int): CharArray { 67 | charArray.copyInto(dest, destOffset, start, start + len) 68 | return dest 69 | } 70 | } -------------------------------------------------------------------------------- /src/iris/json/flow/TokenerAbstractWithSequence.kt: -------------------------------------------------------------------------------- 1 | package iris.json.flow 2 | 3 | import iris.json.Util.ValueType 4 | 5 | /** 6 | * @created 20.09.2020 7 | * @author [Ivan Ivanov](https://vk.com/irisism) 8 | */ 9 | abstract class TokenerAbstractWithSequence() : Tokener { 10 | 11 | override fun nextChar(): Char { 12 | skipWhitespaces() 13 | return curCharInc() ?: throw exception("End of source data") 14 | } 15 | 16 | protected abstract fun curChar(): Char? 17 | protected abstract fun curCharInc(): Char? 18 | protected abstract fun moveNext() 19 | protected abstract fun sequenceStart(): TokenSequence 20 | 21 | interface TokenSequence { 22 | fun finish(shift: Int = 0): CharSequence 23 | fun append(char: Char) 24 | } 25 | 26 | private fun skipWhitespaces() { 27 | do { 28 | val char = curChar() ?: break 29 | if (!char.isWhitespace()) { 30 | break 31 | } 32 | moveNext() 33 | } while (true) 34 | } 35 | 36 | override fun readString(quote: Char): CharSequence { 37 | var escaping = false 38 | val seq = this.sequenceStart() 39 | do { 40 | val char = curCharInc()?: break 41 | if (char == '\\') 42 | escaping = true 43 | else if (escaping) { 44 | escaping = false 45 | } else if (char == quote) { 46 | break 47 | } 48 | seq.append(char) 49 | } while (true) 50 | return seq.finish(-1) 51 | } 52 | 53 | override fun readPrimitive(): Tokener.PrimitiveData { 54 | var curType = ValueType.Integer 55 | val seq = this.sequenceStart() 56 | var isFirst = true 57 | loop@ do { 58 | val char = curChar() ?: break 59 | when { 60 | char.isDigit() -> {} 61 | char == '-' -> if (!isFirst) curType = ValueType.Constant 62 | char == '.' -> if (curType == ValueType.Integer) curType = ValueType.Float 63 | char.isLetter() -> curType = ValueType.Constant 64 | else -> break@loop 65 | } 66 | if (isFirst) 67 | isFirst = false 68 | moveNext() 69 | seq.append(char) 70 | } while (true) 71 | return Tokener.PrimitiveData(seq.finish(), curType) 72 | } 73 | 74 | override fun readFieldName(quote: Char?): CharSequence { 75 | return if (quote==null) readPrimitive().sequence else readString(quote) 76 | } 77 | } -------------------------------------------------------------------------------- /src/iris/json/plain/IrisJsonArray.kt: -------------------------------------------------------------------------------- 1 | package iris.json.plain 2 | 3 | import iris.json.JsonArray 4 | import iris.json.JsonItem 5 | 6 | /** 7 | * @created 14.04.2020 8 | * @author [Ivan Ivanov](https://vk.com/irisism) 9 | */ 10 | class IrisJsonArray(private val items: List) : IrisJsonItem(), JsonArray { 11 | 12 | override fun appendToJsonString(buffer: A): A { 13 | buffer.append('[') 14 | if (items.isNotEmpty()) { 15 | val first = items.first() 16 | items.forEach { 17 | if (first !== it) 18 | buffer.append(", ") 19 | it.appendToJsonString(buffer) 20 | } 21 | } 22 | buffer.append(']') 23 | return buffer 24 | } 25 | 26 | override fun get(ind: Int): JsonItem { 27 | return items[ind] 28 | } 29 | 30 | override fun get(key: String): JsonItem { 31 | val ind = key.toInt() 32 | return get(ind) 33 | } 34 | 35 | override fun getList(): List { 36 | return items 37 | } 38 | 39 | override val size: Int 40 | get() = getList().size 41 | 42 | override fun isEmpty() = items.isEmpty() 43 | 44 | override fun isNotEmpty() = items.isNotEmpty() 45 | 46 | override fun set(ind: Int, value: JsonItem): JsonItem { 47 | (items as MutableList)[ind] = value 48 | val obj = this.obj 49 | if (obj != null) 50 | obj[ind] = value.obj() 51 | return this 52 | } 53 | 54 | override fun set(key: String, value: JsonItem): JsonItem { 55 | return set(key.toInt(), value) 56 | } 57 | 58 | private var obj : MutableList? = null 59 | 60 | override fun obj(): Any? { 61 | if (obj != null) 62 | return obj 63 | val res = mutableListOf() 64 | for (it in items) 65 | res.add(it.obj()) 66 | obj = res 67 | return res 68 | } 69 | 70 | override fun isArray() = true 71 | 72 | override fun iterable() = this 73 | 74 | override fun iterator(): Iterator { 75 | return Iter() 76 | } 77 | 78 | private inner class Iter : Iterator { 79 | 80 | private val size = items.size 81 | private var pointer = 0 82 | 83 | override fun hasNext(): Boolean { 84 | return pointer < size 85 | } 86 | 87 | override fun next(): JsonItem { 88 | val item = items[pointer] 89 | pointer++ 90 | return item 91 | } 92 | } 93 | } -------------------------------------------------------------------------------- /src/iris/json/flow/JsonFlowParser.kt: -------------------------------------------------------------------------------- 1 | package iris.json.flow 2 | 3 | import iris.json.Configuration 4 | import iris.json.Util 5 | import java.io.* 6 | import java.net.URL 7 | 8 | /** 9 | * @created 20.09.2020 10 | * @author [Ivan Ivanov](https://vk.com/irisism) 11 | */ 12 | class JsonFlowParser(val tokener: Tokener, val configuration: Configuration) { 13 | 14 | companion object { 15 | 16 | fun start(url: URL, configuration: Configuration = Configuration.globalConfiguration) = start(url.openStream(), configuration) 17 | 18 | fun start(ins: InputStream, configuration: Configuration = Configuration.globalConfiguration) = readItem(reader(InputStreamReader(ins)), configuration) 19 | 20 | fun start(reader: Reader, configuration: Configuration = Configuration.globalConfiguration) = readItem(reader(reader), configuration) 21 | 22 | fun start(file: File, configuration: Configuration = Configuration.globalConfiguration) = readItem(reader(FileReader(file)), configuration) 23 | 24 | fun start(text: String, configuration: Configuration = Configuration.globalConfiguration) = readItem(TokenerString(text), configuration) 25 | 26 | private fun reader(reader: Reader) = TokenerContentBuildReader(reader) 27 | 28 | fun start(source: Tokener, configuration: Configuration = Configuration.globalConfiguration) = readItem(source, configuration) 29 | 30 | fun readItem(source: Tokener, configuration: Configuration = Configuration.globalConfiguration): FlowItem { 31 | return JsonFlowParser(source, configuration).readItem() 32 | } 33 | } 34 | 35 | fun readItem(): FlowItem { 36 | val source = this.tokener 37 | val char = source.nextChar() 38 | val type = when { 39 | Util.isDigitOrAlpha(char) || char == '-' -> Util.Type.Value 40 | char == '{' -> Util.Type.Object 41 | char == '[' -> Util.Type.Array 42 | char == '"' || char == '\'' -> Util.Type.String 43 | else -> throw source.exception("Character: \"$char\"") 44 | } 45 | 46 | return when (type) { 47 | Util.Type.Value -> { 48 | source.back(); FlowValue(source) 49 | } 50 | Util.Type.Object -> FlowObject(this) 51 | Util.Type.String -> FlowString(source, char) 52 | Util.Type.Array -> FlowArray(this) 53 | else -> throw source.exception("$type not realised yet") 54 | } 55 | } 56 | } -------------------------------------------------------------------------------- /src/iris/json/flow/FlowString.kt: -------------------------------------------------------------------------------- 1 | package iris.json.flow 2 | 3 | import iris.json.JsonString 4 | import iris.json.plain.IrisJsonItem 5 | import iris.json.plain.IrisJsonNull 6 | import iris.sequence.IrisSequence 7 | 8 | /** 9 | * @created 20.09.2020 10 | * @author [Ivan Ivanov](https://vk.com/irisism) 11 | */ 12 | class FlowString(tokener: Tokener, val quote: Char) : FlowItem(tokener), JsonString { 13 | 14 | private var data: CharSequence? = null 15 | 16 | override fun appendToJsonString(buffer: A): A { 17 | parse() 18 | buffer.append('"') 19 | (data as? IrisSequence)?.joinTo(buffer) ?: buffer.append(data) 20 | buffer.append('"') 21 | return buffer 22 | } 23 | 24 | override fun get(ind: Int): IrisJsonItem { 25 | return IrisJsonNull.Null 26 | } 27 | 28 | override fun get(key: String): IrisJsonItem { 29 | return IrisJsonNull.Null 30 | } 31 | 32 | override fun parse() { 33 | if (data == null) 34 | data = tokener.readString(quote) 35 | } 36 | 37 | private var ready: String? = null// by lazy(LazyThreadSafetyMode.NONE) { init() } 38 | 39 | private fun init(): String { 40 | parse() 41 | val data = data!! 42 | val len = data.length 43 | val res = StringBuilder(len) 44 | if (len == 0) 45 | return "" 46 | var isEscape = false 47 | var fromIndex = 0 48 | var i = 0 49 | do { 50 | val ch = data[i] 51 | if (isEscape) { 52 | isEscape = false 53 | val repl = when (ch) { 54 | '"' -> '"' 55 | 'n' -> '\n' 56 | 'b' -> '\b' 57 | '/' -> '/' 58 | 'r' -> '\r' 59 | 't' -> '\t' 60 | 'u' -> 'u' 61 | else -> ch 62 | } 63 | //if (ch != '-') { 64 | res.append(data, fromIndex, i - 1) 65 | if (repl == 'u') { 66 | val d = data.subSequence(i + 1, i + 1 + 4).toString().toInt(16) 67 | res.appendCodePoint(d) 68 | i += 4 69 | } else { 70 | res.append(repl) 71 | } 72 | fromIndex = i + 1 73 | //} 74 | } else { 75 | if (ch == '\\') 76 | isEscape = true 77 | } 78 | i++ 79 | } while (i < len) 80 | 81 | return if (fromIndex == 0) // no any escape 82 | data.toString() 83 | else { 84 | if (fromIndex != len) 85 | res.append(data, fromIndex, len) 86 | res.toString() 87 | } 88 | } 89 | 90 | override fun obj(): String { 91 | if (ready != null) 92 | return ready!! 93 | ready = init() 94 | return ready!! 95 | } 96 | 97 | override fun isPrimitive() = true 98 | } 99 | -------------------------------------------------------------------------------- /src/iris/sequence/IrisSequenceCharArray.kt: -------------------------------------------------------------------------------- 1 | package iris.sequence 2 | 3 | import java.util.* 4 | 5 | /** 6 | * @created 01.08.2020 7 | * @author [Ivan Ivanov](https://vk.com/irisism) 8 | */ 9 | class IrisSequenceCharArray(val source: CharArray, val start: Int = 0, val end: Int = source.size) : IrisSequence, CharArraySource { 10 | 11 | private val len = end - start 12 | override val length: Int 13 | get() = len 14 | 15 | override fun get(index: Int): Char { 16 | return source[start + index] 17 | } 18 | 19 | override fun subSequence(startIndex: Int, endIndex: Int): CharSequence { 20 | return IrisSequenceCharArray(source,this.start + startIndex,this.start + endIndex) 21 | } 22 | 23 | override fun toString(): String { 24 | return String(source, start, length) 25 | } 26 | 27 | override fun joinTo(buffer: A): A { 28 | when (buffer) { 29 | is StringBuilder -> buffer.append(source, start, length) 30 | is StringBuffer -> buffer.append(source, start, length) 31 | else -> buffer.append(this) 32 | } 33 | return buffer 34 | } 35 | 36 | private var hash = 0 37 | private var hashed = false 38 | override fun hashCode(): Int { 39 | if (hashed) 40 | return hash 41 | var res = 0 42 | for (i in start until end) 43 | res = (res * 33) + source[i].toInt() 44 | hash = res 45 | hashed = true 46 | return res 47 | } 48 | 49 | override fun equals(other: Any?): Boolean { 50 | if (this === other) return true 51 | if (other == null) return false 52 | 53 | if (this.javaClass == other.javaClass) { 54 | other as IrisSequenceCharArray 55 | return Arrays.equals(source, start, end, other.source, other.start, other.end) 56 | } 57 | if (other !is CharSequence) 58 | return false 59 | val len = len 60 | if (len != other.length) return false 61 | val source = source 62 | var pos = start 63 | for (i in 0 until len) 64 | if (source[pos++] != other[i]) 65 | return false 66 | return true 67 | } 68 | 69 | override fun toCharArray(): CharArray { 70 | val len = length 71 | return toCharArray(CharArray(len), 0, 0, len) 72 | } 73 | 74 | override fun toCharArray(start: Int, len: Int): CharArray { 75 | return toCharArray(CharArray(len), 0, start, len) 76 | } 77 | 78 | override fun toCharArray(dest: CharArray): CharArray { 79 | return toCharArray(dest, 0, start, end - start) 80 | } 81 | 82 | override fun toCharArray(dest: CharArray, destOffset: Int, start: Int, len: Int): CharArray { 83 | val st = this.start + start 84 | source.copyInto(dest, destOffset, st, st + len) 85 | return dest 86 | } 87 | } -------------------------------------------------------------------------------- /src/iris/json/plain/IrisJsonObject.kt: -------------------------------------------------------------------------------- 1 | package iris.json.plain 2 | 3 | import iris.json.Configuration 4 | import iris.json.JsonEntry 5 | import iris.json.JsonItem 6 | import iris.json.JsonObject 7 | 8 | /** 9 | * @created 14.04.2020 10 | * @author [Ivan Ivanov](https://vk.com/irisism) 11 | */ 12 | open class IrisJsonObject(private val entries: List, private val configuration: Configuration) : IrisJsonItem(), JsonObject { 13 | 14 | constructor(vararg items: Pair, configuration: Configuration) : this(items.asList(), configuration) 15 | 16 | override fun get(key: String): JsonItem { 17 | return (entries.find {it.first == key }?.second) ?: IrisJsonNull.Null 18 | } 19 | 20 | override fun get(ind: Int): JsonItem { 21 | return get(ind.toString()) 22 | } 23 | 24 | override fun set(ind: Int, value: JsonItem): JsonItem { 25 | return set(ind.toString(), value) 26 | } 27 | 28 | override fun set(key: String, value: JsonItem): JsonItem { 29 | val el = entries as MutableList 30 | val index = el.indexOfFirst { it.first == key } 31 | if (index == -1) 32 | el += JsonEntry(key, value) 33 | else 34 | el[index] = JsonEntry(key, value) 35 | val obj = obj 36 | if (obj != null) { 37 | obj[key] = value.obj() 38 | } 39 | return this 40 | } 41 | 42 | private var obj: MutableMap? = null 43 | 44 | override fun obj(): Any? { 45 | if (obj != null) 46 | return obj 47 | val res = configuration.mapObjectFactory.getMap(entries.size) 48 | for (it in entries) 49 | res[it.first.toString()] = it.second.obj() 50 | obj = res 51 | return res 52 | } 53 | 54 | override fun appendToJsonString(buffer: A): A { 55 | buffer.append("{") 56 | var firstDone = false 57 | for (entry in entries) { 58 | if (firstDone) 59 | buffer.append(", ") 60 | else 61 | firstDone = true 62 | buffer.append("\"") 63 | buffer.append(entry.first) 64 | buffer.append("\": ") 65 | entry.second.appendToJsonString(buffer) 66 | 67 | } 68 | buffer.append('}') 69 | return buffer 70 | } 71 | 72 | override fun getEntries(): Collection { 73 | return entries 74 | } 75 | 76 | override fun isObject() = true 77 | 78 | override fun iterator(): Iterator { 79 | return Iter() 80 | } 81 | 82 | inner class Iter: Iterator { 83 | 84 | private val iterator = entries.iterator() 85 | 86 | override fun hasNext(): Boolean { 87 | return iterator.hasNext() 88 | } 89 | 90 | override fun next(): JsonEntry { 91 | return iterator.next() 92 | } 93 | } 94 | } 95 | 96 | 97 | -------------------------------------------------------------------------------- /src/iris/json/serialization/DeserializerFactory.kt: -------------------------------------------------------------------------------- 1 | package iris.json.serialization 2 | 3 | import iris.json.JsonItem 4 | import kotlin.reflect.KClass 5 | import kotlin.reflect.KType 6 | import kotlin.reflect.full.* 7 | import kotlin.reflect.jvm.jvmErasure 8 | 9 | /** 10 | * @created 05.10.2020 11 | * @author [Ivan Ivanov](https://vk.com/irisism) 12 | */ 13 | 14 | object DeserializerFactory { 15 | private val cache = mutableMapOf, Deserializer>() 16 | private val typeCache = mutableMapOf() 17 | private val anyClass = Any::class 18 | 19 | fun getDeserializer(d: KClass<*>, allowSuperclasses: Boolean = true): Deserializer { 20 | cache[d]?.let { return it } 21 | if (allowSuperclasses) { 22 | for (supers in d.superclasses) { 23 | if (supers == anyClass) continue 24 | cache[supers]?.let { 25 | return it.forSubclass(d).also { cache[d] = it } 26 | } 27 | } 28 | } 29 | val deser = DeserializerClassImpl() 30 | cache[d] = deser 31 | return DeserializerClassBuilder.build(d, deser) 32 | } 33 | 34 | fun registerDeserializer(d: KClass<*>, deserializer: Deserializer) { 35 | cache[d] = deserializer 36 | registerDeserializer(d.starProjectedType, deserializer) 37 | } 38 | 39 | fun registerDeserializer(type: KType, deserializer: Deserializer) { 40 | typeCache[type] = deserializer 41 | } 42 | 43 | fun getDeserializer(type: KType): Deserializer { 44 | typeCache[type] 45 | ?.let { return it } 46 | 47 | DeserializerPrimitiveImpl.convertType(type, null) 48 | ?.let { 49 | return it.also { typeCache[type] = it } 50 | } 51 | 52 | return with(type.jvmErasure) { when { 53 | isSubclassOf(Collection::class) -> { 54 | val deser = DeserializerCollectionImpl() 55 | typeCache[type] = deser 56 | deser.typeDeserializer = getDeserializer(type.arguments.firstOrNull()?.type?: throw IllegalStateException("Don't know how I got here")) 57 | deser 58 | } 59 | isSubclassOf(Map::class) -> { 60 | val deser = DeserializerMapImpl() 61 | typeCache[type] = deser 62 | deser.valueDeserializer = getDeserializer(getMapType(type)) 63 | deser 64 | } 65 | isSubclassOf(JsonItem::class) -> 66 | DeserializerJsonItem() 67 | else -> 68 | getDeserializer(this) 69 | }} 70 | } 71 | 72 | private fun getMapType(type: KType): KType { 73 | 74 | val type = with(type.jvmErasure) { 75 | if (this == Map::class) 76 | type 77 | else 78 | allSupertypes.find { 79 | it.jvmErasure == Map::class 80 | } ?: throw IllegalArgumentException("$type is not subclass of Map") 81 | } 82 | val (key, value) = type.arguments 83 | if (!key.type!!.isSubtypeOf(CharSequence::class.starProjectedType)) 84 | throw IllegalStateException("Map key cannot be non CharSequence inherited") 85 | return value.type!! 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/iris/json/serialization/DeserializerPrimitiveImpl.kt: -------------------------------------------------------------------------------- 1 | package iris.json.serialization 2 | 3 | import iris.json.JsonItem 4 | import iris.json.serialization.DeserializerPrimitive.Type 5 | import java.text.SimpleDateFormat 6 | import java.util.* 7 | import kotlin.reflect.KClass 8 | import kotlin.reflect.KType 9 | import kotlin.reflect.jvm.jvmErasure 10 | 11 | /** 12 | * @created 08.10.2020 13 | * @author [Ivan Ivanov](https://vk.com/irisism) 14 | */ 15 | 16 | class DeserializerPrimitiveImpl(private val type: Type) : DeserializerPrimitive { 17 | 18 | companion object { 19 | private val DATE = DeserializerPrimitiveImpl(Type.DATE) 20 | private val INTEGER = DeserializerPrimitiveImpl(Type.INTEGER) 21 | private val LONG = DeserializerPrimitiveImpl(Type.LONG) 22 | private val DOUBLE = DeserializerPrimitiveImpl(Type.DOUBLE) 23 | private val FLOAT = DeserializerPrimitiveImpl(Type.FLOAT) 24 | private val BOOLEAN = DeserializerPrimitiveImpl(Type.BOOLEAN) 25 | private val STRING = DeserializerPrimitiveImpl(Type.STRING) 26 | private val ANY = DeserializerPrimitiveImpl(Type.ANY) 27 | 28 | private val dateClass = Date::class 29 | private val intClass = Int::class 30 | private val longClass = Long::class 31 | private val doubleClass = Double::class 32 | private val floatClass = Float::class 33 | private val booleanClass = Boolean::class 34 | private val stringClass = String::class 35 | private val anyClass = Any::class 36 | 37 | private val format = SimpleDateFormat("YYYY-MM-dd HH:mm:ss") 38 | 39 | fun convertType(s: KType, fieldData: JsonField?): DeserializerPrimitiveImpl? { 40 | return if (fieldData?.type.isNullOrBlank()) { 41 | when (s.jvmErasure) { 42 | dateClass -> DATE 43 | intClass -> INTEGER 44 | longClass -> LONG 45 | doubleClass -> DOUBLE 46 | floatClass -> FLOAT 47 | booleanClass -> BOOLEAN 48 | stringClass -> STRING 49 | anyClass -> ANY 50 | else -> null 51 | } 52 | } else { 53 | when (fieldData!!.type) { 54 | "Datetime" -> DATE 55 | "Date" -> DATE 56 | "Integer", "Int" -> INTEGER 57 | "Long" -> LONG 58 | "Double" -> DOUBLE 59 | "Float" -> FLOAT 60 | "bool", "Boolean" -> BOOLEAN 61 | "String", "Text" -> STRING 62 | "Any" -> ANY 63 | else -> null 64 | } 65 | } 66 | } 67 | } 68 | 69 | override fun deserialize(item: JsonItem): T { 70 | return getValue(item) as T 71 | } 72 | 73 | override fun forSubclass(d: KClass<*>): Deserializer { 74 | return this 75 | } 76 | 77 | override fun getValue(item: JsonItem): Any? { 78 | return when (type) { 79 | Type.INTEGER -> item.asIntOrNull() 80 | Type.LONG -> item.asLongOrNull() 81 | Type.DOUBLE -> item.asDoubleOrNull() 82 | Type.FLOAT -> item.asFloatOrNull() 83 | Type.BOOLEAN -> item.asBooleanOrNull() 84 | Type.STRING -> item.asStringOrNull() 85 | Type.DATE -> when (val obj = item.obj()) { 86 | is Number -> Date(obj.toLong()*1000L) 87 | is String -> format.parse(obj) 88 | else -> obj 89 | } 90 | Type.ANY -> item.obj() 91 | //else -> item.obj() 92 | } 93 | } 94 | } -------------------------------------------------------------------------------- /src/iris/json/plain/IrisJsonValue.kt: -------------------------------------------------------------------------------- 1 | package iris.json.plain 2 | 3 | import iris.json.JsonValue 4 | import iris.json.Util 5 | import iris.sequence.* 6 | 7 | /** 8 | * @created 14.04.2020 9 | * @author [Ivan Ivanov](https://vk.com/irisism) 10 | */ 11 | class IrisJsonValue(private val data: IrisSequence, private val valueType: Util.ValueType) : IrisJsonItem(), JsonValue { 12 | override fun toString(): String { 13 | return data.toString() 14 | } 15 | 16 | override fun appendToJsonString(buffer: A): A { 17 | data.joinTo(buffer) 18 | return buffer 19 | } 20 | 21 | override fun get(ind: Int): IrisJsonItem { 22 | return IrisJsonNull.Null 23 | } 24 | 25 | override fun get(key: String): IrisJsonItem { 26 | return IrisJsonNull.Null 27 | } 28 | 29 | private fun init(): Any? { 30 | val s = data 31 | 32 | return when (valueType) { 33 | Util.ValueType.Constant -> when (s as CharSequence) { 34 | "null" -> null 35 | "true", "1" -> true 36 | "false", "0" -> false 37 | else -> s.toString() 38 | } 39 | Util.ValueType.Integer -> s.toLong() 40 | Util.ValueType.Float -> s.toDouble() 41 | //else -> throw IllegalArgumentException("No argument: $valueType") 42 | } 43 | } 44 | 45 | override fun asIntOrNull(): Int? { 46 | if (done) 47 | return super.asIntOrNull() 48 | val res = data.toIntOrNull() 49 | ready = res 50 | done = true 51 | return res 52 | } 53 | 54 | override fun asInt(): Int { 55 | if (done) 56 | return super.asInt() 57 | val res = data.toInt() 58 | ready = res 59 | done = true 60 | return res 61 | } 62 | 63 | override fun asLongOrNull(): Long? { 64 | if (done) 65 | return super.asLongOrNull() 66 | val res = data.toLongOrNull() 67 | ready = res 68 | done = true 69 | return res 70 | } 71 | 72 | override fun asLong(): Long { 73 | if (done) 74 | return super.asLong() 75 | val res = data.toLong() 76 | ready = res 77 | done = true 78 | return res 79 | } 80 | 81 | override fun asDoubleOrNull(): Double? { 82 | if (done) 83 | return super.asDoubleOrNull() 84 | val res = data.toDoubleOrNull() 85 | ready = res 86 | done = true 87 | return res 88 | } 89 | 90 | override fun asDouble(): Double { 91 | if (done) 92 | return super.asDouble() 93 | val res = data.toDouble() 94 | ready = res 95 | done = true 96 | return res 97 | } 98 | 99 | override fun asFloatOrNull(): Float? { 100 | if (done) 101 | return super.asFloatOrNull() 102 | val res = data.toFloatOrNull() 103 | ready = res 104 | done = true 105 | return res 106 | } 107 | 108 | override fun asFloat(): Float { 109 | if (done) 110 | return super.asFloat() 111 | val res = data.toFloat() 112 | ready = res 113 | done = true 114 | return res 115 | } 116 | 117 | private var ready: Any? = null 118 | private var done = false 119 | 120 | override fun obj(): Any? { 121 | if (done) 122 | return ready 123 | ready = init() 124 | done = true 125 | return ready 126 | } 127 | 128 | override fun isPrimitive() = true 129 | } -------------------------------------------------------------------------------- /src/iris/json/plain/IrisJsonString.kt: -------------------------------------------------------------------------------- 1 | package iris.json.plain 2 | 3 | import iris.json.JsonString 4 | import iris.sequence.toInt 5 | 6 | /** 7 | * @created 14.04.2020 8 | * @author [Ivan Ivanov](https://vk.com/irisism) 9 | */ 10 | class IrisJsonString(private val data: CharSequence/*, private val escapes: ArrayList?*/) : IrisJsonItem(), JsonString { 11 | 12 | override fun appendToJsonString(buffer: A): A { 13 | buffer.append('"') 14 | buffer.append(data) 15 | //data.joinTo(buffer) 16 | buffer.append('"') 17 | return buffer 18 | } 19 | 20 | override fun get(ind: Int): IrisJsonItem { 21 | return IrisJsonNull.Null 22 | } 23 | 24 | override fun get(key: String): IrisJsonItem { 25 | return IrisJsonNull.Null 26 | } 27 | 28 | private var ready: String? = null 29 | 30 | private fun init(): String { 31 | /*if (data.isEmpty()) return "" 32 | if (escapes == null) return data.toString() 33 | val len = data.length 34 | val data = data 35 | var i = 0 36 | var escIndex = 0 37 | val res = StringBuilder(len) 38 | do { 39 | val escToIndex = if (escIndex >= escapes.size) len else escapes[escIndex++] 40 | res.append(data, i, escToIndex) 41 | i = escToIndex + 1 42 | if (i >= len) break 43 | 44 | val ch = data[i] 45 | val repl = when (ch) { 46 | 'u' -> 'u' 47 | '"' -> '"' 48 | 'n' -> '\n' 49 | 'b' -> '\b' 50 | '/' -> '/' 51 | 'r' -> '\r' 52 | 't' -> '\t' 53 | else -> ch 54 | } 55 | if (repl == 'u') { 56 | val d = data.subSequence(i + 1, i + 1 + 4).toInt(16) 57 | res.appendCodePoint(d) 58 | i += 5 // uXXXX = 5 chars 59 | } else { 60 | res.append(repl) 61 | i++ 62 | } 63 | } while (i < len) 64 | 65 | return res.toString()*/ 66 | 67 | val len = data.length 68 | var isEscape = false 69 | var fromIndex = 0 70 | var i = 0 71 | val res = StringBuilder(len) 72 | do { 73 | val ch = data[i] 74 | if (isEscape) { 75 | isEscape = false 76 | val repl = when (ch) { 77 | '"' -> '"' 78 | 'n' -> '\n' 79 | 'b' -> '\b' 80 | '/' -> '/' 81 | 'r' -> '\r' 82 | 't' -> '\t' 83 | 'u' -> 'u' 84 | else -> ch 85 | } 86 | //if (ch != '-') { 87 | res.append(data, fromIndex, i - 1) 88 | if (repl == 'u') { 89 | val d = data.subSequence(i + 1, i + 1 + 4).toString().toInt(16) 90 | res.appendCodePoint(d) 91 | i += 4 92 | } else { 93 | res.append(repl) 94 | } 95 | fromIndex = i + 1 96 | //} 97 | } else { 98 | if (ch == '\\') 99 | isEscape = true 100 | } 101 | i++ 102 | } while (i < len) 103 | 104 | return if (fromIndex == 0) // no any escape 105 | data.toString() 106 | else { 107 | if (fromIndex != len) { 108 | res.append(data, fromIndex, len) 109 | } 110 | res.toString() 111 | } 112 | } 113 | 114 | override fun equals(other: Any?): Boolean { 115 | return asString() == other 116 | } 117 | 118 | override fun hashCode(): Int { 119 | return asString().hashCode() 120 | } 121 | 122 | override fun obj(): Any? { 123 | if (ready == null) 124 | ready = init() 125 | return ready 126 | } 127 | 128 | override fun isPrimitive() = true 129 | } 130 | -------------------------------------------------------------------------------- /src/iris/json/flow/TokenerString.kt: -------------------------------------------------------------------------------- 1 | package iris.json.flow 2 | 3 | import iris.json.Util.ValueType 4 | import iris.sequence.IrisSubSequence 5 | import kotlin.math.max 6 | import kotlin.math.min 7 | 8 | /** 9 | * @created 20.09.2020 10 | * @author [Ivan Ivanov](https://vk.com/irisism) 11 | */ 12 | class TokenerString(val source: String) : Tokener { 13 | 14 | companion object { 15 | const val SPACE = ' '.toInt() 16 | const val TAB = '\t'.toInt() 17 | const val LF = '\n'.toInt() 18 | const val CR = '\r'.toInt() 19 | } 20 | 21 | var pointer: Int = 0 22 | 23 | override fun nextChar(): Char { 24 | skipWhitespaces() 25 | return source[pointer++] 26 | } 27 | 28 | private fun skipWhitespaces() { 29 | val len = source.length 30 | do { 31 | val char = source[pointer].toInt() 32 | if (char > SPACE) 33 | break 34 | if (!(char == SPACE || char == TAB || char == LF || char == CR)) 35 | break 36 | pointer++ 37 | } while (pointer < len) 38 | } 39 | 40 | override fun getSourceSequence(start: Int, end: Int): CharSequence { 41 | return IrisSubSequence(source, start, if (end == 0) source.length else end) 42 | } 43 | 44 | override fun exception(s: String): IllegalArgumentException { 45 | return IllegalArgumentException(s + "\nPosition $pointer: " + getPlace()) 46 | } 47 | 48 | private fun getPlace(): String { 49 | val start = max(0, pointer - 20) 50 | val end = min(pointer + 20, source.length - 1) 51 | return '"' + source.substring(start, end) + '"' 52 | } 53 | 54 | override fun readString(quote: Char): CharSequence { 55 | var escaping = false 56 | val len = source.length 57 | val start = this.pointer 58 | var pointer = pointer 59 | do { 60 | val char = source[pointer++] 61 | if (escaping) { 62 | escaping = false 63 | } else if (char == '\\') 64 | escaping = true 65 | else if (char == quote) { 66 | break 67 | } 68 | } while (pointer < len) 69 | this.pointer = pointer 70 | return IrisSubSequence(source, start, pointer - 1) 71 | } 72 | 73 | override fun readFieldName(quote: Char?): CharSequence { 74 | return if (quote == null) readQuotelessFieldName() else readString(quote) 75 | } 76 | 77 | private fun readQuotelessFieldName(): CharSequence { 78 | val first = pointer 79 | val len = source.length 80 | do { 81 | if (!(source[pointer] in '0'..'9' || source[pointer] in 'a'..'z' || source[pointer] in 'A'..'Z')) 82 | break 83 | pointer++ 84 | } while (pointer < len) 85 | return IrisSubSequence(source, first, pointer) 86 | } 87 | 88 | override fun readPrimitive(): Tokener.PrimitiveData { 89 | var curType = ValueType.Integer 90 | val first = pointer 91 | val len = source.length 92 | loop@ do { 93 | when (source[pointer]) { 94 | in '0'..'9' -> {} 95 | '-' -> if (first != pointer) curType = ValueType.Constant 96 | '.' -> if (curType == ValueType.Integer) curType = ValueType.Float 97 | in 'a'..'z', in 'A'..'Z' -> curType = ValueType.Constant 98 | else -> break@loop 99 | } 100 | pointer++ 101 | } while (pointer < len) 102 | return Tokener.PrimitiveData(IrisSubSequence(source, first, pointer), curType) 103 | } 104 | 105 | override fun back() { 106 | pointer-- 107 | } 108 | 109 | } -------------------------------------------------------------------------------- /src/iris/sequence/SimpleStringReader.kt: -------------------------------------------------------------------------------- 1 | package iris.sequence 2 | 3 | import java.io.Reader 4 | import java.io.StringReader 5 | import kotlin.math.min 6 | import kotlin.math.round 7 | 8 | /** 9 | * @created 22.09.2020 10 | * @author [Ivan Ivanov](https://vk.com/irisism) 11 | */ 12 | class SimpleStringReader(private val source: CharSequence) : Reader() { 13 | 14 | private var pointer = 0 15 | private val length = source.length 16 | private val strSource = source as? String 17 | private val buff = (source as? CharArraySource) 18 | 19 | /*override fun read(cbuf: CharArray): Int { 20 | val length = source.length 21 | val pointer = this.pointer 22 | val realLen = min(length - pointer, cbuf.size) 23 | if (realLen <= 0) 24 | return -1 25 | 26 | if (strSource != null) { 27 | strSource.toCharArray(cbuf, 0, pointer, pointer + realLen) 28 | this.pointer += realLen 29 | } else if (buff != null) { 30 | buff.toCharArray(cbuf, 0, pointer, realLen) 31 | this.pointer += realLen 32 | } else { 33 | val till = pointer + realLen 34 | var off = 0 35 | var pointer = this.pointer 36 | while (pointer < till) { 37 | cbuf[off++] = source[pointer++] 38 | } 39 | this.pointer = pointer 40 | } 41 | return realLen 42 | }*/ 43 | 44 | override fun read(cbuf: CharArray, off: Int, len: Int): Int { 45 | val length = length 46 | val pointer = this.pointer 47 | val realLen = min(length - pointer, len) 48 | if (realLen <= 0) 49 | return -1 50 | 51 | if (strSource != null) { 52 | strSource.toCharArray(cbuf, off, pointer, pointer + realLen) 53 | this.pointer += realLen 54 | } else if (buff != null) { 55 | buff.toCharArray(cbuf, off, pointer, realLen) 56 | this.pointer += realLen 57 | } else { 58 | val till = pointer + realLen 59 | var off = off 60 | var pointer = this.pointer 61 | while (pointer < till) { 62 | cbuf[off++] = source[pointer++] 63 | } 64 | this.pointer = pointer 65 | } 66 | return realLen 67 | } 68 | 69 | override fun close() { 70 | 71 | } 72 | } 73 | 74 | fun main() { 75 | val buff = CharArray(4*1024) 76 | var totalSimple = 0.0 77 | var totalString = 0.0 78 | val repeats = 100_000 79 | test.length 80 | testChars.length 81 | repeat(repeats) { 82 | val str = testStrReader(buff) 83 | val ch = testSimpleStrReader(buff) 84 | totalSimple += ch 85 | totalString += str 86 | } 87 | totalSimple /= 1000000.0 88 | totalString /= 1000000.0 89 | val pct = round(totalString * 100.0 / totalSimple - 100).toInt() 90 | println("AVG: String: ${totalString / repeats} Simple: ${totalSimple / repeats} DIFF: ${(totalString - totalSimple)} (${if (pct >= 0) "+" else ""}$pct%)") 91 | } 92 | 93 | val test = (0..10_000).joinToString("|а") { it.toString() } 94 | val testChars = IrisSequenceCharArray(test.toCharArray()) 95 | fun testStrReader(buffer: CharArray): Long { 96 | val start = System.nanoTime() 97 | val str = StringReader(test) 98 | do { 99 | val am = str.read(buffer) 100 | if (am == -1) 101 | break 102 | } while (true) 103 | val end = System.nanoTime() 104 | return end - start 105 | } 106 | 107 | fun testSimpleStrReader(buffer: CharArray): Long { 108 | 109 | val start = System.nanoTime() 110 | val str = SimpleStringReader(testChars) 111 | do { 112 | val am = str.read(buffer) 113 | if (am == -1) 114 | break 115 | } while (true) 116 | val end = System.nanoTime() 117 | return end - start 118 | } -------------------------------------------------------------------------------- /src/iris/json/flow/FlowValue.kt: -------------------------------------------------------------------------------- 1 | package iris.json.flow 2 | 3 | import iris.json.JsonValue 4 | import iris.json.Util.ValueType 5 | import iris.json.plain.IrisJsonItem 6 | import iris.json.plain.IrisJsonNull 7 | import iris.sequence.* 8 | 9 | /** 10 | * @created 20.09.2020 11 | * @author [Ivan Ivanov](https://vk.com/irisism) 12 | */ 13 | class FlowValue(tokener: Tokener) : FlowItem(tokener), JsonValue { 14 | 15 | private var data: Tokener.PrimitiveData? = null 16 | 17 | override fun appendToJsonString(buffer: A): A { 18 | parse() 19 | buffer.append(data!!.sequence) 20 | return buffer 21 | } 22 | 23 | override fun get(ind: Int): IrisJsonItem { 24 | return IrisJsonNull.Null 25 | } 26 | 27 | override fun get(key: String): IrisJsonItem { 28 | return IrisJsonNull.Null 29 | } 30 | 31 | private fun init(): Any? { 32 | parse() 33 | val data = data!! 34 | val s = data.sequence 35 | return when (data.type) { 36 | ValueType.Constant -> when (s) { 37 | "null" -> null 38 | "true", "1" -> true 39 | "false", "0" -> false 40 | else -> s.toString() 41 | } 42 | ValueType.Integer -> s.toLong() 43 | ValueType.Float -> s.toDouble() 44 | else -> throw IllegalArgumentException("No argument: ${data.type}") 45 | } 46 | } 47 | 48 | override fun asIntOrNull(): Int? { 49 | if (done) 50 | return super.asIntOrNull() 51 | parse() 52 | val res = data!!.sequence.toIntOrNull() 53 | ready = res 54 | done = true 55 | return res 56 | } 57 | 58 | override fun asInt(): Int { 59 | if (done) 60 | return super.asInt() 61 | parse() 62 | val res = data!!.sequence.toInt() 63 | ready = res 64 | done = true 65 | return res 66 | } 67 | 68 | override fun asLongOrNull(): Long? { 69 | if (done) 70 | return super.asLongOrNull() 71 | parse() 72 | val res = data!!.sequence.toLongOrNull() 73 | ready = res 74 | done = true 75 | return res 76 | } 77 | 78 | override fun asLong(): Long { 79 | if (done) 80 | return super.asLong() 81 | parse() 82 | val res = data!!.sequence.toLong() 83 | ready = res 84 | done = true 85 | return res 86 | } 87 | 88 | override fun asDoubleOrNull(): Double? { 89 | if (done) 90 | return super.asDoubleOrNull() 91 | parse() 92 | val res = data!!.sequence.toDoubleOrNull() 93 | ready = res 94 | done = true 95 | return res 96 | } 97 | 98 | override fun asDouble(): Double { 99 | if (done) 100 | return super.asDouble() 101 | parse() 102 | val res = data!!.sequence.toDouble() 103 | ready = res 104 | done = true 105 | return res 106 | } 107 | 108 | override fun asFloatOrNull(): Float? { 109 | if (done) 110 | return super.asFloatOrNull() 111 | parse() 112 | val res = data!!.sequence.toFloatOrNull() 113 | ready = res 114 | done = true 115 | return res 116 | } 117 | 118 | override fun asFloat(): Float { 119 | if (done) 120 | return super.asFloat() 121 | parse() 122 | val res = data!!.sequence.toFloat() 123 | ready = res 124 | done = true 125 | return res 126 | } 127 | 128 | private var ready: Any? = null 129 | private var done = false 130 | 131 | override fun obj(): Any? { 132 | if (done) 133 | return ready 134 | ready = init() 135 | done = true 136 | return ready 137 | } 138 | 139 | override fun parse() { 140 | if (data != null) 141 | return 142 | data = this.tokener.readPrimitive() 143 | } 144 | 145 | override fun isPrimitive() = true 146 | } -------------------------------------------------------------------------------- /src/iris/json/JsonEncoder.kt: -------------------------------------------------------------------------------- 1 | package iris.json 2 | 3 | /** 4 | * @created 26.09.2020 5 | * @author [Ivan Ivanov](https://vk.com/irisism) 6 | */ 7 | object JsonEncoder { 8 | 9 | fun encode(obj: Any?, escapeUtf: Boolean = false): String { 10 | val sb = StringBuilder() 11 | encode(obj, sb, escapeUtf) 12 | return sb.toString() 13 | } 14 | 15 | fun encode(obj: Any?, sb: StringBuilder, escapeUtf: Boolean = false) { 16 | when (obj) { 17 | null -> sb.append("null") 18 | is Map<*, *> -> encode2(obj as Map, sb, escapeUtf) 19 | is Collection<*> -> array2String(obj, sb, escapeUtf) 20 | is Array<*> -> array2String(obj, sb, escapeUtf) 21 | else -> value2JsonString(obj, sb, escapeUtf) 22 | } 23 | } 24 | 25 | private fun encode2(obj: Map?, sb: StringBuilder, escapeUtf: Boolean = false) { 26 | if (obj == null) { 27 | sb.append("null") 28 | return 29 | } 30 | 31 | var first = true 32 | sb.append('{') 33 | 34 | for (entry in obj.entries) { 35 | if (first) 36 | first = false 37 | else 38 | sb.append(',') 39 | object2String(entry.key.toString(), entry.value, sb, escapeUtf) 40 | } 41 | 42 | sb.append('}') 43 | } 44 | 45 | private fun object2String(key: String?, value: Any?, sb: StringBuilder, escapeUtf: Boolean = false) { 46 | sb.append('"') 47 | if (key == null) 48 | sb.append("null") 49 | else 50 | escape(key, sb, escapeUtf) 51 | sb.append("\":") 52 | 53 | value2JsonString(value, sb, escapeUtf) 54 | } 55 | 56 | private fun escape(s: CharSequence, sb: StringBuilder, escapeUtf: Boolean/* = false*/) { 57 | val arr = s.toString().toCharArray() 58 | for (ch in arr) { 59 | //val ch = s[i] 60 | when (ch) { 61 | '"' -> sb.append("\\\"") 62 | '\\' -> sb.append("\\\\") 63 | '\b' -> sb.append("\\b") 64 | //'\f' -> sb.append("\\f") 65 | '\n' -> sb.append("\\n") 66 | '\r' -> sb.append("\\r") 67 | '\t' -> sb.append("\\t") 68 | '/' -> sb.append("\\/") 69 | else -> 70 | //Reference: http://www.unicode.org/versions/Unicode5.1.0/ 71 | if (escapeUtf && (ch in '\u0000'..'\u001F' || ch in '\u007F'..'\u009F' || ch in '\u2000'..'\u20FF')) { 72 | val ss = Integer.toHexString(ch.toInt()) 73 | sb.append("\\u") 74 | for (k in 0 until 4 - ss.length) { 75 | sb.append('0') 76 | } 77 | sb.append(ss.toUpperCase()) 78 | } else { 79 | sb.append(ch) 80 | } 81 | } 82 | }//for 83 | } 84 | 85 | private fun value2JsonString(value: Any?, sb: StringBuilder, escapeUtf: Boolean) { 86 | if (value == null) { 87 | sb.append("null") 88 | return 89 | } 90 | 91 | if (value is CharSequence) { 92 | sb.append('"'); escape(value, sb, escapeUtf); sb.append('"') 93 | return 94 | } 95 | 96 | if (value is Number) { 97 | sb.append(value.toString()) 98 | return 99 | } 100 | 101 | if (value is Map<*, *>) { 102 | encode2(value as Map, sb) 103 | return 104 | } 105 | 106 | if (value is List<*>) { 107 | array2String(value, sb, escapeUtf) 108 | return 109 | } 110 | 111 | if (value is Array<*>) { 112 | array2String(value, sb, escapeUtf) 113 | return 114 | } 115 | 116 | sb.append(value.toString()) 117 | 118 | } 119 | 120 | private fun array2String(list: Collection<*>?, sb: StringBuilder, escapeUtf: Boolean) { 121 | if (list == null) { 122 | sb.append("null") 123 | return 124 | } 125 | 126 | var first = true 127 | sb.append('[') 128 | 129 | for (value in list) { 130 | if (first) 131 | first = false 132 | else 133 | sb.append(',') 134 | value2JsonString(value, sb, escapeUtf) 135 | } 136 | sb.append(']') 137 | } 138 | 139 | private fun array2String(list: Array<*>?, sb: StringBuilder, escapeUtf: Boolean) { 140 | if (list == null) { 141 | sb.append("null") 142 | return 143 | } 144 | 145 | var first = true 146 | 147 | sb.append('[') 148 | for (value in list) { 149 | if (first) 150 | first = false 151 | else 152 | sb.append(',') 153 | value2JsonString(value, sb, escapeUtf) 154 | } 155 | sb.append(']') 156 | } 157 | } -------------------------------------------------------------------------------- /src/iris/json/flow/FlowObject.kt: -------------------------------------------------------------------------------- 1 | package iris.json.flow 2 | 3 | import iris.json.JsonEntry 4 | import iris.json.JsonItem 5 | import iris.json.JsonObject 6 | import iris.json.Util 7 | import iris.json.plain.IrisJsonNull 8 | import java.util.* 9 | 10 | /** 11 | * @created 20.09.2020 12 | * @author [Ivan Ivanov](https://vk.com/irisism) 13 | */ 14 | class FlowObject(private val parser: JsonFlowParser) : FlowItem(parser.tokener), JsonObject { 15 | 16 | override fun get(ind: Int): JsonItem { 17 | return get(ind.toString()) 18 | } 19 | 20 | private val entries = LinkedList() 21 | 22 | private var isDone = false 23 | private var needToParse: FlowItem? = null 24 | 25 | override fun get(key: String): JsonItem { 26 | for (e in entries) { 27 | if (e.first == key) 28 | return e.second 29 | } 30 | if (isDone) return IrisJsonNull.Null 31 | testNeedToParse() 32 | do { 33 | val next = parseNext() ?: break 34 | entries += next 35 | val nextVal = next.second as FlowItem 36 | if (next.first == key) { 37 | this.needToParse = nextVal 38 | return next.second 39 | } 40 | nextVal.parse() 41 | } while (true) 42 | 43 | isDone = true 44 | return IrisJsonNull.Null 45 | } 46 | 47 | private fun parseNext(): JsonEntry? { 48 | val tokener = this.tokener 49 | var char: Char? = tokener.nextChar() 50 | if (char == '}') { 51 | isDone = true 52 | return null 53 | } 54 | if (char == ',') { 55 | char = tokener.nextChar() 56 | } 57 | if (!(char == '"' || char == '\'')) { 58 | if (char != null && Util.isAlpha(char)) { 59 | char = null 60 | tokener.back() 61 | } else 62 | throw tokener.exception("\" (quote) or \"'\" was expected") 63 | } 64 | 65 | 66 | val key = tokener.readFieldName(char) 67 | char = tokener.nextChar() 68 | if (char != ':') 69 | throw tokener.exception("\":\" was expected") 70 | val value = parser.readItem() 71 | return JsonEntry(key, value) 72 | } 73 | 74 | private var obj: MutableMap? = null 75 | 76 | override fun obj(): Any? { 77 | if (obj != null) 78 | return obj 79 | parse() 80 | val res = parser.configuration.mapObjectFactory.getMap(entries.size) 81 | 82 | for (it in entries) { 83 | val key = it.first.toString() 84 | res[key] = it.second.obj() 85 | } 86 | obj = res 87 | return res 88 | } 89 | 90 | override fun set(key: String, value: JsonItem): JsonItem { 91 | val el = entries 92 | val index = el.indexOfFirst { it.first == key } 93 | if (index == -1) 94 | el += JsonEntry(key, value) 95 | else 96 | el[index] = JsonEntry(key, value) 97 | val obj = obj 98 | if (obj != null) { 99 | obj[key] = value.obj() 100 | } 101 | return this 102 | } 103 | 104 | override fun set(ind: Int, value: JsonItem): JsonItem { 105 | return set(ind.toString(), value) 106 | } 107 | 108 | override fun parse() { 109 | if (isDone) return 110 | testNeedToParse() 111 | do { 112 | val next = parseNext() ?: break 113 | entries += next 114 | (next.second as FlowItem).parse() 115 | } while (true) 116 | isDone = true 117 | } 118 | 119 | private fun testNeedToParse() { 120 | val needToParse = this.needToParse 121 | if (needToParse != null) { 122 | needToParse.parse() 123 | this.needToParse = null 124 | } 125 | } 126 | 127 | override fun appendToJsonString(buffer: A): A { 128 | parse() 129 | buffer.append("{") 130 | var firstDone = false 131 | for (entry in entries) { 132 | if (firstDone) 133 | buffer.append(", ") 134 | else 135 | firstDone = true 136 | buffer.append("\"") 137 | buffer.append(entry.first) 138 | buffer.append("\": ") 139 | entry.second.appendToJsonString(buffer) 140 | 141 | } 142 | buffer.append('}') 143 | return buffer 144 | } 145 | 146 | override fun getEntries(): Collection { 147 | parse() 148 | return entries 149 | } 150 | 151 | override fun isObject() = true 152 | 153 | override fun iterator(): Iterator { 154 | // TODO: Надо бы тут парсить по мере итератора, а не всё сразу 155 | parse() 156 | return Iter() 157 | } 158 | 159 | inner class Iter : Iterator { 160 | 161 | private val iterator = entries.iterator() 162 | 163 | override fun hasNext(): Boolean { 164 | return iterator.hasNext() 165 | } 166 | 167 | override fun next(): JsonEntry { 168 | return iterator.next() 169 | } 170 | } 171 | } -------------------------------------------------------------------------------- /test/iris/json/test/performance_object_tree.kt: -------------------------------------------------------------------------------- 1 | package iris.json.test 2 | 3 | import iris.json.flow.JsonFlowParser 4 | import iris.json.flow.TokenerString 5 | import iris.json.plain.JsonPlainParser 6 | import iris.json.proxy.JsonProxyObject 7 | import org.json.JSONObject 8 | import org.json.simple.parser.JSONParser 9 | import java.io.File 10 | import kotlin.math.roundToInt 11 | 12 | fun main() { 13 | PerformanceObjectTree.main() 14 | } 15 | 16 | object PerformanceObjectTree { 17 | fun main() { 18 | val testString = File("test.json").readText() 19 | 20 | var totalIris = 0.0 21 | var totalIrisProxy = 0.0 22 | var totalIrisOld = 0.0 23 | var totalJson = 0.0 24 | var totalSimpleJson = 0.0 25 | var totalPlainAccess = 0.0 26 | 27 | val map = JsonPlainParser(testString).parse().asMap() 28 | 29 | // little warmup 30 | testJsonParser(testString) 31 | testSimpleJsonParser(testString) 32 | testIrisParser(testString) 33 | testIrisProxy(map) 34 | testIrisParserOld(testString) 35 | testPlainAccess(map) 36 | 37 | 38 | repeat(100_000) { 39 | //totalJson += testJsonParser(testString) 40 | //totalSimpleJson += testSimpleJsonParser(testString) 41 | totalIris += testIrisParser(testString) 42 | totalIrisOld += testIrisParserOld(testString) 43 | totalIrisProxy += testIrisProxy(map) 44 | totalPlainAccess += testPlainAccess(map) 45 | 46 | if (it != 0 && it % 10_000 == 0) 47 | println("$it iteration") 48 | } 49 | 50 | totalIris /= 1000000.0 51 | totalIrisProxy /= 1000000.0 52 | totalIrisOld /= 1000000.0 53 | totalJson /= 1000000.0 54 | totalSimpleJson /= 1000000.0 55 | totalPlainAccess /= 1000000.0 56 | println("AVG:" + 57 | "\norg.json: ${totalJson.roundToInt()}" + 58 | "\norg.json.simple: ${totalSimpleJson.roundToInt()}" + 59 | "\nIris Plain: ${totalIrisOld.roundToInt()}" + 60 | "\nIris Flow: ${totalIris.roundToInt()}" + 61 | "\nIris Proxy: ${totalIrisProxy.roundToInt()}" + 62 | "\nPOJO: ${totalPlainAccess.roundToInt()}" 63 | ) 64 | } 65 | 66 | fun testPlainAccess(map: Map<*, *>): Long { 67 | val start = System.nanoTime() 68 | val d = map.asMap()["object"].asMap()["message"].asMap()["attachments"].asList()[0].asMap()["wall"].asMap()["id"] as Long 69 | if (d == 1L) // check for not to let compiler optimize code 70 | print("true") 71 | val end = System.nanoTime() 72 | return end - start 73 | } 74 | 75 | private inline fun Any?.asMap() = this as Map<*, *> 76 | private inline fun Any?.asList() = this as List<*> 77 | 78 | fun testIrisProxy(map: Map): Long { 79 | val start = System.nanoTime() 80 | val rand = JsonProxyObject(map) 81 | val d = rand["object"]["message"]["attachments"][0]["wall"]["id"].asLong() 82 | if (d == 1L) // check for not to let compiler optimize code 83 | print("true") 84 | val end = System.nanoTime() 85 | return end - start 86 | } 87 | 88 | fun testIrisParserOld(test: String): Long { 89 | val start = System.nanoTime() 90 | val rand = JsonPlainParser(test).parse() 91 | val d = rand["object"]["message"]["attachments"][0]["wall"]["id"].asLong() 92 | if (d == 1L) // check for not to let compiler optimize code 93 | print("true") 94 | val end = System.nanoTime() 95 | return end - start 96 | } 97 | 98 | fun testIrisParser(test: String): Long { 99 | val start = System.nanoTime() 100 | val rand = JsonFlowParser.readItem(TokenerString(test)) 101 | val d = rand["object"]["message"]["attachments"][0]["wall"]["id"].asLong() 102 | if (d == 1L) // check for not to let compiler optimize code 103 | print("true") 104 | val end = System.nanoTime() 105 | return end - start 106 | } 107 | 108 | fun testJsonParser(test: String): Long { 109 | 110 | val start = System.nanoTime() 111 | val rand = JSONObject(test) 112 | val d = rand.getJSONObject("object").getJSONObject("message").getJSONArray("attachments").getJSONObject(0).getJSONObject("wall").getLong("id") 113 | if (d == 1L) // check for not to let compiler optimize code 114 | print("true") 115 | val end = System.nanoTime() 116 | return end - start 117 | } 118 | 119 | fun testSimpleJsonParser(test: String): Long { 120 | val start = System.nanoTime() 121 | val rand = JSONParser().parse(test) 122 | val d = rand.asMap()["object"].asMap()["message"].asMap()["attachments"].asList()[0].asMap()["wall"].asMap()["id"] as Long 123 | if (d == 1L) // check for not to let compiler optimize code 124 | print("true") 125 | val end = System.nanoTime() 126 | return end - start 127 | } 128 | } -------------------------------------------------------------------------------- /src/iris/json/flow/FlowArray.kt: -------------------------------------------------------------------------------- 1 | package iris.json.flow 2 | 3 | import iris.json.JsonArray 4 | import iris.json.JsonItem 5 | 6 | /** 7 | * @created 20.09.2020 8 | * @author [Ivan Ivanov](https://vk.com/irisism) 9 | */ 10 | class FlowArray(private val parser: JsonFlowParser) : FlowItem(parser.tokener), JsonArray { 11 | 12 | private val items = mutableListOf() 13 | 14 | override fun appendToJsonString(buffer: A): A { 15 | parse() 16 | buffer.append('[') 17 | if (items.isNotEmpty()) { 18 | val first = items.first() 19 | items.forEach { 20 | if (first !== it) 21 | buffer.append(", ") 22 | it.appendToJsonString(buffer) 23 | } 24 | } 25 | buffer.append(']') 26 | return buffer 27 | } 28 | 29 | private var isDone = false 30 | private var needToParse: FlowItem? = null 31 | 32 | override fun get(ind: Int): JsonItem { 33 | if (isDone) 34 | return items[ind] 35 | val toAdd = ind - items.size 36 | if (toAdd < 0) 37 | return items[ind] 38 | 39 | testNeedToParse() 40 | if (toAdd > 0) { 41 | for (i in 0 until toAdd) { 42 | val next = parseNext() 43 | if (next == null) { 44 | isDone = true 45 | break 46 | } 47 | items += next 48 | next.parse() 49 | } 50 | } 51 | val next = parseNext() 52 | if (next == null) { 53 | isDone = true 54 | } else { 55 | items += next 56 | needToParse = next 57 | } 58 | 59 | return items[ind] 60 | } 61 | 62 | private fun parseNext(): FlowItem? { 63 | val char = tokener.nextChar() 64 | if (char == ']') { 65 | isDone = true 66 | return null 67 | } 68 | if (char != ',') 69 | tokener.back() 70 | else { 71 | val char = tokener.nextChar() 72 | if (char == ']') { 73 | if (!parser.configuration.trailingCommaAllowed) 74 | throw tokener.exception("Trailing commas are not allowed in current configuration settings. ") 75 | return null 76 | } 77 | tokener.back() 78 | } 79 | 80 | return parser.readItem() 81 | } 82 | 83 | private fun parseNextAndAdd(): FlowItem? { 84 | val next = parseNext() 85 | if (next == null) { 86 | isDone = true 87 | return null 88 | } 89 | items += next 90 | next.parse() 91 | return next 92 | } 93 | 94 | override fun parse() { 95 | if (isDone) 96 | return 97 | testNeedToParse() 98 | do { 99 | val next = parseNext() ?: break 100 | next.parse() 101 | items += next 102 | } while (true) 103 | isDone = true 104 | } 105 | 106 | private fun testNeedToParse() { 107 | val needToParse = this.needToParse 108 | if (needToParse != null) { 109 | needToParse.parse() 110 | this.needToParse = null 111 | } 112 | } 113 | 114 | override fun get(key: String): JsonItem { 115 | val ind = key.toInt() 116 | return get(ind) 117 | } 118 | 119 | override fun getList(): List { 120 | parse() 121 | return items 122 | } 123 | 124 | override val size: Int 125 | get() = getList().size 126 | 127 | override fun isEmpty(): Boolean { 128 | if (isDone) return items.isEmpty() 129 | if (items.isNotEmpty()) return false 130 | val res = parseNextAndAdd() 131 | return res == null 132 | } 133 | 134 | override fun isNotEmpty(): Boolean { 135 | if (isDone) return items.isNotEmpty() 136 | if (items.isNotEmpty()) return true 137 | val res = parseNextAndAdd() 138 | return res != null 139 | } 140 | 141 | override fun set(ind: Int, value: JsonItem): JsonItem { 142 | items[ind] = value 143 | val obj = this.obj 144 | if (obj != null) 145 | obj[ind] = value.obj() 146 | return this 147 | } 148 | 149 | override fun set(key: String, value: JsonItem): JsonItem { 150 | return set(key.toInt(), value) 151 | } 152 | 153 | private var obj : MutableList? = null 154 | 155 | override fun obj(): Any? { 156 | return obj ?: run { 157 | parse() 158 | obj = items.mapTo(ArrayList(items.size)) { it.obj() } 159 | obj 160 | } 161 | } 162 | 163 | override fun iterable() = this 164 | 165 | override fun iterator(): Iterator { 166 | return Iter() 167 | } 168 | 169 | override fun isArray() = true 170 | 171 | private inner class Iter : Iterator { 172 | 173 | private var pointer = 0 174 | 175 | override fun hasNext(): Boolean { 176 | if (isDone) { 177 | return pointer < items.size 178 | } 179 | 180 | while (pointer >= items.size) { 181 | parseNextAndAdd() ?: break 182 | } 183 | return pointer < items.size 184 | } 185 | 186 | override fun next(): JsonItem { 187 | return get(pointer++) 188 | } 189 | } 190 | } -------------------------------------------------------------------------------- /src/iris/json/plain/IrisJsonItem.kt: -------------------------------------------------------------------------------- 1 | package iris.json.plain 2 | 3 | import iris.json.JsonItem 4 | import iris.json.proxy.JsonProxyUtil 5 | 6 | /** 7 | * @created 14.04.2020 8 | * @author [Ivan Ivanov](https://vk.com/irisism) 9 | */ 10 | abstract class IrisJsonItem() : JsonItem { 11 | 12 | override fun iterable(): Iterable { 13 | throw IllegalStateException("This is not iterable json item") 14 | } 15 | 16 | override fun asIntOrNull(): Int? { 17 | return when (val obj = obj()) { 18 | is Int -> obj 19 | is Number -> obj.toInt() 20 | else -> obj.toString().toIntOrNull() 21 | } 22 | } 23 | 24 | override fun asInt(): Int { 25 | return when (val obj = obj()) { 26 | is Int -> obj 27 | is Number -> obj.toInt() 28 | else -> obj.toString().toInt() 29 | } 30 | } 31 | 32 | override fun asLongOrNull(): Long? { 33 | return when (val obj = obj()) { 34 | is Long -> obj 35 | is Number -> obj.toLong() 36 | else -> obj.toString().toLongOrNull() 37 | } 38 | } 39 | 40 | override fun asLong(): Long { 41 | return when (val obj = obj()) { 42 | is Long -> obj 43 | is Number -> obj.toLong() 44 | else -> obj.toString().toLong() 45 | } 46 | } 47 | 48 | override fun asDoubleOrNull(): Double? { 49 | return when (val obj = obj()) { 50 | is Double -> obj 51 | is Number -> obj.toDouble() 52 | else -> obj.toString().toDoubleOrNull() 53 | } 54 | } 55 | 56 | override fun asDouble(): Double { 57 | return when (val obj = obj()) { 58 | is Double -> obj 59 | is Number -> obj.toDouble() 60 | else -> obj.toString().toDouble() 61 | } 62 | } 63 | 64 | override fun asFloatOrNull(): Float? { 65 | return when (val obj = obj()) { 66 | is Float -> obj 67 | is Number -> obj.toFloat() 68 | else -> obj.toString().toFloatOrNull() 69 | } 70 | } 71 | 72 | override fun asFloat(): Float { 73 | return when (val obj = obj()) { 74 | is Float -> obj 75 | is Number -> obj.toFloat() 76 | else -> obj.toString().toFloat() 77 | } 78 | } 79 | 80 | override fun asBooleanOrNull(): Boolean? { 81 | return when (val obj = obj()) { 82 | is Boolean -> obj 83 | else -> null 84 | } 85 | } 86 | 87 | override fun asBoolean(): Boolean { 88 | return (obj() as Boolean) 89 | } 90 | 91 | override fun asList(): List { 92 | return when (val obj = obj()) { 93 | is List<*> -> obj 94 | else -> (obj as Iterable<*>).toList() 95 | } 96 | } 97 | 98 | override fun asTypedList(): List { 99 | return when (val obj = obj()) { 100 | is List<*> -> obj as List 101 | else -> (obj as Iterable<*>).toList() as List 102 | } 103 | } 104 | 105 | override fun asMap(): Map { 106 | return (obj() as Map) 107 | } 108 | 109 | override fun asStringOrNull(): String? { 110 | return obj()?.toString() 111 | } 112 | 113 | override fun asString(): String { 114 | return obj()?.toString()!! 115 | } 116 | 117 | override fun find(tree: Array): JsonItem { 118 | var cur: JsonItem = this 119 | for (t in tree) { 120 | if (t.isEmpty()) continue 121 | cur = cur[t] 122 | } 123 | return cur 124 | } 125 | 126 | override fun find(tree: List): JsonItem { 127 | var cur: JsonItem = this 128 | for (t in tree) { 129 | if (t.isEmpty()) continue 130 | cur = cur[t] 131 | } 132 | return cur 133 | } 134 | 135 | override fun find(tree: String): JsonItem { 136 | return find(tree.replace('[', '.').replace("]", "").replace(' ', '.').split('.')) 137 | } 138 | 139 | override fun set(ind: Int, value: JsonItem): JsonItem { 140 | throw IllegalStateException("Set operation is not available") 141 | } 142 | 143 | override fun set(key: String, value: JsonItem): JsonItem { 144 | throw IllegalStateException("Set operation is not available") 145 | } 146 | 147 | override fun set(ind: Int, value: Any?): JsonItem { 148 | return set(ind, JsonProxyUtil.wrap(value)) 149 | } 150 | 151 | override fun set(key: String, value: Any?): JsonItem { 152 | return set(key, JsonProxyUtil.wrap(value)) 153 | } 154 | 155 | override fun equals(other: Any?): Boolean { 156 | return when (other) { 157 | null -> false 158 | is JsonItem -> obj() == other.obj() 159 | else -> obj() == other 160 | } 161 | } 162 | 163 | override fun hashCode(): Int { 164 | return obj().hashCode() 165 | } 166 | 167 | override fun isNull() = false 168 | 169 | override fun isNotNull() = !isNull() 170 | 171 | override fun isPrimitive() = false 172 | 173 | override fun isArray() = false 174 | 175 | override fun isObject() = false 176 | 177 | override fun toJsonString(): String { 178 | return appendToJsonString(StringBuilder()).toString() 179 | } 180 | } -------------------------------------------------------------------------------- /test.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "message_new", 3 | "object": { 4 | "message": { 5 | "date": 1586893276, 6 | "from_id": 23434235, 7 | "id": 0, 8 | "out": 0, 9 | "peer_id": 2000000001, 10 | "text": "", 11 | "conversation_message_id": 21248, 12 | "fwd_messages": [], 13 | "ref": "feed_top", 14 | "ref_source": "feed_top", 15 | "important": false, 16 | "random_id": 0, 17 | "attachments": [ 18 | { 19 | "type": "wall", 20 | "wall": { 21 | "id": 125041, 22 | "from_id": -34534534, 23 | "to_id": -34534534, 24 | "date": 1586892602, 25 | "post_type": "post", 26 | "text": "«Левада-центр» отметил в 2017 году 42% людей, а в настоящее время всего 29%.\n\n#OL #Статистика", 27 | "marked_as_ads": 0, 28 | "attachments": [ 29 | { 30 | "type": "photo", 31 | "photo": { 32 | "id": 443355, 33 | "album_id": -7, 34 | "owner_id": -444444, 35 | "user_id": 100, 36 | "sizes": [ 37 | { 38 | "type": "m", 39 | "url": "https://sun9-47.userapi.com/c856524/v856524072/15f33d/JF4hwJsQXyI.jpg", 40 | "width": 130, 41 | "height": 87 42 | }, 43 | { 44 | "type": "o", 45 | "url": "https://sun9-43.userapi.com/c856524/v856524072/15f340/RPBGus8_iOM.jpg", 46 | "width": 130, 47 | "height": 87 48 | }, 49 | { 50 | "type": "p", 51 | "url": "https://sun9-50.userapi.com/c856524/v856524072/15f341/7ESDnWLgbWM.jpg", 52 | "width": 200, 53 | "height": 133 54 | }, 55 | { 56 | "type": "q", 57 | "url": "https://sun9-32.userapi.com/c856524/v856524072/15f342/3A1Tkaj-ZLQ.jpg", 58 | "width": 320, 59 | "height": 213 60 | }, 61 | { 62 | "type": "r", 63 | "url": "https://sun9-38.userapi.com/c856524/v856524072/15f343/e-DhriFCu6c.jpg", 64 | "width": 510, 65 | "height": 340 66 | }, 67 | { 68 | "type": "s", 69 | "url": "https://sun9-67.userapi.com/c856524/v856524072/15f33c/tZfn9jsYSQM.jpg", 70 | "width": 75, 71 | "height": 50 72 | }, 73 | { 74 | "type": "x", 75 | "url": "https://sun9-52.userapi.com/c856524/v856524072/15f33e/PrZ3kQX91AE.jpg", 76 | "width": 604, 77 | "height": 403 78 | }, 79 | { 80 | "type": "y", 81 | "url": "https://sun9-43.userapi.com/c856524/v856524072/15f33f/elXIIDh7RwY.jpg", 82 | "width": 630, 83 | "height": 420 84 | } 85 | ], 86 | "text": "", 87 | "date": 1586889483, 88 | "access_key": "2cdc6a54ad629248b0" 89 | } 90 | }, 91 | { 92 | "type": "poll", 93 | "poll": { 94 | "id": 370300936, 95 | "owner_id": -133353, 96 | "created": 1586889258, 97 | "question": "Should you try?", 98 | "votes": 283, 99 | "answers": [ 100 | { 101 | "id": 1237881034, 102 | "text": "Да", 103 | "votes": 35, 104 | "rate": 12.37 105 | }, 106 | { 107 | "id": 1237881035, 108 | "text": "Нет", 109 | "votes": 248, 110 | "rate": 87.63 111 | } 112 | ], 113 | "anonymous": true, 114 | "multiple": false, 115 | "answer_ids": [], 116 | "end_date": 0, 117 | "closed": false, 118 | "is_board": false, 119 | "can_edit": false, 120 | "can_vote": false, 121 | "can_report": false, 122 | "can_share": false, 123 | "author_id": -173961453, 124 | "background": { 125 | "angle": 180, 126 | "color": "5c6778", 127 | "id": 8, 128 | "name": "серый фон", 129 | "points": [ 130 | { 131 | "color": "707c8c", 132 | "position": 0 133 | }, 134 | { 135 | "color": "4d5565", 136 | "position": 1 137 | } 138 | ], 139 | "type": "gradient" 140 | } 141 | } 142 | } 143 | ], 144 | "comments": { 145 | "count": 7 146 | }, 147 | "likes": { 148 | "count": 12 149 | }, 150 | "reposts": { 151 | "count": 0 152 | }, 153 | "views": { 154 | "count": 607 155 | }, 156 | "from": { 157 | "id": 173961453, 158 | "name": "Lalala OL", 159 | "screen_name": "efefe", 160 | "is_closed": 0, 161 | "type": "page" 162 | } 163 | } 164 | } 165 | ], 166 | "is_hidden": false 167 | }, 168 | "client_info": { 169 | "button_actions": [ 170 | "text", 171 | "vkpay", 172 | "open_app", 173 | "location", 174 | "open_link" 175 | ], 176 | "keyboard": true, 177 | "inline_keyboard": true, 178 | "lang_id": 0 179 | } 180 | }, 181 | "group_id": 181050268, 182 | "event_id": "345g3434gfdgdfgg34g34g34g", 183 | "secret": "34g34g34g34ggrdfdgfd" 184 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # iris-json-parser for Kotlin 2 | **Faster up to 4 times** parser comparing to standard org.json because of late objects initialization 3 | 4 | Speed improvement is achieved by idea of Proxy pattern, where objects are created when requested. 5 | 6 | ## Realisations for all languages 7 | - **Kotlin** (main) [iris-json-parser-kotlin](https://github.com/iris2iris/iris-json-parser-kotlin) 8 | - **Java** [iris-json-parser-java](https://github.com/iris2iris/iris-json-parser-java) 9 | 10 | ## Examples of use 11 | 12 | #### Deserialization 13 | 🔥 New feature (v0.5). Deserialization to objects. 14 | 15 | Full source code on [iris/json/test/serialization.kt](https://github.com/iris2iris/iris-json-parser-kotlin/blob/master/test/iris/json/test/serialization.kt) 16 | ````kotlin 17 | val item = JsonPlainParser.parse("""{"id": 3, 18 | |"person1": {"name": "Akbar", "age": 35, "cashAmount": 12200.12, "property": {"name": "Домик в деревне"}}, 19 | |"type": "MaleFirst", 20 | |"person2": {"name": "Alla Who", "height": 170, "income": 1214.81} 21 | |}""".trimMargin()) 22 | 23 | val user: User = item.asObject() 24 | println(user.person1) 25 | println(user.person2) 26 | ```` 27 | 28 | #### Flow pre-parse 29 | Interesting feature (v0.3). Flow preparing JSON-tree information only until required field. 30 | 31 | Useful when required fields are located at first part of JSON string. 32 | 33 | ```kotlin 34 | val testString = File("test.json").readText() 35 | 36 | // Demonstration of functional abilities 37 | val obj = JsonFlowParser.start(testString) // parsed to IrisJsonItem's 38 | 39 | // stringifies result objects 40 | println("IrisJsonItem.toString/JSON string: $obj") 41 | 42 | // stringifies objects to Appendable buffer 43 | val b = StringBuilder() 44 | obj.joinTo(b) 45 | println("IrisJsonItem.joinTo/JSON string: $b") 46 | 47 | // Simple access to required object on objects tree 48 | println("IrisJsonItem toString/JSON string: " + obj["object"]["message"]["attachments"][0]["wall"]["id"]) 49 | 50 | // Converting to required types 51 | println("To Long: " + obj["object"]["message"]["attachments"][0]["wall"]["id"].asLong()) 52 | 53 | // Access by string path 54 | println("To Int: " + obj.find("object message attachments 0 wall id").asInt()) 55 | 56 | // Stylized to Java/JavaScript properties access 57 | println("To Double: " + obj.find("object.message.attachments[0].wall.id").asDouble()) 58 | ``` 59 | 60 | #### Full pre-parse 61 | Prepares full JSON-tree information. Useful when lots of fields are requested. 62 | 63 | ```kotlin 64 | // Demonstration of functional abilities 65 | val res = JsonPlainParser.parse(testString) // parsed to IrisJsonItem's 66 | 67 | // stringifies result objects 68 | println("IrisJsonItem.toString/JSON string: $res") 69 | 70 | // stringifies objects to Appendable buffer 71 | val b = StringBuilder() 72 | res.joinTo(b) 73 | println("IrisJsonItem.joinTo/JSON string: $res") 74 | 75 | // Simple access to required object on objects tree 76 | println("IrisJsonItem toString/JSON string: " + res["object"]["message"]["attachments"][0]["wall"]["id"]) 77 | 78 | // Converting to required types 79 | println("To Long: " + res["object"]["message"]["attachments"][0]["wall"]["id"].asLong()) 80 | 81 | // Access by string path 82 | println("To Int: " + res.find("object message attachments 0 wall id").asInt()) 83 | 84 | // Stylized to Java/JavaScript properties access 85 | println("To Double: " + res.find("object.message.attachments[0].wall.id").asDouble()) 86 | ``` 87 | 88 | ## Performance test 89 | 90 | #### Array of 50 elements 91 | Test code is in [iris/json/test/performance_array.kt](https://github.com/iris2iris/iris-json-parser-kotlin/blob/master/test/iris/json/test/performance_array.kt) file. 92 | 93 | Test JSON file is in [test_array.json](https://github.com/iris2iris/iris-json-parser-kotlin/blob/master/test_array.json) file. 94 | 95 | Testing access to first element of array and access to last element of array. 100k iterations 96 | ``` 97 | AVG[0]: 98 | org.json: 22363 99 | org.json.simple: 27080 100 | Iris Plain: 5394 // previous 7110 101 | Iris Flow: 564 // previous 93 102 | Iris Proxy: 27 103 | POJO: 11 104 | 105 | AVG[49]: 106 | org.json: 22416 107 | org.json.simple: 26869 108 | Iris Plain: 5411 // previous 7067 109 | Iris Flow: 5870 // previous 7498 110 | Iris Proxy: 26 111 | POJO: 10 112 | ``` 113 | 114 | #### Complex json-tree structure 115 | 116 | Test code is in [iris/json/test/performance_object_tree.kt](https://github.com/iris2iris/iris-json-parser-kotlin/blob/master/test/iris/json/test/performance_object_tree.kt) file. 117 | 118 | Test JSON file is in [test.json](https://github.com/iris2iris/iris-json-parser-kotlin/blob/master/test.json) file. 119 | 120 | Testing access to `object.message.attachments[0].wall.id` and converting it to Long. 100k iterations 121 | ``` 122 | org.json: 9149 123 | org.json.simple: 11186 124 | Iris Plain: 2652 125 | Iris Flow: 617 126 | Iris Proxy: 53 127 | POJO: 21 128 | ``` 129 | 130 | Check out [CHANGELOG.md](https://github.com/iris2iris/iris-json-parser-kotlin/blob/master/CHANGELOG.md) 131 | 132 | ⭐ If this tool was useful for you, don't forget to give star. 133 | -------------------------------------------------------------------------------- /src/iris/json/serialization/DeserializerClassBuilder.kt: -------------------------------------------------------------------------------- 1 | package iris.json.serialization 2 | 3 | import iris.json.serialization.DeserializerClassImpl.* 4 | import kotlin.reflect.* 5 | import kotlin.reflect.full.findAnnotation 6 | import kotlin.reflect.full.memberProperties 7 | 8 | /** 9 | * @created 11.10.2020 10 | * @author [Ivan Ivanov](https://vk.com/irisism) 11 | */ 12 | object DeserializerClassBuilder { 13 | fun build(d: KClass<*>, targetClassImpl: DeserializerClassImpl = DeserializerClassImpl()): DeserializerClassImpl { 14 | var hasPolymorphies = false 15 | val constructorInfo = getBestConstructor(d) 16 | val (strategyFactory, constructorFields) = constructorInfo 17 | val mProperties = d.memberProperties 18 | val fieldsList = mProperties.associateTo(HashMap(mProperties.size)) { property -> 19 | val objectItemName = property.name 20 | val jsonItemName = property.findAnnotation()?.name 21 | ?.let { t -> if(t.isNotEmpty()) t else objectItemName } 22 | ?: objectItemName 23 | val p = getPropertyInfo(property, constructorFields[objectItemName]) 24 | if (!hasPolymorphies && p.polymorphInfo != null) 25 | hasPolymorphies = true 26 | jsonItemName to p 27 | } 28 | 29 | targetClassImpl.constructorStrategyFactory = strategyFactory 30 | targetClassImpl.fields = fieldsList 31 | targetClassImpl.hasPolymorphisms = hasPolymorphies 32 | return targetClassImpl 33 | } 34 | 35 | private fun getPropertyInfo(it: KProperty<*>, constructorParameter: KParameter?): DeserializerClassImpl.PropertyInfo { 36 | 37 | var type: DeserializerPrimitiveImpl? = null 38 | var inheritInfo: DeserializerClassImpl.PolymorphInfo? = null 39 | var innerClass: Deserializer? = null 40 | 41 | val data = it.findAnnotation() 42 | if (data != null) { // is polymorphic 43 | val cases = mutableMapOf() 44 | data.strings.associateTo(cases) { it.label to DeserializerFactory.getDeserializer(it.instance) } 45 | data.ints.associateTo(cases) { it.label to DeserializerFactory.getDeserializer(it.instance) } 46 | inheritInfo = DeserializerClassImpl.PolymorphInfo(data.sourceField, cases) 47 | } else { 48 | val tType = DeserializerPrimitiveImpl.convertType(it.returnType, null) 49 | if (tType != null) { // simple type int/string/boolean 50 | type = tType 51 | } else { 52 | innerClass = DeserializerFactory.getDeserializer(it.returnType) 53 | } 54 | } 55 | 56 | return PropertyInfo(/*it.name, */it, constructorParameter, type, innerClass, inheritInfo) 57 | } 58 | 59 | private fun getBestConstructor(d: KClass<*>): Pair> { 60 | return if (d.java.isAnnotationPresent(Metadata::class.java)) buildParametersByMetadata(d) else buildParametersInClassOrder(d) 61 | } 62 | 63 | private fun buildParametersByMetadata(d: KClass<*>): Pair> { 64 | val constructors = d.constructors 65 | if (constructors.isEmpty()) 66 | throw IllegalArgumentException("No any constructor for $d") 67 | var best: KFunction<*> = constructors.first() 68 | for (c in constructors) 69 | if (c.parameters.size > best.parameters.size) 70 | best = c 71 | val parameters = best.parameters 72 | return (if (parameters.isEmpty()) EmptyConstructorStrategyFactory(best) else MapConstructorStrategyFactory(best, parameters.size)) to parameters.associateBy { it.name!! } 73 | } 74 | 75 | private fun buildParametersInClassOrder(d: KClass<*>): Pair> { 76 | val targetConstructorSize = d.memberProperties.size 77 | val constructor = findZeroOrTargetConstructor(d, targetConstructorSize) 78 | val strategyFactory = if (constructor.parameters.isEmpty())EmptyConstructorStrategyFactory(constructor) else RawConstructorStrategyFactory(constructor, buildParams(constructor.parameters)) 79 | val map = constructor.parameters.associateBy { it.name!! } 80 | return strategyFactory to map 81 | } 82 | 83 | private fun buildParams(parameters: List): Array { 84 | return Array(parameters.size) { 85 | buildDefault(parameters[it]) 86 | } 87 | } 88 | 89 | private fun buildDefault(kParameter: KParameter): Any? { 90 | return when (val type = kParameter.type) { 91 | typeOf() -> if (type.isMarkedNullable) null else 0 92 | typeOf() -> if (type.isMarkedNullable) null else 0L 93 | typeOf() -> if (type.isMarkedNullable) null else 0.0 94 | typeOf() -> if (type.isMarkedNullable) null else 0f 95 | typeOf() -> if (type.isMarkedNullable) null else false 96 | else -> null 97 | } 98 | } 99 | 100 | private fun findZeroOrTargetConstructor(d: KClass<*>, targetConstructorSize: Int): KFunction<*> { 101 | val constructors = d.constructors 102 | if (constructors.isEmpty()) 103 | throw IllegalArgumentException("No any constructor for $d") 104 | for (c in constructors) { 105 | if (c.parameters.isEmpty()) 106 | return c 107 | if (c.parameters.size == targetConstructorSize) 108 | return c 109 | } 110 | throw IllegalArgumentException("No constructor for $d with $targetConstructorSize or no arguments size") 111 | } 112 | } -------------------------------------------------------------------------------- /test/iris/json/test/performance_array.kt: -------------------------------------------------------------------------------- 1 | package iris.json.test 2 | 3 | import iris.json.flow.JsonFlowParser 4 | import iris.json.flow.TokenerString 5 | import iris.json.plain.JsonPlainParser 6 | import iris.json.proxy.JsonProxyArray 7 | import org.json.JSONArray 8 | import org.json.simple.parser.JSONParser 9 | import java.io.File 10 | import kotlin.math.roundToInt 11 | 12 | fun main() { 13 | 14 | val testString = File("test_array.json").readText() 15 | val repeats = 100_000 16 | 17 | var totalIrisFlow = 0.0 18 | var totalIrisProxy = 0.0 19 | var totalIrisPlain = 0.0 20 | var totalJson = 0.0 21 | var totalSimpleJson = 0.0 22 | var totalPlainAccess = 0.0 23 | 24 | var totalIrisFlowLast = 0.0 25 | var totalIrisProxyLast = 0.0 26 | var totalIrisPlainLast = 0.0 27 | var totalJsonLast = 0.0 28 | var totalSimpleJsonLast = 0.0 29 | var totalPlainAccessLast = 0.0 30 | val map = JsonPlainParser(testString).parse().asList() 31 | 32 | // little warmup 33 | testJsonParser(testString, 0) 34 | testSimpleJsonParser(testString, 0) 35 | testIrisParserFlow(testString, 0) 36 | testIrisProxy(map, 0) 37 | testIrisParserPlain(testString, 0) 38 | testPlainAccess(map, 0) 39 | 40 | testJsonParser(testString, 49) 41 | testSimpleJsonParser(testString, 49) 42 | testIrisParserFlow(testString, 49) 43 | testIrisProxy(map, 49) 44 | testIrisParserPlain(testString, 49) 45 | testPlainAccess(map, 49) 46 | 47 | repeat(repeats) { 48 | //totalJson += testJsonParser(testString, 0) 49 | //totalSimpleJson += testSimpleJsonParser(testString, 0) 50 | totalIrisFlow += testIrisParserFlow(testString, 0) 51 | totalIrisPlain += testIrisParserPlain(testString, 0) 52 | totalIrisProxy += testIrisProxy(map, 0) 53 | totalPlainAccess += testPlainAccess(map, 0) 54 | 55 | //totalJsonLast += testJsonParser(testString, 49) 56 | //totalSimpleJsonLast += testSimpleJsonParser(testString, 49) 57 | totalIrisFlowLast += testIrisParserFlow(testString, 49) 58 | totalIrisPlainLast += testIrisParserPlain(testString, 49) 59 | totalIrisProxyLast += testIrisProxy(map, 49) 60 | totalPlainAccessLast += testPlainAccess(map, 49) 61 | if (it != 0 && it % 10_000 == 0) 62 | println("$it iteration") 63 | } 64 | 65 | totalIrisFlow /= 1000000.0 66 | totalIrisProxy /= 1000000.0 67 | totalIrisPlain /= 1000000.0 68 | totalJson /= 1000000.0 69 | totalSimpleJson /= 1000000.0 70 | totalPlainAccess /= 1000000.0 71 | println("AVG[0]:" + 72 | "\norg.json: ${totalJson.roundToInt()}" + 73 | "\norg.json.simple: ${totalSimpleJson.roundToInt()}" + 74 | "\nIris Plain: ${totalIrisPlain.roundToInt()}" + 75 | "\nIris Flow: ${totalIrisFlow.roundToInt()}" + 76 | "\nIris Proxy: ${totalIrisProxy.roundToInt()}" + 77 | "\nPOJO: ${totalPlainAccess.roundToInt()}" 78 | ) 79 | 80 | println() 81 | 82 | totalIrisFlowLast /= 1000000.0 83 | totalIrisPlainLast /= 1000000.0 84 | totalJsonLast /= 1000000.0 85 | totalSimpleJsonLast /= 1000000.0 86 | totalIrisProxyLast /= 1000000.0 87 | totalPlainAccessLast /= 1000000.0 88 | println("AVG[49]:" + 89 | "\norg.json: ${totalJsonLast.roundToInt()}" + 90 | "\norg.json.simple: ${totalSimpleJsonLast.roundToInt()}" + 91 | "\nIris Plain: ${totalIrisPlainLast.roundToInt()}" + 92 | "\nIris Flow: ${totalIrisFlowLast.roundToInt()}" + 93 | "\nIris Proxy: ${totalIrisProxyLast.roundToInt()}" + 94 | "\nPOJO: ${totalPlainAccessLast.roundToInt()}" 95 | ) 96 | } 97 | 98 | fun testPlainAccess(map: List, ind: Int): Long { 99 | val start = System.nanoTime() 100 | val d = (map[ind] as Map)["ID"] as String 101 | if (d == "1") // check for not to let compiler optimize code 102 | print("true") 103 | val end = System.nanoTime() 104 | return end - start 105 | } 106 | 107 | fun testIrisProxy(map: List, ind: Int): Long { 108 | val start = System.nanoTime() 109 | val rand = JsonProxyArray(map) 110 | val d = rand[ind]["ID"].asString() 111 | if (d == "1") // check for not to let compiler optimize code 112 | print("true") 113 | val end = System.nanoTime() 114 | return end - start 115 | } 116 | 117 | fun testIrisParserPlain(test: String, ind: Int): Long { 118 | val start = System.nanoTime() 119 | val rand = JsonPlainParser(test).parse() 120 | val d = rand[ind]["ID"].asString() 121 | if (d == "1") // check for not to let compiler optimize code 122 | print("true") 123 | val end = System.nanoTime() 124 | return end - start 125 | } 126 | 127 | fun testIrisParserFlow(test: String, ind: Int): Long { 128 | val start = System.nanoTime() 129 | val rand = JsonFlowParser.readItem(TokenerString(test)) 130 | val d = rand[ind]["ID"].asString() 131 | if (d == "1") // check for not to let compiler optimize code 132 | print("true") 133 | val end = System.nanoTime() 134 | return end - start 135 | } 136 | 137 | fun testJsonParser(test: String, ind: Int): Long { 138 | 139 | val start = System.nanoTime() 140 | val rand = JSONArray(test) 141 | val d = rand.getJSONObject(ind).getString("ID") 142 | if (d == "1") // check for not to let compiler optimize code 143 | print("true") 144 | val end = System.nanoTime() 145 | return end - start 146 | } 147 | fun testSimpleJsonParser(test: String, ind: Int): Long { 148 | val start = System.nanoTime() 149 | val rand = JSONParser().parse(test) 150 | val d = ((rand as List)[ind] as Map)["ID"] 151 | if (d == "1") // check for not to let compiler optimize code 152 | print("true") 153 | val end = System.nanoTime() 154 | return end - start 155 | } -------------------------------------------------------------------------------- /src/iris/sequence/CharArrayBuilder.kt: -------------------------------------------------------------------------------- 1 | package iris.sequence 2 | 3 | import java.io.File 4 | import kotlin.math.min 5 | import kotlin.math.round 6 | 7 | /** 8 | * @created 23.09.2020 9 | * @author [Ivan Ivanov](https://vk.com/irisism) 10 | */ 11 | class CharArrayBuilder(initialCapacity: Int = DEFAULT_CAPACITY) : Appendable, CharSequence, CharArraySource { 12 | 13 | companion object { 14 | const val DEFAULT_CAPACITY = 16 15 | } 16 | 17 | private var buffer = CharArray(initialCapacity) 18 | private var pointer = 0 19 | 20 | override val length: Int get() = pointer 21 | 22 | fun reset() { 23 | pointer = 0 24 | } 25 | 26 | override fun get(index: Int): Char { 27 | return buffer[index] 28 | } 29 | 30 | override fun subSequence(startIndex: Int, endIndex: Int): CharSequence { 31 | return IrisSequenceCharArray(buffer, startIndex, endIndex) 32 | } 33 | 34 | private fun ensureCapacityInternal(minimumCapacity: Int) { 35 | val oldCapacity: Int = buffer.size 36 | if (minimumCapacity > oldCapacity) { 37 | buffer = buffer.copyOf(newCapacity(minimumCapacity)) 38 | } 39 | } 40 | 41 | private fun newCapacity(minCapacity: Int): Int { 42 | val oldCapacity: Int = buffer.size 43 | var newCapacity = (oldCapacity shl 1) + 2 44 | if (newCapacity - minCapacity < 0) { 45 | newCapacity = minCapacity 46 | } 47 | return newCapacity 48 | } 49 | 50 | override fun append(csq: CharSequence): CharArrayBuilder { 51 | return append(csq, 0, csq.length) 52 | } 53 | 54 | override fun append(csq: CharSequence, start: Int, end: Int): CharArrayBuilder { 55 | val len = end - start 56 | ensureCapacityInternal(pointer + len) 57 | for (i in start until end) 58 | buffer[pointer++] = csq[i] 59 | return this 60 | } 61 | 62 | fun append(csq: String): CharArrayBuilder { 63 | val len = csq.length 64 | ensureCapacityInternal(pointer + len) 65 | csq.toCharArray(buffer, pointer) 66 | pointer += len 67 | return this 68 | } 69 | 70 | fun append(csq: String, start: Int = 0, end: Int = csq.length): CharArrayBuilder { 71 | val len = end - start 72 | ensureCapacityInternal(pointer + len) 73 | csq.toCharArray(buffer, pointer, start, end) 74 | pointer += len 75 | return this 76 | } 77 | 78 | override fun append(c: Char): CharArrayBuilder { 79 | ensureCapacityInternal(pointer + 1) 80 | buffer[pointer++] = c 81 | return this 82 | } 83 | 84 | fun append(arr: CharArray): CharArrayBuilder { 85 | val len = arr.size 86 | ensureCapacityInternal(pointer + len) 87 | arr.copyInto(buffer, pointer, 0, len) 88 | //System.arraycopy(arr, 0, buffer, pointer, len) 89 | pointer += len 90 | return this 91 | } 92 | 93 | fun append(arr: CharArray, start: Int = 0, end: Int = arr.size): CharArrayBuilder { 94 | val len = end - start 95 | ensureCapacityInternal(pointer + len) 96 | arr.copyInto(buffer, pointer, start, end) 97 | //System.arraycopy(arr, start, buffer, pointer, len) 98 | pointer += len 99 | return this 100 | } 101 | 102 | /*fun toCharArray(dest: CharArray = CharArray(length), start: Int = 0, len: Int = dest.size): CharArray { 103 | val realLen = min(len, length) 104 | if (realLen == 0) 105 | return CharArray(0) 106 | System.arraycopy(buffer, 0, dest, start, realLen) 107 | return dest 108 | }*/ 109 | 110 | override fun toCharArray(): CharArray { 111 | val len = length 112 | return toCharArray(CharArray(len), 0, 0, len) 113 | } 114 | 115 | override fun toCharArray(start: Int, len: Int): CharArray { 116 | return toCharArray(CharArray(len), 0, start, len) 117 | } 118 | 119 | override fun toCharArray(dest: CharArray): CharArray { 120 | val len = min(length, dest.size) 121 | return toCharArray(dest, 0, 0, len) 122 | } 123 | 124 | override fun toCharArray(dest: CharArray, destOffset: Int, start: Int, len: Int): CharArray { 125 | val realLen = min(len, length) 126 | if (realLen == 0) 127 | return CharArray(0) 128 | buffer.copyInto(dest, destOffset, start, start + realLen) 129 | //System.arraycopy(buffer, 0, dest, start, realLen) 130 | return dest 131 | } 132 | 133 | override fun toString(): String { 134 | return String(buffer, 0, pointer) 135 | } 136 | 137 | override fun equals(other: Any?): Boolean { 138 | if (this === other) return true 139 | if (other !is CharSequence) return false 140 | val len = length 141 | if (len != other.length) return false 142 | for (i in 0 until len) 143 | if (buffer[i] != other[i]) 144 | return false 145 | return true 146 | } 147 | 148 | override fun hashCode(): Int { 149 | val l = buffer.size 150 | return l + if (l > 0) get(0).toInt()*1023 else 0 151 | } 152 | } 153 | 154 | fun main() { 155 | var totalChar = 0.0 156 | var totalString = 0.0 157 | val repeats = 100_000 158 | repeat(repeats) { 159 | val str = testStringBuilder() 160 | val ch = testCharBuilder() 161 | totalChar += ch 162 | totalString += str 163 | } 164 | totalChar /= 1000000.0 165 | totalString /= 1000000.0 166 | val pct = round(totalString * 100.0 / totalChar - 100).toInt() 167 | println("AVG: String: ${totalString / repeats} Char: ${totalChar / repeats} DIFF: ${(totalString - totalChar)} (${if (pct >= 0) "+" else ""}$pct%)") 168 | } 169 | 170 | //val strings = (0..1000).map { "аааа|$it" } // strings 171 | //val strings = (0..1000).joinToString { "а|$it" }.chunked(1).map { it.first() } // char 172 | //val strings = (0..1000).joinToString { "а|$it" }.chunked(4*1024) // long strings 173 | //val strings = File("test_array.json").readText().chunked(4*1024) // long strings 174 | val strings = File("test_array.json").readText().chunked(4*1024).map { it.toCharArray() } // long chars 175 | 176 | fun testCharBuilder(): Long { 177 | val start = System.nanoTime() 178 | val sb = CharArrayBuilder() 179 | for (i in strings) 180 | sb.append(i) 181 | //val d = sb.toString() 182 | val end = System.nanoTime() 183 | /*if (d == "111") 184 | println("111")*/ 185 | return end - start 186 | } 187 | 188 | fun testStringBuilder(): Long { 189 | val start = System.nanoTime() 190 | val sb = StringBuilder() 191 | for (i in strings) 192 | sb.append(i) 193 | //val d = sb.toString() 194 | val end = System.nanoTime() 195 | /*if (d == "111") 196 | println("111")*/ 197 | return end - start 198 | } -------------------------------------------------------------------------------- /src/iris/json/serialization/DeserializerClassImpl.kt: -------------------------------------------------------------------------------- 1 | package iris.json.serialization 2 | 3 | import iris.json.JsonEntry 4 | import iris.json.JsonItem 5 | import iris.json.JsonObject 6 | import java.util.* 7 | import kotlin.collections.ArrayList 8 | import kotlin.collections.HashMap 9 | import kotlin.reflect.* 10 | 11 | /** 12 | * @created 08.10.2020 13 | * @author [Ivan Ivanov](https://vk.com/irisism) 14 | */ 15 | 16 | class DeserializerClassImpl : DeserializerClass { 17 | 18 | var hasPolymorphisms = false 19 | lateinit var fields: Map 20 | 21 | lateinit var constructorStrategyFactory: ConstructorStrategyFactory 22 | var conscructorParametersSize = 0 23 | 24 | class PolymorphInfo(val sourceField: String, val inheritClasses: Map) 25 | 26 | class PropertyInfo(/*val fieldName: String, */val property: KProperty<*>, val constructorParameter: KParameter? = null 27 | , val type: DeserializerPrimitiveImpl? 28 | , val customClass: Deserializer? = null 29 | , val polymorphInfo: PolymorphInfo? = null 30 | ) 31 | 32 | interface ConstructorStrategyFactory { 33 | fun getInstance(): ConstructorStrategy 34 | } 35 | 36 | interface ConstructorStrategy { 37 | fun setParameter(constructorParameter: KParameter, value: Any?) 38 | fun execute(): Any? 39 | } 40 | 41 | class EmptyConstructorStrategyFactory(private val constructorFunction: KFunction<*>) : ConstructorStrategyFactory, ConstructorStrategy { 42 | override fun getInstance() = this 43 | 44 | override fun setParameter(constructorParameter: KParameter, value: Any?) {} 45 | 46 | override fun execute(): Any? { 47 | return constructorFunction.call() 48 | } 49 | } 50 | 51 | class RawConstructorStrategyFactory(private val constructorFunction: KFunction<*>, private val params: Array) : ConstructorStrategyFactory { 52 | override fun getInstance(): ConstructorStrategy { 53 | return RawConstructorStrategy() 54 | } 55 | 56 | inner class RawConstructorStrategy : ConstructorStrategy { 57 | 58 | private val cParams = params.copyOf() 59 | 60 | override fun setParameter(constructorParameter: KParameter, value: Any?) { 61 | cParams[constructorParameter.index] = value 62 | } 63 | 64 | override fun execute(): Any? { 65 | return constructorFunction.call(*cParams) 66 | } 67 | } 68 | } 69 | 70 | class MapConstructorStrategyFactory(private val constructorFunction: KFunction<*>, private val constructorParametersSize: Int) : ConstructorStrategyFactory { 71 | override fun getInstance(): ConstructorStrategy { 72 | return MapConstructorStrategy() 73 | } 74 | 75 | inner class MapConstructorStrategy : ConstructorStrategy { 76 | 77 | private val map = HashMap(constructorParametersSize) 78 | 79 | override fun setParameter(constructorParameter: KParameter, value: Any?) { 80 | map[constructorParameter] = value 81 | } 82 | 83 | override fun execute(): Any? { 84 | return constructorFunction.callBy(map) 85 | } 86 | } 87 | } 88 | 89 | 90 | 91 | 92 | class SetterPropertyException(message: String, cause: Throwable) : Throwable(message, cause) 93 | 94 | override fun deserialize(item: JsonItem): T { 95 | return getObject((item as JsonObject).getEntries()) 96 | } 97 | 98 | override fun forSubclass(d: KClass<*>): Deserializer { 99 | return this 100 | } 101 | 102 | override fun getObject(entries: Collection): T { 103 | val info = this 104 | val fields = info.fields 105 | val hasPolymorphisms = info.hasPolymorphisms 106 | val delayedInit: MutableMap? 107 | val result: MutableMap? 108 | if (hasPolymorphisms) { 109 | delayedInit = mutableMapOf() 110 | result = mutableMapOf() 111 | } else { 112 | delayedInit = null 113 | result = null 114 | } 115 | 116 | //val conscructorParametersSize = constructorParametersList.size 117 | val otherSize = fields.size - conscructorParametersSize 118 | //val constructorMap = HashMap(conscructorParametersSize) 119 | //val constructorList = constructorParametersList.copyOf() 120 | 121 | val constructorStrategy = constructorStrategyFactory.getInstance() 122 | 123 | val otherFields = (if (otherSize == 0) 124 | null 125 | else 126 | ArrayList, Any?>>(fields.size - conscructorParametersSize)) 127 | 128 | for ((key, jsonItem) in entries) { 129 | val field = key.toString() 130 | val param = fields[field]?: continue 131 | val polymorphInfo = param.polymorphInfo 132 | if (polymorphInfo != null) { 133 | val sourceValue = result!![polymorphInfo.sourceField] 134 | if (sourceValue != null) { // already know what type is it 135 | val inherit = polymorphInfo.inheritClasses[sourceValue]!! 136 | val newValue: Any? = inherit.deserialize(jsonItem) 137 | result[field] = newValue 138 | param.constructorParameter?.let { constructorStrategy.setParameter(it, newValue)/*constructorList[it.order] = newValue*/ } 139 | ?: run { otherFields!! += param.property to newValue } 140 | 141 | } else { // need delay initialization until we know source info 142 | val item = delayedInit!![polymorphInfo.sourceField] 143 | if (item == null) 144 | delayedInit[polymorphInfo.sourceField] = Delayed(Delayed.Data(param, jsonItem)) 145 | else 146 | item.add(Delayed.Data(param, jsonItem)) 147 | } 148 | } else { 149 | val value = getValue(jsonItem, param) 150 | if (delayedInit != null) { 151 | val delayed = delayedInit[field] 152 | if (delayed != null) { // yes! at last we have delayed information! 153 | val item = delayed.firstItem 154 | val property = item.propertyInfo 155 | val inherit = property.polymorphInfo!!.inheritClasses[value]!! 156 | val newValue: Any? = inherit.deserialize(item.json) 157 | item.propertyInfo.constructorParameter?.let { constructorStrategy.setParameter(it, newValue)/* constructorList[it.order] = newValue*/ } 158 | ?: run { otherFields!! += property.property to newValue } 159 | 160 | if (delayed.items != null) { 161 | for (item in delayed.items!!) { 162 | val property = item.propertyInfo 163 | val inherit = property.polymorphInfo!!.inheritClasses[value]!! 164 | val newValue: Any = inherit.deserialize(item.json) 165 | item.propertyInfo.constructorParameter?.let { constructorStrategy.setParameter(it, newValue) /*constructorList[it.order] = newValue*/ } 166 | ?: run { otherFields!! += property.property to newValue } 167 | } 168 | } 169 | } 170 | result!![field] = value 171 | } 172 | 173 | param.constructorParameter?.let { constructorStrategy.setParameter(it, value)/* constructorList[it.order] = value*/ } 174 | ?: run{ otherFields!! += param.property to value } 175 | } 176 | } 177 | //val item = info.constructorFunction.call(constructorList) as T 178 | val item = constructorStrategy.execute() as T 179 | if (otherFields != null) { 180 | for (field in otherFields) { 181 | try { 182 | (field.first as KMutableProperty<*>).setter.call(item, field.second) 183 | } catch (e: ClassCastException) { 184 | throw SetterPropertyException("Property $field does not have available setter", e) 185 | } 186 | } 187 | } 188 | return item 189 | } 190 | 191 | private class Delayed(val firstItem: Data) { 192 | class Data(val propertyInfo: PropertyInfo, val json: JsonItem) 193 | 194 | var items: MutableList? = null 195 | fun add(item: Data) { 196 | if (items == null) 197 | items = LinkedList() 198 | items!! += item 199 | } 200 | } 201 | 202 | private fun getValue(value: JsonItem, property: PropertyInfo): Any? { 203 | property.type?.let { 204 | return it.getValue(value) 205 | } 206 | 207 | property.customClass?.let { 208 | return it.deserialize(value) 209 | } 210 | throw IllegalStateException("How we got here?") 211 | } 212 | } -------------------------------------------------------------------------------- /test/iris/json/flow/serialization.kt: -------------------------------------------------------------------------------- 1 | package iris.json.flow 2 | 3 | import iris.json.JsonItem 4 | import iris.json.asObject 5 | import iris.json.serialization.* 6 | import kotlin.reflect.KClass 7 | 8 | /** 9 | * @created 08.10.2020 10 | * @author [Ivan Ivanov](https://vk.com/irisism) 11 | */ 12 | class User(val id: Int, type: String) { 13 | 14 | val type = type 15 | 16 | @PolymorphData( 17 | sourceField = "type" 18 | , strings = [PolymorphCaseString(label = "MaleFirst", instance = Male::class), PolymorphCaseString(label = "FemaleFirst", instance = Female::class)] 19 | ) 20 | var person1: Human? = null 21 | 22 | @PolymorphData( 23 | sourceField = "type" 24 | , strings = [PolymorphCaseString(label = "FemaleFirst", instance = Male::class), PolymorphCaseString(label = "MaleFirst", instance = Female::class)] 25 | ) 26 | var person2: Human? = null 27 | } 28 | 29 | open class Human 30 | data class Male(val name: String, val age: Int = 1000, val cashAmount: Double = 1333.0, val property: Property? = null): Human() 31 | data class Female(val name: String, val height: Int, val income: Double, val property: Property? = null): Human() 32 | 33 | data class Property(val name: String) 34 | 35 | data class ListedClass(val listed: List) 36 | data class ListedInt(val listed: List) 37 | data class ListedListInt(val listed: List>) 38 | 39 | data class DefinedJsonField( 40 | @JsonField(name = "object") 41 | val obj: String 42 | ) 43 | 44 | fun main() { 45 | println("*** Iris Json Flow test ***\n") 46 | 47 | testUser(); println() 48 | testListInt(); println() 49 | testListListInt(); println() 50 | testListedData(); println() 51 | testPureList(); println() 52 | testPrimitive(); println() 53 | testMap(); println() 54 | testJsonItem(); println() 55 | testDefinedJsonField(); println() 56 | testRegisteredDeserializer(); println() 57 | testQuotelessFieldNames(); println() 58 | testSubclassRegister(); println() 59 | testRecursiveClassFields(); println() 60 | testRecursiveGenericClassFields(); println() 61 | } 62 | 63 | fun createJsonItem(text: String): JsonItem { 64 | return JsonFlowParser.start(text) 65 | } 66 | 67 | data class RecursiveGenericClass ( 68 | var inner: List? = null, 69 | var someData: String? = null 70 | ) 71 | 72 | fun testRecursiveGenericClassFields() { 73 | println("testRecursiveGenericClassFields:") 74 | val item = createJsonItem("""{ 75 | inner: [{inner: [{someData: "3 level"}], someData: "2 level"}], someData: "1 level" 76 | }""".trimMargin()) 77 | val list = item.asObject() 78 | println(list) 79 | } 80 | 81 | data class RecursiveClass ( 82 | var inner: RecursiveClass? = null, 83 | var someData: String? = null 84 | ) 85 | 86 | fun testRecursiveClassFields() { 87 | println("testRecursiveClassFields:") 88 | val item = createJsonItem("""{ 89 | inner: {inner: {someData: "3 level"}, someData: "2 level"}, someData: "1 level" 90 | }""".trimMargin()) 91 | val list = item.asObject() 92 | println(list) 93 | } 94 | 95 | fun testSubclassRegister() { 96 | println("testSubclassRegister:") 97 | 98 | DeserializerFactory.registerDeserializer(iris.json.plain.Human::class, object : Deserializer { 99 | 100 | private val maleDeserializer = DeserializerFactory.getDeserializer(iris.json.plain.Male::class, false) 101 | private val femaleDeserializer = DeserializerFactory.getDeserializer(iris.json.plain.Female::class, false) 102 | 103 | override fun deserialize(item: JsonItem): T { 104 | val deserializer = when (item["type"].asString()) { 105 | "male" -> maleDeserializer 106 | "female" -> femaleDeserializer 107 | else -> throw IllegalArgumentException("There are only 2 genders") 108 | } 109 | return deserializer.deserialize(item) 110 | } 111 | 112 | override fun forSubclass(d: KClass<*>): Deserializer { 113 | return this 114 | } 115 | }) 116 | 117 | val item = JsonFlowParser.start("""{ 118 | |person1: {"type": "male", name: "Akbar", age: 35, "cashAmount": 12200.12, "property": {"name": "Домик в деревне"}}, 119 | |"person2": {"type": "female","name": "Alla Who", "height": 170, "income": 1214.81} 120 | |}""".trimMargin()) 121 | val list = item.asObject>() 122 | println(list) 123 | } 124 | 125 | fun testQuotelessFieldNames() { 126 | println("testQuotelessFieldNames:") 127 | val item = JsonFlowParser.start("""{ 128 | |person1: {name: "Akbar", age: 35, "cashAmount": 12200.12, "property": {"name": "Домик в деревне"}}, 129 | |person2: {name: "Alla Who", age: 15, "cashAmount": 23232.12, "property": {"name": "В центре высотка"}} 130 | |}""".trimMargin()) 131 | val list = item.asObject>() 132 | println(list) 133 | } 134 | 135 | class EnumDeserializer : DeserializerPrimitive { 136 | 137 | enum class YesNoEnum(val value: String) { 138 | Yes("yes"), No("no") 139 | } 140 | 141 | override fun getValue(item: JsonItem): Any? { 142 | return deserialize(item) 143 | } 144 | 145 | override fun deserialize(item: JsonItem): T { 146 | return when(item.asString()) { 147 | "yes" -> YesNoEnum.Yes 148 | "no" -> YesNoEnum.No 149 | else -> null 150 | } as T 151 | } 152 | 153 | override fun forSubclass(d: KClass<*>): Deserializer { 154 | return this 155 | } 156 | } 157 | 158 | fun testRegisteredDeserializer() { 159 | println("testRegisteredDeserializer:") 160 | DeserializerFactory.registerDeserializer(EnumDeserializer.YesNoEnum::class, EnumDeserializer()) 161 | val item = JsonFlowParser.start(""" 162 | { 163 | "drunk": "yes", 164 | "enough": "no" 165 | }""".trimMargin()) 166 | val list = item.asObject>() 167 | println(list) 168 | } 169 | 170 | fun testDefinedJsonField() { 171 | println("testDefinedJsonField:") 172 | val item = JsonFlowParser.start("""{ 173 | "object": "Other field name" 174 | }""".trimMargin()) 175 | 176 | val list = item.asObject() 177 | println(list) 178 | } 179 | 180 | fun testJsonItem() { 181 | println("testJsonItem:") 182 | val item = JsonFlowParser.start("""{ 183 | |"person1": {"name": "Akbar", "age": 35, "cashAmount": 12200.12, "property": {"name": "Домик в деревне"}}, 184 | |"person2": {"name": "Alla Who", "age": 15, "cashAmount": 23232.12, "property": {"name": "В центре высотка"}} 185 | |}""".trimMargin()) 186 | val list = item.asObject>() 187 | println(list) 188 | } 189 | 190 | fun testMap() { 191 | println("testMap:") 192 | val item = JsonFlowParser.start("""{ 193 | |"person1": {"name": "Akbar", "age": 35, "cashAmount": 12200.12, "property": {"name": "Домик в деревне"}}, 194 | |"person2": {"name": "Alla Who", "age": 15, "cashAmount": 23232.12, "property": {"name": "В центре высотка"}} 195 | |}""".trimMargin()) 196 | val list = item.asObject>() 197 | println(list) 198 | } 199 | 200 | fun testPrimitive() { 201 | println("testPrimitive:") 202 | val item = JsonFlowParser.start("""1""".trimMargin()) 203 | val list = item.asObject() 204 | println(list) 205 | } 206 | 207 | fun testPureList() { 208 | println("testPureList:") 209 | val item = JsonFlowParser.start("""[ 210 | |1,2,3,4,5 211 | |,6,7,8,9,10 212 | |]""".trimMargin()) 213 | val list = item.asObject>() 214 | println(list) 215 | } 216 | 217 | fun testListListInt() { 218 | println("testListListInt:") 219 | val item = JsonFlowParser.start("""{ 220 | |"listed":[ 221 | |[1,2,3,4,5] 222 | |,[6,7,8,9,10] 223 | |]}""".trimMargin()) 224 | val list = item.asObject() 225 | println(list) 226 | } 227 | 228 | fun testListInt() { 229 | println("testListInt:") 230 | val item = JsonFlowParser.start("""{ 231 | |"listed":[1,2,3,4,5]}""".trimMargin()) 232 | val list = item.asObject() 233 | println(list) 234 | } 235 | 236 | fun testListedData() { 237 | println("testListedData:") 238 | val item = JsonFlowParser.start("""{ 239 | |"listed":[ 240 | |{"name": "Alla Who", "age": 35, "cashAmount": 122.12121, "property": {"name": "Домик в деревне"}} 241 | |, {"name": "Akbar", "age": 46, "cashAmount": 44.3, "property": {"name": "Деревня"}} 242 | |]}""".trimMargin()) 243 | val list = item.asObject() 244 | println(list) 245 | } 246 | 247 | fun testUser() { 248 | println("testUser:") 249 | val item = JsonFlowParser.start("""{"id": 3, 250 | |"person1": {"name": "Akbar", "age": 35, "cashAmount": 12200.12, "property": {"name": "Домик в деревне"}}, 251 | |"type": "MaleFirst", 252 | |"person2": {"name": "Alla Who", "height": 170, "income": 1214.81} 253 | |}""".trimMargin()) 254 | 255 | val user = item.asObject() 256 | println(user.person1) 257 | println(user.person2) 258 | 259 | } -------------------------------------------------------------------------------- /src/iris/json/plain/JsonPlainParser.kt: -------------------------------------------------------------------------------- 1 | package iris.json.plain 2 | 3 | import iris.json.Configuration 4 | import iris.json.JsonEntry 5 | import iris.json.Util 6 | import iris.json.flow.TokenerString 7 | import iris.sequence.IrisSubSequence 8 | import java.io.* 9 | import java.net.URL 10 | import java.util.* 11 | import kotlin.math.max 12 | import kotlin.math.min 13 | 14 | /** 15 | * @created 14.04.2020 16 | * @author [Ivan Ivanov](https://vk.com/irisism) 17 | */ 18 | class JsonPlainParser(source: String, private val configuration: Configuration = Configuration.globalConfiguration) { 19 | 20 | companion object { 21 | 22 | fun parse(url: URL, configuration: Configuration = Configuration.globalConfiguration) = 23 | parse(url.openStream(), configuration) 24 | 25 | fun parse(ins: InputStream, configuration: Configuration = Configuration.globalConfiguration) = 26 | parse(reader(InputStreamReader(ins)), configuration) 27 | 28 | fun parse(reader: Reader, configuration: Configuration = Configuration.globalConfiguration) = 29 | parse(reader(reader), configuration) 30 | 31 | fun parse(file: File, configuration: Configuration = Configuration.globalConfiguration) = 32 | parse(reader(FileReader(file)), configuration) 33 | 34 | private fun reader(reader: Reader) = reader.use { it.readText() } 35 | 36 | fun parse(source: String, configuration: Configuration = Configuration.globalConfiguration): IrisJsonItem { 37 | return JsonPlainParser(source, configuration).readItem() 38 | } 39 | } 40 | 41 | private var pointer = 0 42 | //private val source: CharArray = source.toCharArray() 43 | private val source = source 44 | 45 | fun parse(): IrisJsonItem { 46 | return readItem() 47 | } 48 | 49 | private fun readItem(): IrisJsonItem { 50 | skipWhitespaces() 51 | val char = source[pointer++] 52 | val type = when { 53 | Util.isDigitOrAlpha(char) || char == '-' -> Util.Type.Value 54 | char == '{' -> Util.Type.Object 55 | char == '[' -> Util.Type.Array 56 | char == '"' || char == '\'' -> Util.Type.String 57 | else -> throw parseException("Unexpected character \"$char\" in object type detection state") 58 | } 59 | 60 | when (type) { 61 | Util.Type.Value -> { // примитивы 62 | pointer-- 63 | val start = pointer 64 | val value = readPrimitive() 65 | val end = pointer 66 | return IrisJsonValue(IrisSubSequence(source, start, end), value) 67 | } 68 | Util.Type.Object -> { 69 | return readObject() 70 | } 71 | Util.Type.String -> { 72 | val start = pointer 73 | /*val escapes = */readString(char) 74 | val end = pointer - 1 75 | return IrisJsonString(IrisSubSequence(source, start, end)/*, escapes*/) 76 | } 77 | Util.Type.Array -> { 78 | return readArray() 79 | } 80 | else -> TODO("$type not realised yet $pointer\n" + getPlace()) 81 | } 82 | } 83 | 84 | private fun getPlace(): String { 85 | return '"' + source.substring(max(0, pointer - 10), min(pointer + 10, source.length - 1)) + '"' 86 | } 87 | 88 | private fun readObject(): IrisJsonObject { 89 | 90 | val key = readKey() ?: return IrisJsonObject(ArrayList(0), configuration) 91 | val value = readValue() 92 | 93 | val key2 = readKey() ?: return IrisJsonObject(ArrayList(1).also { it += key to value }, configuration) 94 | val value2 = readValue() 95 | 96 | val key3 = readKey() ?: return IrisJsonObject(ArrayList(2).also { it += key to value; it += key2 to value2 }, configuration) 97 | val value3 = readValue() 98 | 99 | val entries = LinkedList() 100 | entries += key to value 101 | entries += key2 to value2 102 | entries += key3 to value3 103 | 104 | val len = source.length 105 | var char: Char 106 | do { 107 | skipWhitespaces() 108 | // "id" : ... 109 | char = source[pointer++] 110 | if (char == '}') 111 | break 112 | if (char == ',') { 113 | skipWhitespaces() 114 | char = source[pointer++] 115 | } 116 | if (!(char == '"' || char == '\'')) 117 | throw IllegalArgumentException("\" (quote) or \"'\" was expected in position $pointer\n" + getPlace()) 118 | 119 | val start = pointer 120 | // ключ 121 | readString(char) 122 | val end = pointer - 1 123 | val key = IrisSubSequence(source, start, end) 124 | skipWhitespaces() 125 | char = source[pointer++] 126 | if (char != ':') 127 | throw IllegalArgumentException("\":\" was expected in position $pointer\n" + getPlace()) 128 | skipWhitespaces() 129 | val value = readItem() 130 | entries += key to value 131 | //counter-- 132 | } while (pointer < len) 133 | return IrisJsonObject(entries, configuration) 134 | } 135 | 136 | private fun readValue(): IrisJsonItem { 137 | skipWhitespaces() 138 | val char = source[pointer++] 139 | if (char != ':') 140 | throw IllegalArgumentException("\":\" was expected in position $pointer\n" + getPlace()) 141 | skipWhitespaces() 142 | return readItem() 143 | } 144 | 145 | private fun readKey(): CharSequence? { 146 | skipWhitespaces() 147 | // "id" : ... 148 | var char = source[pointer++] 149 | if (char == '}') 150 | return null 151 | if (char == ',') { 152 | skipWhitespaces() 153 | char = source[pointer++] 154 | } 155 | if (!(char == '"' || char == '\'')) { 156 | if (char in 'a'..'z' || char in 'A'..'Z') { 157 | val start = pointer - 1 158 | // ключ 159 | readPrimitive() 160 | val end = pointer 161 | return IrisSubSequence(source, start, end) 162 | } else 163 | throw IllegalArgumentException("\" (quote) or \"'\" was expected in position $pointer\n" + getPlace()) 164 | } else { 165 | val start = pointer 166 | // ключ 167 | readString(char) 168 | val end = pointer - 1 169 | return IrisSubSequence(source, start, end) 170 | } 171 | } 172 | 173 | private fun readArray(): IrisJsonArray { 174 | 175 | skipWhitespaces() 176 | var char = source[pointer] 177 | if (char == ']') { 178 | pointer++ 179 | return IrisJsonArray(emptyList()) 180 | } 181 | 182 | val entries = mutableListOf() 183 | val len = source.length 184 | val source = source 185 | do { 186 | val value = readItem() 187 | entries += value 188 | 189 | skipWhitespaces() 190 | char = source[pointer] 191 | if (char == ']') { 192 | pointer++ 193 | break 194 | } 195 | if (char != ',') { 196 | throw parseException(""""," was excepted, but "$char" found""") 197 | } 198 | pointer++ 199 | skipWhitespaces() 200 | 201 | char = source[pointer] 202 | if (char == ']') { 203 | if (!configuration.trailingCommaAllowed) 204 | throw parseException("Trailing commas are not allowed in current configuration settings. ") 205 | pointer++ 206 | break 207 | } 208 | } while (pointer < len) 209 | 210 | return IrisJsonArray(entries) 211 | } 212 | 213 | private fun skipWhitespaces() { 214 | val len = source.length 215 | do { 216 | val char = source[pointer].toInt() 217 | if (char > TokenerString.SPACE) 218 | break 219 | if (!(char == TokenerString.SPACE || char == TokenerString.TAB || char == TokenerString.LF || char == TokenerString.CR)) 220 | break 221 | pointer++ 222 | } while (pointer < len) 223 | } 224 | 225 | private fun readString(quote: Char)/*: ArrayList?*/ { 226 | var escaping = false 227 | val len = source.length 228 | var char: Char 229 | //var escapes: ArrayList? = null 230 | var pointer = this.pointer 231 | val offset = pointer + 1 // "+1" is correction because of pointer++ 232 | do { 233 | char = source[pointer++] 234 | if (escaping) { 235 | escaping = false 236 | } else if (char == '\\') { 237 | escaping = true 238 | /*if (escapes == null) escapes = ArrayList() 239 | escapes += pointer - offset*/ 240 | }else if (char == quote) { 241 | break 242 | } 243 | } while (pointer < len) 244 | this.pointer = pointer 245 | //return escapes 246 | } 247 | 248 | private fun readPrimitive(): Util.ValueType { 249 | var curType = Util.ValueType.Integer 250 | val first = pointer 251 | val len = source.length 252 | do { 253 | val char = source[pointer] 254 | when { 255 | Util.isDigit(char) -> {} 256 | char == '-' -> if (first != pointer) curType = Util.ValueType.Constant 257 | char == '.' -> if (curType == Util.ValueType.Integer) curType = Util.ValueType.Float 258 | Util.isAlpha(char) -> curType = Util.ValueType.Constant 259 | else -> break 260 | } 261 | pointer++ 262 | } while (pointer < len) 263 | return curType 264 | } 265 | 266 | fun parseException(message: String): IllegalStateException { 267 | return IllegalStateException("$message\nAt position $pointer: " + source.subSequence(max(0, pointer - 10), min(pointer + 10, source.length - 1))) 268 | } 269 | } -------------------------------------------------------------------------------- /test/iris/json/plain/serialization.kt: -------------------------------------------------------------------------------- 1 | package iris.json.plain 2 | 3 | import iris.json.JsonItem 4 | import iris.json.PureJavaUser 5 | import iris.json.asObject 6 | import iris.json.serialization.* 7 | import kotlin.reflect.KClass 8 | 9 | /** 10 | * @created 08.10.2020 11 | * @author [Ivan Ivanov](https://vk.com/irisism) 12 | */ 13 | class User(val id: Int, type: String) { 14 | 15 | val type = type 16 | 17 | @PolymorphData( 18 | sourceField = "type" 19 | , strings = [PolymorphCaseString(label = "MaleFirst", instance = Male::class), PolymorphCaseString(label = "FemaleFirst", instance = Female::class)] 20 | ) 21 | var person1: Human? = null 22 | 23 | @PolymorphData( 24 | sourceField = "type" 25 | , strings = [PolymorphCaseString(label = "FemaleFirst", instance = Male::class), PolymorphCaseString(label = "MaleFirst", instance = Female::class)] 26 | ) 27 | var person2: Human? = null 28 | } 29 | 30 | open class Human 31 | data class Male(val name: String, val age: Int = 1000, val cashAmount: Double = 1333.0, val property: Property? = null): Human() 32 | data class Female(val name: String, val height: Int, val income: Double, val property: Property? = null): Human() 33 | 34 | data class Property(val name: String) 35 | 36 | data class ListedClass(val listed: List) 37 | data class ListedInt(val listed: List) 38 | data class ListedListInt(val listed: List>) 39 | 40 | data class DefinedJsonField( 41 | @JsonField(name = "object") 42 | val obj: String 43 | ) 44 | 45 | fun main() { 46 | println("*** Iris Json Plain test ***\n") 47 | 48 | testUser(); println() 49 | testPureJavaUser(); println() 50 | testListInt(); println() 51 | testListListInt(); println() 52 | testListedData(); println() 53 | testPureList(); println() 54 | testPrimitive(); println() 55 | testMap(); println() 56 | testJsonItem(); println() 57 | testDefinedJsonField(); println() 58 | testRegisteredDeserializer(); println() 59 | testQuotelessFieldNames(); println() 60 | testSubclassRegister(); println() 61 | testRecursiveClassFields(); println() 62 | testRecursiveGenericClassFields(); println() 63 | } 64 | 65 | fun testPureJavaUser() { 66 | println("testPureJavaUser:") 67 | val item = createJsonItem("""{"id": 3, 68 | |"person1": {"name": "Akbar", "age": 35, "cashAmount": 12200.12, "property": {"name": "Домик в деревне"}}, 69 | |"type": "MaleFirst", 70 | |"person2": {"name": "Alla Who", "height": 170, "income": 1214.81} 71 | |}""".trimMargin()) 72 | 73 | val user = item.asObject() 74 | println(user.person1) 75 | println(user.person2) 76 | } 77 | 78 | fun createJsonItem(text: String): JsonItem { 79 | val parser = JsonPlainParser(text) 80 | return parser.parse() 81 | } 82 | 83 | data class RecursiveGenericClass ( 84 | var inner: List? = null, 85 | var someData: String? = null 86 | ) 87 | 88 | fun testRecursiveGenericClassFields() { 89 | println("testRecursiveGenericClassFields:") 90 | val item = createJsonItem("""{ 91 | inner: [{inner: [{someData: "3 level"}], someData: "2 level"}], someData: "1 level" 92 | }""".trimMargin()) 93 | val list = item.asObject() 94 | println(list) 95 | } 96 | 97 | data class RecursiveClass ( 98 | var inner: RecursiveClass? = null, 99 | var someData: String? = null 100 | ) 101 | 102 | fun testRecursiveClassFields() { 103 | println("testRecursiveClassFields:") 104 | val item = createJsonItem("""{ 105 | inner: {inner: {someData: "3 level"}, someData: "2 level"}, someData: "1 level" 106 | }""".trimMargin()) 107 | val list = item.asObject() 108 | println(list) 109 | } 110 | 111 | fun testSubclassRegister() { 112 | println("testSubclassRegister:") 113 | 114 | DeserializerFactory.registerDeserializer(Human::class, object : Deserializer { 115 | 116 | private val maleDeserializer = DeserializerFactory.getDeserializer(Male::class, false) 117 | private val femaleDeserializer = DeserializerFactory.getDeserializer(Female::class, false) 118 | 119 | override fun deserialize(item: JsonItem): T { 120 | val deserializer = when (item["type"].asString()) { 121 | "male" -> maleDeserializer 122 | "female" -> femaleDeserializer 123 | else -> throw IllegalArgumentException("There are only 2 genders") 124 | } 125 | return deserializer.deserialize(item) 126 | } 127 | 128 | override fun forSubclass(d: KClass<*>): Deserializer { 129 | return this 130 | } 131 | }) 132 | 133 | val item = createJsonItem("""{ 134 | |person1: {"type": "male", name: "Akbar", age: 35, "cashAmount": 12200.12, "property": {"name": "Домик в деревне"}}, 135 | |"person2": {"type": "female","name": "Alla Who", "height": 170, "income": 1214.81} 136 | |}""".trimMargin()) 137 | val list = item.asObject>() 138 | println(list) 139 | } 140 | 141 | fun testQuotelessFieldNames() { 142 | println("testQuotelessFieldNames:") 143 | val parser = JsonPlainParser("""{ 144 | |person1: {name: "Akbar", age: 35, "cashAmount": 12200.12, "property": {"name": "Домик в деревне"}}, 145 | |person2: {name: "Alla Who", age: 15, "cashAmount": 23232.12, "property": {"name": "В центре высотка"}} 146 | |}""".trimMargin()) 147 | val item = parser.parse() 148 | val list = item.asObject>() 149 | println(list) 150 | } 151 | 152 | class EnumDeserializer : DeserializerPrimitive { 153 | 154 | enum class YesNoEnum(val value: String) { 155 | Yes("yes"), No("no") 156 | } 157 | 158 | override fun getValue(item: JsonItem): Any? { 159 | return deserialize(item) 160 | } 161 | 162 | override fun deserialize(item: JsonItem): T { 163 | return when(item.asString()) { 164 | "yes" -> YesNoEnum.Yes 165 | "no" -> YesNoEnum.No 166 | else -> null 167 | } as T 168 | } 169 | 170 | override fun forSubclass(d: KClass<*>): Deserializer { 171 | return this 172 | } 173 | } 174 | 175 | fun testRegisteredDeserializer() { 176 | println("testRegisteredDeserializer:") 177 | DeserializerFactory.registerDeserializer(EnumDeserializer.YesNoEnum::class, EnumDeserializer()) 178 | val parser = JsonPlainParser(""" 179 | { 180 | "drunk": "yes", 181 | "enough": "no" 182 | }""".trimMargin()) 183 | val item = parser.parse() 184 | val list = item.asObject>() 185 | println(list) 186 | } 187 | 188 | fun testDefinedJsonField() { 189 | println("testDefinedJsonField:") 190 | val parser = JsonPlainParser("""{ 191 | "object": "Other field name" 192 | }""".trimMargin()) 193 | val item = parser.parse() 194 | val list = item.asObject() 195 | println(list) 196 | } 197 | 198 | fun testJsonItem() { 199 | println("testJsonItem:") 200 | val parser = JsonPlainParser("""{ 201 | |"person1": {"name": "Akbar", "age": 35, "cashAmount": 12200.12, "property": {"name": "Домик в деревне"}}, 202 | |"person2": {"name": "Alla Who", "age": 15, "cashAmount": 23232.12, "property": {"name": "В центре высотка"}} 203 | |}""".trimMargin()) 204 | val item = parser.parse() 205 | val list = item.asObject>() 206 | println(list) 207 | } 208 | 209 | fun testMap() { 210 | println("testMap:") 211 | val parser = JsonPlainParser("""{ 212 | |"person1": {"name": "Akbar", "age": 35, "cashAmount": 12200.12, "property": {"name": "Домик в деревне"}}, 213 | |"person2": {"name": "Alla Who", "age": 15, "cashAmount": 23232.12, "property": {"name": "В центре высотка"}} 214 | |}""".trimMargin()) 215 | val item = parser.parse() 216 | val list = item.asObject>() 217 | println(list) 218 | } 219 | 220 | fun testPrimitive() { 221 | println("testPrimitive:") 222 | val parser = JsonPlainParser("""1""".trimMargin()) 223 | val item = parser.parse() 224 | val list = item.asObject() 225 | println(list) 226 | } 227 | 228 | fun testPureList() { 229 | println("testPureList:") 230 | val parser = JsonPlainParser("""[ 231 | |1,2,3,4,5 232 | |,6,7,8,9,10 233 | |]""".trimMargin()) 234 | val item = parser.parse() 235 | val list = item.asObject>() 236 | println(list) 237 | } 238 | 239 | fun testListListInt() { 240 | println("testListListInt:") 241 | val parser = JsonPlainParser("""{ 242 | |"listed":[ 243 | |[1,2,3,4,5] 244 | |,[6,7,8,9,10] 245 | |]}""".trimMargin()) 246 | val item = parser.parse() 247 | val list = item.asObject() 248 | println(list) 249 | } 250 | 251 | fun testListInt() { 252 | println("testListInt:") 253 | val parser = JsonPlainParser("""{ 254 | |"listed":[1,2,3,4,5]}""".trimMargin()) 255 | val item = parser.parse() 256 | val list = item.asObject() 257 | println(list) 258 | } 259 | 260 | fun testListedData() { 261 | println("testListedData:") 262 | val parser = JsonPlainParser("""{ 263 | |"listed":[ 264 | |{"name": "Alla Who", "age": 35, "cashAmount": 122.12121, "property": {"name": "Домик в деревне"}} 265 | |, {"name": "Akbar", "age": 46, "cashAmount": 44.3, "property": {"name": "Деревня"}} 266 | |]}""".trimMargin()) 267 | val item = parser.parse() 268 | val list = item.asObject() 269 | println(list) 270 | } 271 | 272 | fun testUser() { 273 | println("testUser:") 274 | val parser = JsonPlainParser("""{"id": 3, 275 | |"person1": {"name": "Akbar", "age": 35, "cashAmount": 12200.12, "property": {"name": "Домик в деревне"}}, 276 | |"type": "MaleFirst", 277 | |"person2": {"name": "Alla Who", "height": 170, "income": 1214.81} 278 | |}""".trimMargin()) 279 | 280 | val item = parser.parse() 281 | val user = item.asObject() 282 | println(user.person1) 283 | println(user.person2) 284 | 285 | } -------------------------------------------------------------------------------- /src/iris/sequence/NumberConversions.kt: -------------------------------------------------------------------------------- 1 | @file:Suppress("NOTHING_TO_INLINE") 2 | 3 | package iris.sequence 4 | 5 | public inline fun CharSequence?.toBoolean(): Boolean = java.lang.Boolean.parseBoolean(this.toString()) 6 | 7 | public inline fun CharSequence.toByte(): Byte = java.lang.Byte.parseByte(this.toString()) 8 | 9 | public inline fun CharSequence.toByte(radix: Int): Byte = java.lang.Byte.parseByte(this.toString(), checkRadix(radix)) 10 | 11 | public inline fun CharSequence.toShort(): Short = java.lang.Short.parseShort(this.toString()) 12 | 13 | public inline fun CharSequence.toShort(radix: Int): Short = java.lang.Short.parseShort(this.toString(), checkRadix(radix)) 14 | 15 | public inline fun CharSequence.toInt(): Int = this.toInt(10) 16 | 17 | public inline fun CharSequence.toInt(radix: Int): Int = java.lang.Integer.parseInt(this, 0, this.length, checkRadix(radix)) 18 | 19 | public inline fun CharSequence.toLong(): Long = this.toLong(10) 20 | 21 | public inline fun CharSequence.toLong(radix: Int): Long = java.lang.Long.parseLong(this, 0, this.length, checkRadix(radix)) 22 | 23 | public inline fun CharSequence.toFloat(): Float = java.lang.Float.parseFloat(this.toString()) 24 | 25 | public inline fun CharSequence.toDouble(): Double = java.lang.Double.parseDouble(this.toString()) 26 | 27 | public fun CharSequence.toFloatOrNull(): Float? = screenFloatValue(this, java.lang.Float::parseFloat) 28 | 29 | 30 | public fun CharSequence.toDoubleOrNull(): Double? = screenFloatValue(this, java.lang.Double::parseDouble) 31 | 32 | public inline fun CharSequence.toBigInteger(): java.math.BigInteger = 33 | java.math.BigInteger(this.toString()) 34 | 35 | public inline fun CharSequence.toBigInteger(radix: Int): java.math.BigInteger = 36 | java.math.BigInteger(this.toString(), checkRadix(radix)) 37 | 38 | public fun CharSequence.toBigIntegerOrNull(): java.math.BigInteger? = toBigIntegerOrNull(10) 39 | 40 | public fun CharSequence.toBigIntegerOrNull(radix: Int): java.math.BigInteger? { 41 | checkRadix(radix) 42 | val length = this.length 43 | when (length) { 44 | 0 -> return null 45 | 1 -> if (digitOf(this[0], radix) < 0) return null 46 | else -> { 47 | val start = if (this[0] == '-') 1 else 0 48 | for (index in start until length) { 49 | if (digitOf(this[index], radix) < 0) 50 | return null 51 | } 52 | } 53 | } 54 | return toBigInteger(radix) 55 | } 56 | 57 | public inline fun CharSequence.toBigDecimal(mathContext: java.math.MathContext): java.math.BigDecimal = 58 | java.math.BigDecimal(this.toString(), mathContext) 59 | 60 | public fun CharSequence.toBigDecimalOrNull(): java.math.BigDecimal? = 61 | screenFloatValue(this) { it.toBigDecimal() } 62 | 63 | public fun CharSequence.toBigDecimalOrNull(mathContext: java.math.MathContext): java.math.BigDecimal? = 64 | screenFloatValue(this) { it.toBigDecimal(mathContext) } 65 | 66 | private object ScreenFloatValueRegEx { 67 | @JvmField val value = run { 68 | val Digits = "(\\p{Digit}+)" 69 | val HexDigits = "(\\p{XDigit}+)" 70 | val Exp = "[eE][+-]?$Digits" 71 | 72 | val HexString = "(0[xX]$HexDigits(\\.)?)|" + // 0[xX] HexDigits ._opt BinaryExponent FloatTypeSuffix_opt 73 | "(0[xX]$HexDigits?(\\.)$HexDigits)" // 0[xX] HexDigits_opt . HexDigits BinaryExponent FloatTypeSuffix_opt 74 | 75 | val Number = "($Digits(\\.)?($Digits?)($Exp)?)|" + // Digits ._opt Digits_opt ExponentPart_opt FloatTypeSuffix_opt 76 | "(\\.($Digits)($Exp)?)|" + // . Digits ExponentPart_opt FloatTypeSuffix_opt 77 | "(($HexString)[pP][+-]?$Digits)" // HexSignificand BinaryExponent 78 | 79 | val fpRegex = "[\\x00-\\x20]*[+-]?(NaN|Infinity|(($Number)[fFdD]?))[\\x00-\\x20]*" 80 | 81 | Regex(fpRegex) 82 | } 83 | } 84 | 85 | private fun screenFloatValue(str: CharSequence, parse: (String) -> T): T? { 86 | return try { 87 | if (ScreenFloatValueRegEx.value.matches(str)) 88 | parse(str.toString()) 89 | else 90 | null 91 | } catch (e: NumberFormatException) { // overflow 92 | null 93 | } 94 | } 95 | 96 | @PublishedApi 97 | internal fun checkRadix(radix: Int): Int { 98 | if (radix !in Character.MIN_RADIX..Character.MAX_RADIX) { 99 | throw IllegalArgumentException("radix $radix was not in valid range ${Character.MIN_RADIX..Character.MAX_RADIX}") 100 | } 101 | return radix 102 | } 103 | 104 | internal fun digitOf(char: Char, radix: Int): Int = Character.digit(char.toInt(), radix) 105 | 106 | /*******************************/ 107 | 108 | 109 | public fun CharSequence.toByteOrNull(): Byte? = toByteOrNull(radix = 10) 110 | 111 | /** 112 | * Parses the string as a signed [Byte] number and returns the result 113 | * or `null` if the string is not a valid representation of a number. 114 | * 115 | * @throws IllegalArgumentException when [radix] is not a valid radix for string to number conversion. 116 | */ 117 | @SinceKotlin("1.1") 118 | public fun CharSequence.toByteOrNull(radix: Int): Byte? { 119 | val int = this.toIntOrNull(radix) ?: return null 120 | if (int < Byte.MIN_VALUE || int > Byte.MAX_VALUE) return null 121 | return int.toByte() 122 | } 123 | 124 | /** 125 | * Parses the string as a [Short] number and returns the result 126 | * or `null` if the string is not a valid representation of a number. 127 | */ 128 | @SinceKotlin("1.1") 129 | public fun CharSequence.toShortOrNull(): Short? = toShortOrNull(radix = 10) 130 | 131 | /** 132 | * Parses the string as a [Short] number and returns the result 133 | * or `null` if the string is not a valid representation of a number. 134 | * 135 | * @throws IllegalArgumentException when [radix] is not a valid radix for string to number conversion. 136 | */ 137 | @SinceKotlin("1.1") 138 | public fun CharSequence.toShortOrNull(radix: Int): Short? { 139 | val int = this.toIntOrNull(radix) ?: return null 140 | if (int < Short.MIN_VALUE || int > Short.MAX_VALUE) return null 141 | return int.toShort() 142 | } 143 | 144 | /** 145 | * Parses the string as an [Int] number and returns the result 146 | * or `null` if the string is not a valid representation of a number. 147 | */ 148 | @SinceKotlin("1.1") 149 | public fun CharSequence.toIntOrNull(): Int? = toIntOrNull(radix = 10) 150 | 151 | /** 152 | * Parses the string as an [Int] number and returns the result 153 | * or `null` if the string is not a valid representation of a number. 154 | * 155 | * @throws IllegalArgumentException when [radix] is not a valid radix for string to number conversion. 156 | */ 157 | @SinceKotlin("1.1") 158 | public fun CharSequence.toIntOrNull(radix: Int): Int? { 159 | checkRadix(radix) 160 | 161 | val length = this.length 162 | if (length == 0) return null 163 | 164 | val start: Int 165 | val isNegative: Boolean 166 | val limit: Int 167 | 168 | val firstChar = this[0] 169 | if (firstChar < '0') { // Possible leading sign 170 | if (length == 1) return null // non-digit (possible sign) only, no digits after 171 | 172 | start = 1 173 | 174 | if (firstChar == '-') { 175 | isNegative = true 176 | limit = Int.MIN_VALUE 177 | } else if (firstChar == '+') { 178 | isNegative = false 179 | limit = -Int.MAX_VALUE 180 | } else 181 | return null 182 | } else { 183 | start = 0 184 | isNegative = false 185 | limit = -Int.MAX_VALUE 186 | } 187 | 188 | 189 | val limitForMaxRadix = (-Int.MAX_VALUE) / 36 190 | 191 | var limitBeforeMul = limitForMaxRadix 192 | var result = 0 193 | for (i in start until length) { 194 | val digit = digitOf(this[i], radix) 195 | 196 | if (digit < 0) return null 197 | if (result < limitBeforeMul) { 198 | if (limitBeforeMul == limitForMaxRadix) { 199 | limitBeforeMul = limit / radix 200 | 201 | if (result < limitBeforeMul) { 202 | return null 203 | } 204 | } else { 205 | return null 206 | } 207 | } 208 | 209 | result *= radix 210 | 211 | if (result < limit + digit) return null 212 | 213 | result -= digit 214 | } 215 | 216 | return if (isNegative) result else -result 217 | } 218 | 219 | /** 220 | * Parses the string as a [Long] number and returns the result 221 | * or `null` if the string is not a valid representation of a number. 222 | */ 223 | @SinceKotlin("1.1") 224 | public fun CharSequence.toLongOrNull(): Long? = toLongOrNull(radix = 10) 225 | 226 | /** 227 | * Parses the string as a [Long] number and returns the result 228 | * or `null` if the string is not a valid representation of a number. 229 | * 230 | * @throws IllegalArgumentException when [radix] is not a valid radix for string to number conversion. 231 | */ 232 | @SinceKotlin("1.1") 233 | public fun CharSequence.toLongOrNull(radix: Int): Long? { 234 | checkRadix(radix) 235 | 236 | val length = this.length 237 | if (length == 0) return null 238 | 239 | val start: Int 240 | val isNegative: Boolean 241 | val limit: Long 242 | 243 | val firstChar = this[0] 244 | if (firstChar < '0') { // Possible leading sign 245 | if (length == 1) return null // non-digit (possible sign) only, no digits after 246 | 247 | start = 1 248 | 249 | if (firstChar == '-') { 250 | isNegative = true 251 | limit = Long.MIN_VALUE 252 | } else if (firstChar == '+') { 253 | isNegative = false 254 | limit = -Long.MAX_VALUE 255 | } else 256 | return null 257 | } else { 258 | start = 0 259 | isNegative = false 260 | limit = -Long.MAX_VALUE 261 | } 262 | 263 | 264 | val limitForMaxRadix = (-Long.MAX_VALUE) / 36 265 | 266 | var limitBeforeMul = limitForMaxRadix 267 | var result = 0L 268 | for (i in start until length) { 269 | val digit = digitOf(this[i], radix) 270 | 271 | if (digit < 0) return null 272 | if (result < limitBeforeMul) { 273 | if (limitBeforeMul == limitForMaxRadix) { 274 | limitBeforeMul = limit / radix 275 | 276 | if (result < limitBeforeMul) { 277 | return null 278 | } 279 | } else { 280 | return null 281 | } 282 | } 283 | 284 | result *= radix 285 | 286 | if (result < limit + digit) return null 287 | 288 | result -= digit 289 | } 290 | 291 | return if (isNegative) result else -result 292 | } -------------------------------------------------------------------------------- /test_array.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "ID":"3", 4 | "Title":"Замена жидкости в кондиционере", 5 | "Cost":"3040", 6 | "Duration":"8 час.", 7 | "Description":null, 8 | "Discount":"0.25", 9 | "MainImagePath":"zamena.jpg" 10 | }, 11 | { 12 | "ID":"4", 13 | "Title":"Ремонт и замена коллектора", 14 | "Cost":"2770", 15 | "Duration":"150 мин.", 16 | "Description":null, 17 | "Discount":"0.15", 18 | "MainImagePath":"collector.jpg" 19 | }, 20 | { 21 | "ID":"5", 22 | "Title":"Замена актуатора сцепления", 23 | "Cost":"4100", 24 | "Duration":"330 мин.", 25 | "Description":null, 26 | "Discount":"0.15", 27 | "MainImagePath":"actuator.jpg" 28 | }, 29 | { 30 | "ID":"6", 31 | "Title":"Замена жидкости ГУР", 32 | "Cost":"2380", 33 | "Duration":"9 час.", 34 | "Description":null, 35 | "Discount":"0.2", 36 | "MainImagePath":"zamena_gur.jpg" 37 | }, 38 | { 39 | "ID":"7", 40 | "Title":"Заправка кондиционеров", 41 | "Cost":"4860", 42 | "Duration":"2 час.", 43 | "Description":null, 44 | "Discount":"0.05", 45 | "MainImagePath":"zapravka_cond.png" 46 | }, 47 | { 48 | "ID":"8", 49 | "Title":"Замена масла в вариаторе", 50 | "Cost":"4720", 51 | "Duration":"2 час.", 52 | "Description":null, 53 | "Discount":"0.05", 54 | "MainImagePath":"variator.jpg" 55 | }, 56 | { 57 | "ID":"9", 58 | "Title":"Ремонт двигателя", 59 | "Cost":"2470", 60 | "Duration":"4 час.", 61 | "Description":null, 62 | "Discount":"0.25", 63 | "MainImagePath":"remont_dvig.jpg" 64 | }, 65 | { 66 | "ID":"10", 67 | "Title":"Замена заднего сальника АКПП", 68 | "Cost":"1510", 69 | "Duration":"390 мин.", 70 | "Description":null, 71 | "Discount":"0.25", 72 | "MainImagePath":"salnik.jpg" 73 | }, 74 | { 75 | "ID":"11", 76 | "Title":"Покраска", 77 | "Cost":"2370", 78 | "Duration":"2 час.", 79 | "Description":null, 80 | "Discount":"0.15", 81 | "MainImagePath":"pokraska.jpg" 82 | }, 83 | { 84 | "ID":"12", 85 | "Title":"Мойка колес", 86 | "Cost":"3490", 87 | "Duration":"570 мин.", 88 | "Description":null, 89 | "Discount":"0", 90 | "MainImagePath":"moika_koles.jpg" 91 | }, 92 | { 93 | "ID":"13", 94 | "Title":"Замена прокладки впускного-выпуского коллектора", 95 | "Cost":"2980", 96 | "Duration":"3 час.", 97 | "Description":null, 98 | "Discount":"0", 99 | "MainImagePath":"zamena_collector.jpg" 100 | }, 101 | { 102 | "ID":"14", 103 | "Title":"Диагностика работы двигателя", 104 | "Cost":"2760", 105 | "Duration":"150 мин.", 106 | "Description":null, 107 | "Discount":"0.1", 108 | "MainImagePath":"diagnostika_dvig.jpg" 109 | }, 110 | { 111 | "ID":"15", 112 | "Title":"Установка сигнализации", 113 | "Cost":"1760", 114 | "Duration":"4 час.", 115 | "Description":null, 116 | "Discount":"0", 117 | "MainImagePath":"signal.jpg" 118 | }, 119 | { 120 | "ID":"16", 121 | "Title":"Замена компрессора кондиционера", 122 | "Cost":"2720", 123 | "Duration":"270 мин.", 124 | "Description":null, 125 | "Discount":"0.1", 126 | "MainImagePath":"compressor_cond.jpg" 127 | }, 128 | { 129 | "ID":"17", 130 | "Title":"Замена ремня привода ГУР", 131 | "Cost":"3350", 132 | "Duration":"10 час.", 133 | "Description":null, 134 | "Discount":"0", 135 | "MainImagePath":"remen_gur.jpg" 136 | }, 137 | { 138 | "ID":"18", 139 | "Title":"Замена тормозных колодок", 140 | "Cost":"4260", 141 | "Duration":"8 час.", 142 | "Description":null, 143 | "Discount":"0.05", 144 | "MainImagePath":"tormoz.jpg" 145 | }, 146 | { 147 | "ID":"19", 148 | "Title":"Замена масла", 149 | "Cost":"1430", 150 | "Duration":"510 мин.", 151 | "Description":null, 152 | "Discount":"0.25", 153 | "MainImagePath":"zamena_maslo.jpg" 154 | }, 155 | { 156 | "ID":"20", 157 | "Title":"Замена цепи ГРМ", 158 | "Cost":"4570", 159 | "Duration":"6 час.", 160 | "Description":null, 161 | "Discount":"0.2", 162 | "MainImagePath":"cepi.jpg" 163 | }, 164 | { 165 | "ID":"21", 166 | "Title":"Замена бензонасоса", 167 | "Cost":"1780", 168 | "Duration":"4 час.", 169 | "Description":null, 170 | "Discount":"0.1", 171 | "MainImagePath":"benzonasos.jpg" 172 | }, 173 | { 174 | "ID":"22", 175 | "Title":"Ремонт приводного вала", 176 | "Cost":"4520", 177 | "Duration":"210 мин.", 178 | "Description":null, 179 | "Discount":"0.05", 180 | "MainImagePath":"Услуги автосервиса\rulevoe-upravlenie-avtomobilya.jpg" 181 | }, 182 | { 183 | "ID":"23", 184 | "Title":"Замена подшипника компрессора кондиционера", 185 | "Cost":"1110", 186 | "Duration":"330 мин.", 187 | "Description":null, 188 | "Discount":"0.2", 189 | "MainImagePath":"Услуги автосервисаКондиционер.jpg" 190 | }, 191 | { 192 | "ID":"24", 193 | "Title":"Снятие и установка колес", 194 | "Cost":"3130", 195 | "Duration":"390 мин.", 196 | "Description":null, 197 | "Discount":"0", 198 | "MainImagePath":"Услуги автосервисаШиномонтаж.jpg" 199 | }, 200 | { 201 | "ID":"25", 202 | "Title":"Замена лямбда зонда", 203 | "Cost":"770", 204 | "Duration":"270 мин.", 205 | "Description":null, 206 | "Discount":"0.05", 207 | "MainImagePath":"Услуги автосервисаВыхлопная система.jpg" 208 | }, 209 | { 210 | "ID":"26", 211 | "Title":"Замена привода в сборе", 212 | "Cost":"3890", 213 | "Duration":"8 час.", 214 | "Description":null, 215 | "Discount":"0.15", 216 | "MainImagePath":"Услуги автосервиса\transmission.jpg" 217 | }, 218 | { 219 | "ID":"27", 220 | "Title":"Замена трубки кондиционера", 221 | "Cost":"2810", 222 | "Duration":"3 час.", 223 | "Description":null, 224 | "Discount":"0.15", 225 | "MainImagePath":"Услуги автосервисаКондиционер.jpg" 226 | }, 227 | { 228 | "ID":"28", 229 | "Title":"Замена ремня кондиционера", 230 | "Cost":"4650", 231 | "Duration":"8 час.", 232 | "Description":null, 233 | "Discount":"0", 234 | "MainImagePath":"Услуги автосервисаКондиционер.jpg" 235 | }, 236 | { 237 | "ID":"29", 238 | "Title":"Замена охлаждающей жидкости", 239 | "Cost":"1590", 240 | "Duration":"210 мин.", 241 | "Description":null, 242 | "Discount":"0", 243 | "MainImagePath":"Услуги автосервисаТехническое обслуживание.png" 244 | }, 245 | { 246 | "ID":"30", 247 | "Title":"Замена троса сцепления", 248 | "Cost":"4460", 249 | "Duration":"8 час.", 250 | "Description":null, 251 | "Discount":"0.05", 252 | "MainImagePath":"Услуги автосервисаСцепление.jpg" 253 | }, 254 | { 255 | "ID":"31", 256 | "Title":"Замена масла в МКПП", 257 | "Cost":"4490", 258 | "Duration":"9 час.", 259 | "Description":null, 260 | "Discount":"0.2", 261 | "MainImagePath":"Услуги автосервисаКПП.png" 262 | }, 263 | { 264 | "ID":"32", 265 | "Title":"Замена рулевой рейки", 266 | "Cost":"4840", 267 | "Duration":"570 мин.", 268 | "Description":null, 269 | "Discount":"0.25", 270 | "MainImagePath":"Услуги автосервиса\rulevoe-upravlenie-avtomobilya.jpg" 271 | }, 272 | { 273 | "ID":"33", 274 | "Title":"Ремонт дисков", 275 | "Cost":"3860", 276 | "Duration":"270 мин.", 277 | "Description":null, 278 | "Discount":"0.1", 279 | "MainImagePath":"Услуги автосервисаШиномонтаж.jpg" 280 | }, 281 | { 282 | "ID":"34", 283 | "Title":"Замена масла заднего редуктора (моста)", 284 | "Cost":"840", 285 | "Duration":"7 час.", 286 | "Description":null, 287 | "Discount":"0.25", 288 | "MainImagePath":"Услуги автосервиса\transmission.jpg" 289 | }, 290 | { 291 | "ID":"35", 292 | "Title":"Замена подшипника задней ступицы", 293 | "Cost":"1860", 294 | "Duration":"270 мин.", 295 | "Description":null, 296 | "Discount":"0.25", 297 | "MainImagePath":"Услуги автосервиса\transmission.jpg" 298 | }, 299 | { 300 | "ID":"36", 301 | "Title":"Восстановление рулевых реек", 302 | "Cost":"1020", 303 | "Duration":"2 час.", 304 | "Description":null, 305 | "Discount":"0.2", 306 | "MainImagePath":"Услуги автосервисаПодвеска.png" 307 | }, 308 | { 309 | "ID":"37", 310 | "Title":"Замена рулевой тяги", 311 | "Cost":"570", 312 | "Duration":"3 час.", 313 | "Description":null, 314 | "Discount":"0", 315 | "MainImagePath":"Услуги автосервиса\rulevoe-upravlenie-avtomobilya.jpg" 316 | }, 317 | { 318 | "ID":"38", 319 | "Title":"Ремонт и замена гидроблока АКПП", 320 | "Cost":"2040", 321 | "Duration":"270 мин.", 322 | "Description":null, 323 | "Discount":"0", 324 | "MainImagePath":"Услуги автосервисаАКПП.jpg" 325 | }, 326 | { 327 | "ID":"39", 328 | "Title":"Замена масла раздаточной коробки", 329 | "Cost":"2070", 330 | "Duration":"510 мин.", 331 | "Description":null, 332 | "Discount":"0", 333 | "MainImagePath":"Услуги автосервиса\transmission.jpg" 334 | }, 335 | { 336 | "ID":"40", 337 | "Title":"Диагностика кондиционера", 338 | "Cost":"2590", 339 | "Duration":"10 час.", 340 | "Description":null, 341 | "Discount":"0.1", 342 | "MainImagePath":"Услуги автосервисаКондиционер.jpg" 343 | }, 344 | { 345 | "ID":"41", 346 | "Title":"Ремонт и замена катализатора", 347 | "Cost":"500", 348 | "Duration":"270 мин.", 349 | "Description":null, 350 | "Discount":"0.25", 351 | "MainImagePath":"Услуги автосервисаВыхлопная система.jpg" 352 | }, 353 | { 354 | "ID":"42", 355 | "Title":"Замена гофры глушителя", 356 | "Cost":"760", 357 | "Duration":"270 мин.", 358 | "Description":null, 359 | "Discount":"0.25", 360 | "MainImagePath":"Услуги автосервисаВыхлопная система.jpg" 361 | }, 362 | { 363 | "ID":"43", 364 | "Title":"Замена ремня ГРМ", 365 | "Cost":"4630", 366 | "Duration":"150 мин.", 367 | "Description":null, 368 | "Discount":"0.25", 369 | "MainImagePath":"Услуги автосервисаДвигатель.png" 370 | }, 371 | { 372 | "ID":"44", 373 | "Title":"Ремонт редуктора", 374 | "Cost":"870", 375 | "Duration":"8 час.", 376 | "Description":null, 377 | "Discount":"0.15", 378 | "MainImagePath":"Услуги автосервисаКПП.png" 379 | }, 380 | { 381 | "ID":"45", 382 | "Title":"Замена электромагнитного клапана без снятия ТНВД", 383 | "Cost":"4610", 384 | "Duration":"450 мин.", 385 | "Description":null, 386 | "Discount":"0.1", 387 | "MainImagePath":"Услуги автосервисаdiz.jpg" 388 | }, 389 | { 390 | "ID":"46", 391 | "Title":"Вулканизация шин", 392 | "Cost":"540", 393 | "Duration":"330 мин.", 394 | "Description":null, 395 | "Discount":"0.2", 396 | "MainImagePath":"Услуги автосервисаШиномонтаж.jpg" 397 | }, 398 | { 399 | "ID":"47", 400 | "Title":"Ремонт стартера", 401 | "Cost":"2680", 402 | "Duration":"8 час.", 403 | "Description":null, 404 | "Discount":"0", 405 | "MainImagePath":"Услуги автосервисаЭлектрика.png" 406 | }, 407 | { 408 | "ID":"48", 409 | "Title":"Ремонт коробки передач", 410 | "Cost":"1850", 411 | "Duration":"450 мин.", 412 | "Description":null, 413 | "Discount":"0.25", 414 | "MainImagePath":"Услуги автосервисаКПП.png" 415 | }, 416 | { 417 | "ID":"49", 418 | "Title":"Жидкостная промывка топливной системы", 419 | "Cost":"3620", 420 | "Duration":"6 час.", 421 | "Description":null, 422 | "Discount":"0.15", 423 | "MainImagePath":"Услуги автосервисаТопливная система.png" 424 | }, 425 | { 426 | "ID":"50", 427 | "Title":"Ремонт генератора", 428 | "Cost":"1700", 429 | "Duration":"210 мин.", 430 | "Description":null, 431 | "Discount":"0.2", 432 | "MainImagePath":"Услуги автосервисаЭлектрика.png" 433 | }, 434 | { 435 | "ID":"51", 436 | "Title":"Замена масла АКПП", 437 | "Cost":"2430", 438 | "Duration":"450 мин.", 439 | "Description":null, 440 | "Discount":"0.25", 441 | "MainImagePath":"Услуги автосервисаАКПП.jpg" 442 | }, 443 | { 444 | "ID":"52", 445 | "Title":"Диагностика трансмиссии", 446 | "Cost":"2790", 447 | "Duration":"2 час.", 448 | "Description":null, 449 | "Discount":"0.2", 450 | "MainImagePath":"Услуги автосервиса\transmission.jpg" 451 | } 452 | ] --------------------------------------------------------------------------------