Skip to content

Commit 3c10439

Browse files
authored
Merge pull request #396 from wuseal/bugfix/#379
fix #379
2 parents 1ded2de + d969b9e commit 3c10439

File tree

5 files changed

+224
-131
lines changed

5 files changed

+224
-131
lines changed

src/main/kotlin/wu/seal/jsontokotlin/utils/Extensions.kt

Lines changed: 83 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
11
package wu.seal.jsontokotlin.utils
22

3-
import com.google.gson.Gson
4-
import com.google.gson.JsonArray
5-
import com.google.gson.JsonElement
6-
import com.google.gson.JsonPrimitive
3+
import com.google.gson.*
74
import wu.seal.jsontokotlin.model.classscodestruct.DataClass
85
import wu.seal.jsontokotlin.model.classscodestruct.KotlinClass
96
import java.lang.StringBuilder
@@ -325,4 +322,86 @@ fun String.isJSONSchema(): Boolean {
325322
} else {
326323
false
327324
}
325+
}
326+
327+
/**
328+
* get a Fat JsonObject whose fields contain all the objects' fields around the objects of the json array
329+
*/
330+
fun JsonArray.getFatJsonObject(): JsonObject {
331+
if (size() == 0 || !allItemAreObjectElement()) {
332+
throw IllegalStateException("input arg jsonArray must not be empty and all element should be json object! ")
333+
}
334+
val allFields = flatMap { it.asJsonObject.entrySet().map { entry -> Pair(entry.key, entry.value) } }
335+
val fatJsonObject = JsonObject().apply {
336+
fun JsonElement.isEmptyJsonObj(): Boolean = isJsonObject && asJsonObject.entrySet().isEmpty()
337+
fun JsonElement.isEmptyJsonArray(): Boolean = isJsonArray && asJsonArray.size() == 0
338+
fun JsonElement.numberLevel(): Int {
339+
return if (isJsonPrimitive) {
340+
asJsonPrimitive.toKotlinClass().getNumLevel()
341+
} else -1
342+
}
343+
//create good fatJsonObject contains all fields, and try not use null or empty value
344+
allFields.forEach { (key, value) ->
345+
if (has(key).not()) {
346+
add(key, value)
347+
} else {
348+
//when the the field is a number type, we need to select the value with largest scope
349+
//e.g. given [{"key":10},{"key":11.2}]
350+
//we should use the object with value = 11.2 to represent the object type which will be Double
351+
val newIsHigherNumber = value.numberLevel() > this[key].numberLevel()
352+
val newIsNotNullOrEmpty = value !is JsonNull && !value.isEmptyJsonObj() && !value.isEmptyJsonArray()
353+
val oldIsNullOrEmpty =
354+
(this[key] is JsonNull) or this[key].isEmptyJsonObj() or this[key].isEmptyJsonArray()
355+
val needReplaceValue = newIsHigherNumber || (newIsNotNullOrEmpty and oldIsNullOrEmpty)
356+
if (needReplaceValue) {
357+
add(key, value)
358+
}
359+
}
360+
}
361+
// add nullable type fields
362+
forEach {
363+
val item = it.asJsonObject
364+
item.entrySet().forEach { (key, value) ->
365+
//if the value is null or empty json obj or empty json array,
366+
// then translate it to a new special property to indicate that the property is nullable
367+
//later will consume this property (do it here[DataClassGeneratorByJSONObject#consumeBackstageProperties])
368+
// delete it or translate it back to normal property without [BACKSTAGE_NULLABLE_POSTFIX] when consume it
369+
// and will not be generated in final code
370+
if (value is JsonNull || value.isEmptyJsonObj()) {
371+
add(key + BACKSTAGE_NULLABLE_POSTFIX, value)
372+
}
373+
}
374+
//if some json fields in fat json object not inside one item, then we treat them as nullable
375+
entrySet().map { it.key.replace(BACKSTAGE_NULLABLE_POSTFIX, "") }
376+
.filter { it !in item.entrySet().map { it.key.replace(BACKSTAGE_NULLABLE_POSTFIX, "") } }.forEach {
377+
add(it + BACKSTAGE_NULLABLE_POSTFIX, null)
378+
}
379+
}
380+
381+
//let all fields value to be fatJsonObject or fat JsonArray
382+
allFields.filter { it.second !is JsonNull }.groupBy { it.first }.forEach {
383+
val jsonArrayObj = JsonArray().apply {
384+
it.value.map { it.second }.forEach {
385+
this.add(it)
386+
}
387+
}
388+
if (jsonArrayObj.allItemAreObjectElement()) {
389+
this.add(it.key, jsonArrayObj.getFatJsonObject())
390+
} else if (jsonArrayObj.allItemAreArrayElement()) {
391+
this.add(it.key, jsonArrayObj.getFatJsonArray())
392+
}
393+
}
394+
}
395+
return fatJsonObject
396+
}
397+
398+
fun JsonArray.getFatJsonArray(): JsonArray {
399+
if (size() == 0 || !allItemAreArrayElement()) {
400+
throw IllegalStateException("input arg jsonArray must not be empty and all element should be json array! ")
401+
}
402+
val fatJsonArray = JsonArray()
403+
forEach {
404+
fatJsonArray.addAll(it.asJsonArray)
405+
}
406+
return fatJsonArray
328407
}

src/main/kotlin/wu/seal/jsontokotlin/utils/classgenerator/GenericListClassGeneratorByJSONArray.kt

Lines changed: 2 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -40,14 +40,14 @@ class GenericListClassGeneratorByJSONArray(private val jsonKey: String, jsonArra
4040
return GenericListClass(generic = elementKotlinClass)
4141
}
4242
jsonArrayExcludeNull.allItemAreObjectElement() -> {
43-
val fatJsonObject = getFatJsonObject(jsonArrayExcludeNull)
43+
val fatJsonObject = jsonArrayExcludeNull.getFatJsonObject()
4444
val itemObjClassName = getRecommendItemName(jsonKey)
4545
val dataClassFromJsonObj = DataClassGeneratorByJSONObject(itemObjClassName, fatJsonObject).generate()
4646
LogUtil.i("$tag jsonArray allItemAreObjectElement, return GenericListClass with generic type ${dataClassFromJsonObj.name}")
4747
return GenericListClass(generic = dataClassFromJsonObj)
4848
}
4949
jsonArrayExcludeNull.allItemAreArrayElement() -> {
50-
val fatJsonArray = getFatJsonArray(jsonArrayExcludeNull.map { it.asJsonArray })
50+
val fatJsonArray = jsonArrayExcludeNull.getFatJsonArray()
5151
val genericListClassFromFatJsonArray = GenericListClassGeneratorByJSONArray(jsonKey, fatJsonArray.toString()).generate()
5252
LogUtil.i("$tag jsonArray allItemAreArrayElement, return GenericListClass with generic type ${genericListClassFromFatJsonArray.name}")
5353
return GenericListClass(generic = genericListClassFromFatJsonArray)
@@ -62,68 +62,4 @@ class GenericListClassGeneratorByJSONArray(private val jsonKey: String, jsonArra
6262
private fun getRecommendItemName(jsonKey: String): String {
6363
return adjustPropertyNameForGettingArrayChildType(jsonKey)
6464
}
65-
66-
private fun getFatJsonArray(jsonArrayList: List<JsonArray>): JsonArray {
67-
val fatJsonArray = JsonArray()
68-
jsonArrayList.forEach {
69-
fatJsonArray.addAll(it)
70-
}
71-
return fatJsonArray
72-
}
73-
74-
75-
/**
76-
* get a Fat JsonObject whose fields contains all the objects' fields around the objects of the json array
77-
*/
78-
private fun getFatJsonObject(jsonArray: JsonArray): JsonObject {
79-
if (jsonArray.size() == 0 || !jsonArray.allItemAreObjectElement()) {
80-
throw IllegalStateException("input arg jsonArray must not be empty and all element should be json object! ")
81-
}
82-
val allFields = jsonArray.flatMap { it.asJsonObject.entrySet().map { entry -> Pair(entry.key, entry.value) } }
83-
val fatJsonObject = JsonObject()
84-
allFields.forEach { (key, value) ->
85-
if (value is JsonNull) {
86-
//if the value is null and pre added the same key into the fatJsonObject,
87-
// then translate it to a new special property to indicate that the property is nullable
88-
//later will consume this property (do it here[DataClassGeneratorByJSONObject#consumeBackstageProperties])
89-
// delete it or translate it back to normal property without [BACKSTAGE_NULLABLE_POSTFIX] when consume it
90-
// and will not be generated in final code
91-
if (fatJsonObject.has(key)) {
92-
fatJsonObject.add(key + BACKSTAGE_NULLABLE_POSTFIX, value)
93-
} else {
94-
fatJsonObject.add(key, value)
95-
fatJsonObject.add(key + BACKSTAGE_NULLABLE_POSTFIX, value)
96-
}
97-
} else if (fatJsonObject.has(key)) {
98-
if (fatJsonObject[key].isJsonObject && value.isJsonObject) {//try not miss any fields of the key's related json object
99-
val newValue = getFatJsonObject(JsonArray().apply { add(fatJsonObject[key]);add(value) })
100-
fatJsonObject.add(key, newValue)
101-
} else if (fatJsonObject[key].isJsonArray && value.isJsonArray) {////try not miss any elements of the key's related json array
102-
val newValue = getFatJsonArray(listOf(fatJsonObject[key].asJsonArray, value.asJsonArray))
103-
fatJsonObject.add(key, newValue)
104-
} else if (fatJsonObject[key].isJsonPrimitive && value.isJsonPrimitive && theSamePrimitiveType(fatJsonObject[key].asJsonPrimitive, value.asJsonPrimitive)) {
105-
//if the value and exist value are the same primitive type then ignore it
106-
//except for the following scenario:
107-
//when the the field is a number type, we need to select the value with largest scope
108-
//e.g. given [{"key":10},{"key":11.2}]
109-
//we should use the object with value = 11.2 to represent the object type which will be Double
110-
111-
val prev = fatJsonObject[key].asJsonPrimitive
112-
val cur = value.asJsonPrimitive
113-
if(prev.isNumber && cur.isNumber && cur.toKotlinClass().getNumLevel() > prev.toKotlinClass().getNumLevel()) {
114-
fatJsonObject.add(key, value);
115-
}
116-
} else if (value.isJsonNull) {
117-
//if the value is null, we ignore this value
118-
} else {
119-
//others the two values of the key are not the same type, then give it a null value indicate that it's should be an Any Type in Kotlin
120-
fatJsonObject.add(key, JsonNull.INSTANCE)
121-
}
122-
} else {
123-
fatJsonObject.add(key, value)
124-
}
125-
}
126-
return fatJsonObject
127-
}
128-
12965
}

src/main/kotlin/wu/seal/jsontokotlin/utils/classgenerator/ListClassGeneratorByJSONArray.kt

Lines changed: 2 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -37,14 +37,14 @@ class ListClassGeneratorByJSONArray(private val className: String, jsonArrayStri
3737
return ListClass(name = className, generic = elementKotlinClass)
3838
}
3939
jsonArrayExcludeNull.allItemAreObjectElement() -> {
40-
val fatJsonObject = getFatJsonObject(jsonArrayExcludeNull)
40+
val fatJsonObject = jsonArrayExcludeNull.getFatJsonObject()
4141
val itemObjClassName = "${className}Item"
4242
val dataClassFromJsonObj = DataClassGeneratorByJSONObject(itemObjClassName, fatJsonObject).generate()
4343
LogUtil.i("$tag jsonArray allItemAreObjectElement, return ListClass with generic type ${dataClassFromJsonObj.name}")
4444
return ListClass(className, dataClassFromJsonObj)
4545
}
4646
jsonArrayExcludeNull.allItemAreArrayElement() -> {
47-
val fatJsonArray = getFatJsonArray(jsonArrayExcludeNull)
47+
val fatJsonArray = jsonArrayExcludeNull.getFatJsonArray()
4848
val itemArrayClassName = "${className}SubList"
4949
val listClassFromFatJsonArray = ListClassGeneratorByJSONArray(itemArrayClassName, fatJsonArray.toString()).generate()
5050
LogUtil.i("$tag jsonArray allItemAreArrayElement, return ListClass with generic type ${listClassFromFatJsonArray.name}")
@@ -56,57 +56,4 @@ class ListClassGeneratorByJSONArray(private val className: String, jsonArrayStri
5656
}
5757
}
5858
}
59-
60-
private fun getFatJsonArray(jsonArray: JsonArray): JsonArray {
61-
if (jsonArray.size() == 0 || !jsonArray.allItemAreArrayElement()) {
62-
throw IllegalStateException("input arg jsonArray must not be empty and all element should be json array! ")
63-
}
64-
val fatJsonArray = JsonArray()
65-
jsonArray.forEach {
66-
fatJsonArray.addAll(it.asJsonArray)
67-
}
68-
return fatJsonArray
69-
}
70-
71-
72-
/**
73-
* get a Fat JsonObject whose fields contains all the objects' fields around the objects of the json array
74-
*/
75-
private fun getFatJsonObject(jsonArray: JsonArray): JsonObject {
76-
if (jsonArray.size() == 0 || !jsonArray.allItemAreObjectElement()) {
77-
throw IllegalStateException("input arg jsonArray must not be empty and all element should be json object! ")
78-
}
79-
val allFields = jsonArray.flatMap { it.asJsonObject.entrySet().map { entry -> Pair(entry.key, entry.value) } }
80-
val fatJsonObject = JsonObject()
81-
allFields.forEach { (key, value) ->
82-
if (value is JsonNull ) {
83-
//if the value is null and pre added the same key into the fatJsonObject,
84-
// then translate it to a new special property to indicate that the property is nullable
85-
//later will consume this property (do it here[DataClassGeneratorByJSONObject#consumeBackstageProperties])
86-
// delete it or translate it back to normal property without [BACKSTAGE_NULLABLE_POSTFIX] when consume it
87-
// and will not be generated in final code
88-
if (fatJsonObject.has(key)) {
89-
fatJsonObject.add(key + BACKSTAGE_NULLABLE_POSTFIX, value)
90-
} else {
91-
fatJsonObject.add(key, value)
92-
fatJsonObject.add(key + BACKSTAGE_NULLABLE_POSTFIX, value)
93-
}
94-
} else if (fatJsonObject.has(key) && value is JsonPrimitive && value.isNumber
95-
&& fatJsonObject[key].isJsonPrimitive && fatJsonObject[key].asJsonPrimitive.isNumber) {
96-
//when the the field is a number type, we need to select the value with largest scope
97-
//e.g. given [{"key":10},{"key":11.2}]
98-
//we should use the object with value = 11.2 to represent the object type which will be Double
99-
100-
val prev = fatJsonObject[key].asJsonPrimitive
101-
val cur = value.asJsonPrimitive
102-
if(cur.toKotlinClass().getNumLevel() > prev.toKotlinClass().getNumLevel()) {
103-
fatJsonObject.add(key, value);
104-
}
105-
} else {
106-
fatJsonObject.add(key, value)
107-
}
108-
}
109-
return fatJsonObject
110-
}
111-
11259
}

src/test/kotlin/wu/seal/jsontokotlin/regression/Issue269Test.kt

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1493,9 +1493,9 @@ class Issue269Test {
14931493
) {
14941494
data class Product(
14951495
val id: Int,
1496-
val view_count: Int,
1497-
val order_count: Int,
1498-
val shares: Int
1496+
val view_count: Int?,
1497+
val order_count: Int?,
1498+
val shares: Int?
14991499
)
15001500
}
15011501
}
@@ -1541,9 +1541,9 @@ class Issue269Test {
15411541
15421542
data class ProductX(
15431543
val id: Int,
1544-
val view_count: Int,
1545-
val order_count: Int,
1546-
val shares: Int
1544+
val view_count: Int?,
1545+
val order_count: Int?,
1546+
val shares: Int?
15471547
)
15481548
""".trimIndent()
15491549

0 commit comments

Comments
 (0)