Skip to content

Commit 120633e

Browse files
author
Yannick Gladow
committed
Fix multiple nested subclasses with same names
1 parent af8ce75 commit 120633e

2 files changed

Lines changed: 65 additions & 41 deletions

File tree

src/main/scala/io/yannick_cw/sjq/jsonToCaseClass.scala

Lines changed: 45 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,6 @@ object jsonToCaseClass {
99
val (_, allCCs) = buildCC(j, "CC", List.empty)
1010
val ccs =
1111
allCCs
12-
// todo this is just a hack to get around duplicated nested objects
13-
.distinctBy(_.name)
1412
.map(cc => cc.copy(content = cc.content.filter { case (key, value) => key.nonEmpty && value.nonEmpty }))
1513

1614
ccs.map(cc => (cc.name, renderCC(cc)))
@@ -19,52 +17,60 @@ object jsonToCaseClass {
1917
private def renderCC(cc: CC): String =
2018
s"case class ${cc.name}(${cc.content.map { case (key, value) => s"$key: $value" }.mkString(", ")})"
2119

22-
private def buildCC(j: Json, nextLevelName: String, doneCCs: List[CC]): (Option[String], List[CC]) = {
20+
private def buildCC(j: Json, nextLevelName: String, doneCCs: List[CC]): (String, List[CC]) = {
2321
j.fold(
24-
Some("Option[Any]") -> doneCCs,
25-
_ => Some("Boolean") -> doneCCs,
26-
_ => Some("Double") -> doneCCs,
27-
_ => Some("String") -> doneCCs,
28-
array => {
29-
val innerType = array.flatMap { ele =>
30-
val (x, y) = buildCC(ele, nextLevelName, List.empty)
31-
x.map(_ -> y)
32-
}
33-
innerType
34-
.foldLeft[Option[(String, List[CC])]](None)((left, right) =>
35-
left.map { case (valueName, ccs) => (valueName, right._2 ++ ccs) }.orElse(Some(right)))
36-
.map {
37-
case (valueName, Nil) => valueName -> doneCCs
38-
case (valueName, ele :: Nil) => valueName -> (ele :: doneCCs)
39-
case (valueName, ele :: more) =>
40-
val (nextLevelCCs, deeperCCs) = (ele :: more)
41-
.partition(_.name == valueName)
22+
// todo decode not at all if null
23+
"Option[String]" -> doneCCs,
24+
_ => "Boolean" -> doneCCs,
25+
_ => "Double" -> doneCCs,
26+
_ => "String" -> doneCCs,
27+
array =>
28+
array.toList match {
29+
case all @ ele :: rest if all.forall(_.isObject) =>
30+
val innerType =
31+
rest.foldLeft(buildCC(ele, nextLevelName, doneCCs)) {
32+
case ((newValueName, aggCCs), nextJson) =>
33+
newValueName -> buildCC(nextJson, nextLevelName, aggCCs)._2
34+
}
35+
36+
val (valueName, ccs) = innerType match {
37+
case (valueName, Nil) => valueName -> doneCCs
38+
case (valueName, ele :: Nil) => valueName -> (ele :: Nil)
39+
case (valueName, ele :: more) =>
40+
val (nextLevelCCs, otherCCs) = (ele :: more)
41+
.partition(_.name.startsWith(valueName))
4242

43-
val allContent = nextLevelCCs.map(_.content).fold(Map.empty)(_ ++ _)
44-
val keyCountPerContent =
45-
nextLevelCCs.map(cc => cc.content.map[String, Int] { case (key, _) => (key, 1) }).combineAll
43+
val allContent = nextLevelCCs.map(_.content).fold(Map.empty)(_ ++ _)
44+
val keyCountPerContent =
45+
nextLevelCCs.map(cc => cc.content.map[String, Int] { case (key, _) => (key, 1) }).combineAll
4646

47-
val keyCount = keyCountPerContent.size
47+
val keyCount = nextLevelCCs.size
4848

49-
val newCC = ele.copy(content = keyCountPerContent.map({
50-
case (key, count) if count == keyCount => key -> allContent(key)
51-
case (key, _) => key -> s"Option[${allContent(key)}]"
52-
}))
49+
val newCC = ele.copy(content = keyCountPerContent.map({
50+
case (key, count) if count == keyCount => key -> allContent(key)
51+
case (key, _) => key -> s"Option[${allContent(key)}]"
52+
}))
53+
valueName -> (newCC :: otherCCs)
54+
}
5355

54-
valueName -> (newCC :: deeperCCs ::: doneCCs)
55-
}
56-
.map { case (valueName, ccs) => Some(s"List[$valueName]") -> ccs }
57-
.getOrElse(Some("List[String]") -> doneCCs)
56+
s"List[$valueName]" -> ccs
57+
case ele :: rest if rest.forall(_ == ele) =>
58+
val (newValue, allCCs) = buildCC(ele, nextLevelName, doneCCs)
59+
s"List[$newValue]" -> allCCs
60+
case _ => "List[String]" -> doneCCs
5861
},
5962
jObj => {
60-
val (allNewCCs, newCC) = jObj.toMap.foldLeft(List.empty[CC] -> CC(nextLevelName, Map.empty)) {
61-
case ((allCss, currCC), (key, value)) =>
62-
val (newValue, allNewCCs) = buildCC(value, key, List.empty)
63-
(allCss ++ allNewCCs,
64-
newValue.map(newV => currCC.copy(content = currCC.content.updated(key, newV))).getOrElse(currCC))
63+
val (allNewCCs, newCC) = jObj.toMap.foldLeft(doneCCs -> CC(nextLevelName, Map.empty)) {
64+
case ((allCCs, currCC), (key, value)) =>
65+
val safeNextLevelName = findFreeName(currCC :: allCCs, key)
66+
val (newValue, allNewCCs) = buildCC(value, safeNextLevelName, allCCs)
67+
(allNewCCs, currCC.copy(content = currCC.content.updated(key, newValue)))
6568
}
66-
(Some(nextLevelName), newCC :: allNewCCs)
69+
(nextLevelName, newCC :: allNewCCs)
6770
}
6871
)
6972
}
73+
74+
private def findFreeName(ccs: List[CC], name: String): String =
75+
if (ccs.exists(_.name == name)) findFreeName(ccs, name + "1") else name
7076
}

src/test/scala/io/yannick_cw/sjq/jsonToCaseClassTest.scala

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ class jsonToCaseClassTest extends FlatSpec with Matchers {
5555
| }
5656
""".stripMargin).fold(throw _, x => x)
5757

58-
jsonToCaseClass(json) shouldBe List(("CC", "case class CC(name: Option[Any])"))
58+
jsonToCaseClass(json) shouldBe List(("CC", "case class CC(name: Option[String])"))
5959
}
6060

6161
it should "parse json empty lists" in {
@@ -118,9 +118,27 @@ class jsonToCaseClassTest extends FlatSpec with Matchers {
118118

119119
jsonToCaseClass(json) shouldBe List(
120120
("CC", "case class CC(sub: sub, another: another)"),
121+
("another", "case class another(double: double1)"),
122+
("double1", "case class double1(xx: String)"),
121123
("sub", "case class sub(double: double)"),
122124
("double", "case class double(xx: String)"),
123-
("another", "case class another(double: double)"),
125+
)
126+
}
127+
128+
it should "parse json with the same name but different structures" in {
129+
val json = parse("""
130+
| {
131+
| "sub": { "double": { "xx": 22 } },
132+
| "another": { "double": { "xx": "sds" } }
133+
| }
134+
""".stripMargin).fold(throw _, x => x)
135+
136+
jsonToCaseClass(json) shouldBe List(
137+
("CC", "case class CC(sub: sub, another: another)"),
138+
("another", "case class another(double: double1)"),
139+
("double1", "case class double1(xx: String)"),
140+
("sub", "case class sub(double: double)"),
141+
("double", "case class double(xx: Double)"),
124142
)
125143
}
126144
}

0 commit comments

Comments
 (0)