scala使用Gson解析数组

完整代码在最后哦,可以通过目录跳转

文章目录

      • 一、背景
      • 二、解决方案
        • 1. 高版本的解法(2.8.7)
        • 2. 低版本的解法(2.8.2)
        • 3. 直接解析为实体类
      • 三、总结
        • 1. 在scala中获取类类型
        • 2. 解决json字段名和实体类字段名不匹配的问题
      • 四、完整代码
        • 1. ParseJson.scala
        • 2. Result.java
        • 3. 使用到的依赖

一、背景

假设某个HTTP接口的返回值如下,怎么在scala中使用Gsonresult解析出来呢?

{
    "code":0,
    "message":"OK",
    "result":[
        {
            "originalityId":7,
            "conversionType":10011,
            "conversionTypeValue":"打点激活",
            "targetValueDouble":"1.0"
        }
    ]
}

二、解决方案

1. 高版本的解法(2.8.7)

本来的解法是这样的:

    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
    }

这个示例用的Gson版本为2.8.7,依赖如下:

        
        <dependency>
            <groupId>com.google.code.gsongroupId>
            <artifactId>gsonartifactId>
            <version>2.8.7version>
        dependency>
2. 低版本的解法(2.8.2)

但是上面的解法貌似只在高版本Gson中支持,但是2.8.2的版本好像就不行了:

scala使用Gson解析数组_第1张图片

scala使用Gson解析数组_第2张图片

foreach方法,其实是需要接收一个Consumer类型的算子:

    default void forEach(Consumer<? super T> action) {
        Objects.requireNonNull(action);
        for (T t : this) {
            action.accept(t);
        }
    }

Java里,传一个lambda表达式就好了,但是在scala里,貌似不支持这种操作,那怎么办呢,自己实现一个Consumer对象不就好了,于是写出了下面这样的scala代码:

        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

这个示例用的Gson版本为2.8.2,依赖如下:

    <dependency>
      <groupId>com.google.code.gsongroupId>
      <artifactId>gsonartifactId>
      <version>2.8.2version>
    dependency>
3. 直接解析为实体类

实际使用中,发现用这种元组的方式,貌似没有直接用字段名的方式更容易理解,所以将json解析为实体类,貌似更人性化一些。

还是使用Gson2.8.7,我们写出了如下代码:

        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

其中,Result类是用Java实现的一个实体类,实现如下:

@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;
}

这里解释一下为啥实体类Result是用Java实现的,而不是scala,其实是因为尝试了scalacase class,发现字段名不同的那些字段,使用@SerializedName貌似解决不了(json字符串里的字段名和实体类不一致,导致解析之后实体类字段值为0),具体原因待确定,如果有哪位老师,知道这其中的原因,辛苦留言解释一下,万谢!我的scala case class实现方式如下:

case class Result1(@SerializedName(value = "adId", alternate = Array[String]("originalityId")) adId: Long,
                   conversionType: Int,
                   conversionTypeValue: String,
                   targetValueDouble: Double)

三、总结

简单总结一些这次踩的坑吧:

1. 在scala中获取类类型

在使用Gson::fromJson方法的时候,第二个参数应该传Class类类型,在scala获取类型,应该使用scala包下,Predef类里的classOf方法;而不能使用Class[T],其他的貌似也不行,诸如.class.getClass

2. 解决json字段名和实体类字段名不匹配的问题

使用@SerializedName注解

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.METHOD})
public @interface SerializedName {
    String value();

    String[] alternate() default {};
}

value: 序列化和反序列化的时候使用这个名称

alternate:英文直译:备用,当value指定的值不匹配的时候,会选取alternate指定列表里的名称,来映射(但在scala的case class中,好像失败了)。

四、完整代码

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":"打点激活",
            "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. 使用到的依赖
        <dependency>
            <groupId>com.google.code.gsongroupId>
            <artifactId>gsonartifactId>
            <version>2.8.7version>
        dependency>
        <dependency>
            <groupId>org.projectlombokgroupId>
            <artifactId>lombokartifactId>
            <version>1.16.10version>
        dependency>

大致就这样吧,有问题欢迎留言哦,看到会尽快回复的

你可能感兴趣的:(spark,Java,gson,scala)