The complete code is at the end. You can jump through the directory
1, Background
Assuming that the return value of an HTTP interface is as follows, how can we parse the result using Gson in scala?
{ "code":0, "message":"OK", "result":[ { "originalityId":7, "conversionType":10011, "conversionTypeValue":"Dot activation", "targetValueDouble":"1.0" } ] }
2, Solution
1. High version solution (2.8.7)
The original solution is as follows:
private def parseBody(bodyOpt: Option[JsonObject]) = { val isLegal = bodyOpt.isDefined && { val body = bodyOpt.get body.has(CODE) && body.get(CODE).getAsInt == SUCCESS_CODE && body.has(RESULT) } if (!isLegal) throw new RuntimeException(s"bodyOpt is illegal, bodyOpt: $bodyOpt") val body = bodyOpt.get val results = body.get(RESULT).getAsJsonArray val resultList = new ArrayBuffer[(Long, (Long, Int, String, Double))]() results.forEach(r => { val result = r.getAsJsonObject val adId = result.get(ORIGINALITY_ID).getAsLong val conversionType = result.get(CONVERSION_TYPE).getAsInt val conversionTypeValue = result.get(CONVERSION_TYPE_VALUE).getAsString val targetValueDouble = result.get(TARGET_VALUE_DOUBLE).getAsDouble resultList.append(adId -> (adId, conversionType, conversionTypeValue, targetValueDouble)) }) resultList.toList }
The Gson version used in this example is 2.8.7, and the dependencies are as follows:
<!-- https://mvnrepository.com/artifact/com.google.code.gson/gson --> <dependency> <groupId>com.google.code.gson</groupId> <artifactId>gson</artifactId> <version>2.8.7</version> </dependency>
2. Lower version solution (2.8.2)
However, it seems that the above solution is only supported in the higher version of Gson, but it seems that the version 2.8.2 does not work:
foreach method actually needs to receive an operator of Consumer type:
default void forEach(Consumer<? super T> action) { Objects.requireNonNull(action); for (T t : this) { action.accept(t); } }
In Java, it's good to pass a lambda expression, but in scala, it seems that this operation is not supported. What should we do? It's good to implement a Consumer object by ourselves, so we wrote the following scala Code:
val list = new ArrayBuffer[JsonObject]() val consumer = new Consumer[JsonElement] { override def accept(t: JsonElement): Unit = { val result = JsonParser.parseString(t.toString).getAsJsonObject list.append(result) } } results.forEach(consumer) val resultList1 = list.toList.map { result: JsonObject => val adId = result.get(ORIGINALITY_ID).getAsLong val conversionType = result.get(CONVERSION_TYPE).getAsInt val conversionTypeValue = result.get(CONVERSION_TYPE_VALUE).getAsString val targetValueDouble = result.get(TARGET_VALUE_DOUBLE).getAsDouble adId -> (conversionType, conversionTypeValue, targetValueDouble) } resultList1
The Gson version used in this example is 2.8.2. The dependencies are as follows:
<dependency> <groupId>com.google.code.gson</groupId> <artifactId>gson</artifactId> <version>2.8.2</version> </dependency>
3. Directly resolve to entity class
In actual use, it is found that using this tuple seems easier to understand than directly using the field name, so parsing json into an entity class seems more humanized.
Still use gson2 8.7, we write the following code:
val resultList2 = new ArrayBuffer[Result]() val consumer2 = new Consumer[JsonElement] { override def accept(t: JsonElement): Unit = { val result: Option[Result] = try { Some(new Gson().fromJson(t.toString, classOf[Result])) } catch { case _: Throwable => None } if (result.isDefined) resultList2.append(result.get) } } results.forEach(consumer2) resultList2.toList
The Result class is an entity class implemented in Java, which is implemented as follows:
@Data public class Result { @SerializedName(value = "adId", alternate = {"originalityId"}) private long adId; @SerializedName("conversionType") private int conversionType; @SerializedName("conversionTypeValue") private String conversionTypeValue; @SerializedName("targetValueDouble") private String targetValueDouble; }
Here we explain why the entity class Result is implemented in Java instead of scala. In fact, it is because we tried Scala's case class and found that the fields with different field names can't be solved by using @ SerializedName (the field name in json string is inconsistent with the entity class, resulting in the field value of entity class being 0 after parsing). The specific reason needs to be determined. If there is a teacher, If you know the reason, leave a message and explain it. Thank you! My scala case class is implemented as follows:
case class Result1(@SerializedName(value = "adId", alternate = Array[String]("originalityId")) adId: Long, conversionType: Int, conversionTypeValue: String, targetValueDouble: Double)
3, Summary
Briefly summarize some of the pits you stepped on this time:
1. Get the class type in scala
When using the gson:: fromjason method, the second parameter should pass the class < T > class type. When obtaining the type in Scala, you should use the classOf method in the Predef class under the scala package; You can't use Class[T], and others seem not to work, such as class,. getClass.
2. Solve the mismatch between json field name and entity class field name
Annotate with @ SerializedName
@Documented @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.FIELD, ElementType.METHOD}) public @interface SerializedName { String value(); String[] alternate() default {}; }
value: use this name when serializing and deserializing
Alternate: alternate: when the value specified by value does not match, the name in the alternate specified list will be selected to map (but it seems to fail in scala's case class).
4, Complete code
1. ParseJson.scala
package com.zhang.io import com.google.gson.annotations.SerializedName import com.google.gson.{Gson, JsonElement, JsonObject, JsonParser} import com.zhang.models.Result import java.util.function.Consumer import scala.collection.mutable.ArrayBuffer import scala.util.{Failure, Success, Try} object ParseJson { val jsonStr = """ { "code":0, "message":"OK", "result":[ { "originalityId":7, "conversionType":10011, "conversionTypeValue":"Dot activation", "targetValueDouble":"1.0" } ] } """ private val CODE = "code" private val SUCCESS_CODE = 0 private val MESSAGE = "message" private val RESULT = "result" private val ORIGINALITY_ID = "originalityId" private val CONVERSION_TYPE = "conversionType" private val CONVERSION_TYPE_VALUE = "conversionTypeValue" private val TARGET_VALUE_DOUBLE = "targetValueDouble" def main(args: Array[String]): Unit = { println(Array[String]("originalityId").mkString(", ")) println(Array("originalityId").mkString(", ")) Try(parseBody(Some(JsonParser.parseString(jsonStr).getAsJsonObject))) match { case Success(results) => results.foreach(println) case Failure(exception) => throw exception } } private def parseBody(bodyOpt: Option[JsonObject]) = { val isLegal = bodyOpt.isDefined && { val body = bodyOpt.get body.has(CODE) && body.get(CODE).getAsInt == SUCCESS_CODE && body.has(RESULT) } if (!isLegal) throw new RuntimeException(s"bodyOpt is illegal, bodyOpt: $bodyOpt") val body = bodyOpt.get val results = body.get(RESULT).getAsJsonArray val resultList = new ArrayBuffer[(Long, (Long, Int, String, Double))]() results.forEach(r => { val result = r.getAsJsonObject val adId = result.get(ORIGINALITY_ID).getAsLong val conversionType = result.get(CONVERSION_TYPE).getAsInt val conversionTypeValue = result.get(CONVERSION_TYPE_VALUE).getAsString val targetValueDouble = result.get(TARGET_VALUE_DOUBLE).getAsDouble resultList.append(adId -> (adId, conversionType, conversionTypeValue, targetValueDouble)) }) resultList.toList val list = new ArrayBuffer[JsonObject]() val consumer = new Consumer[JsonElement] { override def accept(t: JsonElement): Unit = { val result = JsonParser.parseString(t.toString).getAsJsonObject list.append(result) } } results.forEach(consumer) val resultList1 = list.toList.map { result: JsonObject => val adId = result.get(ORIGINALITY_ID).getAsLong val conversionType = result.get(CONVERSION_TYPE).getAsInt val conversionTypeValue = result.get(CONVERSION_TYPE_VALUE).getAsString val targetValueDouble = result.get(TARGET_VALUE_DOUBLE).getAsDouble adId -> (conversionType, conversionTypeValue, targetValueDouble) } resultList1 val resultList2 = new ArrayBuffer[Result]() val consumer2 = new Consumer[JsonElement] { override def accept(t: JsonElement): Unit = { val result: Option[Result] = try { Some(new Gson().fromJson(t.toString, classOf[Result])) } catch { case _: Throwable => None } if (result.isDefined) resultList2.append(result.get) } } results.forEach(consumer2) resultList2.toList } } case class Result1(@SerializedName(value = "adId", alternate = Array[String]("originalityId")) adId: Long, conversionType: Int, conversionTypeValue: String, targetValueDouble: Double)
2. Result.java
package com.zhang.models; import com.google.gson.annotations.SerializedName; import lombok.Data; @Data public class Result { @SerializedName(value = "adId", alternate = {"originalityId"}) private long adId; @SerializedName("conversionType") private int conversionType; @SerializedName("conversionTypeValue") private String conversionTypeValue; @SerializedName("targetValueDouble") private String targetValueDouble; }
3. Dependencies used
<dependency> <groupId>com.google.code.gson</groupId> <artifactId>gson</artifactId> <version>2.8.7</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.16.10</version> </dependency>
That's about it. If you have any questions, please leave a message. I'll reply as soon as possible 😃