|
| 1 | +/* |
| 2 | + * Licensed to the Apache Software Foundation (ASF) under one or more |
| 3 | + * contributor license agreements. See the NOTICE file distributed with |
| 4 | + * this work for additional information regarding copyright ownership. |
| 5 | + * The ASF licenses this file to You under the Apache License, Version 2.0 |
| 6 | + * (the "License"); you may not use this file except in compliance with |
| 7 | + * the License. You may obtain a copy of the License at |
| 8 | + * |
| 9 | + * http://www.apache.org/licenses/LICENSE-2.0 |
| 10 | + * |
| 11 | + * Unless required by applicable law or agreed to in writing, software |
| 12 | + * distributed under the License is distributed on an "AS IS" BASIS, |
| 13 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 14 | + * See the License for the specific language governing permissions and |
| 15 | + * limitations under the License. |
| 16 | + */ |
| 17 | +package org.apache.spark.sql |
| 18 | + |
| 19 | +import java.sql.{Date, Timestamp} |
| 20 | +import java.time.{Instant, LocalDate} |
| 21 | + |
| 22 | +import org.json4s.JsonAST.{JArray, JBool, JDecimal, JDouble, JLong, JNull, JObject, JString, JValue} |
| 23 | + |
| 24 | +import org.apache.spark.SparkFunSuite |
| 25 | +import org.apache.spark.sql.catalyst.encoders.{ExamplePoint, ExamplePointUDT} |
| 26 | +import org.apache.spark.sql.catalyst.expressions.GenericRowWithSchema |
| 27 | +import org.apache.spark.sql.internal.SQLConf |
| 28 | +import org.apache.spark.sql.types._ |
| 29 | + |
| 30 | +/** |
| 31 | + * Test suite for [[Row]] JSON serialization. |
| 32 | + */ |
| 33 | +class RowJsonSuite extends SparkFunSuite { |
| 34 | + private val schema = new StructType() |
| 35 | + .add("c1", "string") |
| 36 | + .add("c2", IntegerType) |
| 37 | + |
| 38 | + private def testJson(name: String, value: Any, dt: DataType, expected: JValue): Unit = { |
| 39 | + test(name) { |
| 40 | + val row = new GenericRowWithSchema(Array(value), new StructType().add("a", dt)) |
| 41 | + assert(row.jsonValue === JObject("a" -> expected)) |
| 42 | + } |
| 43 | + } |
| 44 | + |
| 45 | + private def testJson(value: Any, dt: DataType, expected: JValue): Unit = { |
| 46 | + testJson(s"$dt $value", value, dt, expected) |
| 47 | + } |
| 48 | + |
| 49 | + // Nulls |
| 50 | + private def testJsonNull(dt: DataType, expected: JValue): Unit = { |
| 51 | + testJson(null, dt, JNull) |
| 52 | + } |
| 53 | + testJsonNull(IntegerType, JNull) |
| 54 | + testJsonNull(FloatType, JNull) |
| 55 | + testJsonNull(ArrayType(DoubleType, containsNull = true), JNull) |
| 56 | + |
| 57 | + // Primitives |
| 58 | + testJson(true, BooleanType, JBool(true)) |
| 59 | + testJson(false, BooleanType, JBool(false)) |
| 60 | + testJson(23.toByte, ByteType, JLong(23)) |
| 61 | + testJson(-126.toByte, ByteType, JLong(-126)) |
| 62 | + testJson(20281.toShort, ShortType, JLong(20281)) |
| 63 | + testJson(-8752.toShort, ShortType, JLong(-8752)) |
| 64 | + testJson(1078231987, IntegerType, JLong(1078231987)) |
| 65 | + testJson(-10, IntegerType, JLong(-10)) |
| 66 | + testJson(139289832109874199L, LongType, JLong(139289832109874199L)) |
| 67 | + testJson(-7873748239973488L, LongType, JLong(-7873748239973488L)) |
| 68 | + testJson(10.232e10f, FloatType, JDouble(10.232e10f)) |
| 69 | + testJson(9.7e-13f, FloatType, JDouble(9.7e-13f)) |
| 70 | + testJson(3.891e98d, DoubleType, JDouble(3.891e98d)) |
| 71 | + testJson(-7.8e5d, DoubleType, JDouble(-7.8e5d)) |
| 72 | + testJson(BigDecimal("1092.88"), DecimalType(10, 2), JDecimal(BigDecimal("1092.88"))) |
| 73 | + testJson(Decimal("782.0003"), DecimalType(7, 4), JDecimal(BigDecimal("782.0003"))) |
| 74 | + testJson(new java.math.BigDecimal("-77.89"), DecimalType(4, 2), JDecimal(BigDecimal("-77.89"))) |
| 75 | + testJson("hello world", StringType, JString("hello world")) |
| 76 | + testJson("BinaryType", Array('a'.toByte, 'b'.toByte), BinaryType, JString("YWI=")) |
| 77 | + testJson(Date.valueOf("2019-04-22"), DateType, JString("2019-04-22")) |
| 78 | + testJson(LocalDate.of(2018, 5, 14), DateType, JString("2018-05-14")) |
| 79 | + testJson( |
| 80 | + Timestamp.valueOf("2017-01-06 10:22:03.00"), |
| 81 | + TimestampType, |
| 82 | + JString("2017-01-06 10:22:03")) |
| 83 | + testJson( |
| 84 | + Timestamp.valueOf("2017-05-30 10:22:03.00").toInstant, |
| 85 | + TimestampType, |
| 86 | + JString("2017-05-30 10:22:03")) |
| 87 | + |
| 88 | + // Complex types |
| 89 | + testJson( |
| 90 | + "ArrayType(LongType,true)", |
| 91 | + Array(1L, null, 77L), |
| 92 | + ArrayType(LongType, containsNull = true), |
| 93 | + JArray(JLong(1L) :: JNull :: JLong(77L) :: Nil)) |
| 94 | + |
| 95 | + testJson( |
| 96 | + Seq(1, -2, 3), |
| 97 | + ArrayType(IntegerType, containsNull = false), |
| 98 | + JArray(JLong(1) :: JLong(-2) :: JLong(3) :: Nil)) |
| 99 | + |
| 100 | + testJson( |
| 101 | + Map("a" -> "b", "c" -> "d", "e" -> null), |
| 102 | + MapType(StringType, StringType, valueContainsNull = true), |
| 103 | + JObject("a" -> JString("b"), "c" -> JString("d"), "e" -> JNull)) |
| 104 | + |
| 105 | + testJson( |
| 106 | + Map(1 -> "b", 2 -> "d", 3 -> null), |
| 107 | + MapType(IntegerType, StringType, valueContainsNull = true), |
| 108 | + JArray( |
| 109 | + JObject("key" -> JLong(1), "value" -> JString("b")) :: |
| 110 | + JObject("key" -> JLong(2), "value" -> JString("d")) :: |
| 111 | + JObject("key" -> JLong(3), "value" -> JNull) :: Nil)) |
| 112 | + |
| 113 | + testJson( |
| 114 | + new GenericRowWithSchema(Array("1", 2), schema), |
| 115 | + schema, |
| 116 | + JObject("c1" -> JString("1"), "c2" -> JLong(2))) |
| 117 | + |
| 118 | + testJson( |
| 119 | + "UDT", |
| 120 | + new ExamplePoint(3.4d, 8.98d), |
| 121 | + new ExamplePointUDT, |
| 122 | + JArray(JDouble(3.4d) :: JDouble(8.98d) :: Nil)) |
| 123 | + |
| 124 | + test("no schema") { |
| 125 | + val e = intercept[IllegalArgumentException] { |
| 126 | + Row("a").jsonValue |
| 127 | + } |
| 128 | + assert(e.getMessage.contains("requires a non-null schema")) |
| 129 | + } |
| 130 | + |
| 131 | + test("unsupported type") { |
| 132 | + val e = intercept[IllegalArgumentException] { |
| 133 | + val row = new GenericRowWithSchema( |
| 134 | + Array((1, 2)), |
| 135 | + new StructType().add("a", ObjectType(classOf[(Int, Int)]))) |
| 136 | + row.jsonValue |
| 137 | + } |
| 138 | + assert(e.getMessage.contains("Failed to convert value")) |
| 139 | + } |
| 140 | +} |
0 commit comments