scala parses arrays using Gson

Posted by david_s0 on Sun, 23 Jan 2022 07:46:35 +0100

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 😃

Topics: Java Scala Spark gson