1+ package com.fractalwrench.json2kotlin
2+
3+ import com.google.gson.JsonElement
4+ import com.squareup.kotlinpoet.*
5+ import java.util.*
6+
7+ /* *
8+ * Finds distinct types for a collection of fields.
9+ */
10+ internal class TypeReducer (private val typeDetector : JsonTypeDetector ) {
11+
12+ fun findDistinctTypes (fields : Collection <String >,
13+ commonElements : List <TypedJsonElement >,
14+ jsonElementMap : HashMap <JsonElement , TypeSpec >): Map <String , TypeName > {
15+ val fieldMap = HashMap <String , TypeName >()
16+
17+ fields.forEach {
18+ val distinctTypes = findDistinctTypesForField(commonElements, it, jsonElementMap)
19+ fieldMap.put(it, reduceToSingleType(distinctTypes))
20+ }
21+ return fieldMap
22+ }
23+
24+ private fun findDistinctTypesForField (commonElements : List <TypedJsonElement >,
25+ key : String ,
26+ jsonElementMap : HashMap <JsonElement , TypeSpec >): List <TypeName ?> {
27+ return commonElements.map {
28+ val fieldValue = it.asJsonObject.get(key)
29+ if (fieldValue != null ) {
30+ typeDetector.typeForJsonElement(fieldValue, key, jsonElementMap)
31+ } else {
32+ null
33+ }
34+ }.distinct()
35+ }
36+
37+ /* *
38+ * Determines a single type which fits multiple types.
39+ */
40+ private fun reduceToSingleType (types : List <TypeName ?>): TypeName {
41+ val anyClz = Any ::class .asTypeName()
42+ val nullableClassName = anyClz.asNullable()
43+ val nullable = types.contains(nullableClassName) || types.contains(null )
44+ val nonNullTypes = types.filterNotNull().filter { it != nullableClassName }
45+
46+ val typeName: TypeName = when {
47+ nonNullTypes.size == 1 -> nonNullTypes[0 ]
48+ nonNullTypes.size == 2 -> reduceMultipleTypes(nonNullTypes, anyClz)
49+ else -> anyClz
50+ }
51+ return if (nullable) typeName.asNullable() else typeName
52+ }
53+
54+ private fun reduceMultipleTypes (nonNullTypes : List <TypeName >, anyClz : ClassName ): TypeName {
55+ val parameterizedTypeName
56+ = nonNullTypes.filterIsInstance(ParameterizedTypeName ::class .java).firstOrNull()
57+
58+ return when {
59+ parameterizedTypeName != null -> { // handle type params (recursive)
60+ val rawType = parameterizedTypeName.rawType
61+ parameterizedTypeName.typeArguments
62+ ParameterizedTypeName .get(rawType, reduceToSingleType(parameterizedTypeName.typeArguments))
63+ }
64+ nonNullTypes.contains(anyClz) -> // Any will be an empty/missing object
65+ nonNullTypes.filterNot { it == anyClz }.first()
66+ else -> anyClz
67+ }
68+ }
69+
70+ }
0 commit comments