mirror of https://github.com/apache/kafka.git
KAFKA-18910 Remove kafka.utils.json (#19112)
Reviewers: TengYao Chi <kitingiao@gmail.com>, Chia-Ping Tsai <chia7712@gmail.com>
This commit is contained in:
parent
50510bb19d
commit
041d8019d6
|
@ -1,109 +0,0 @@
|
||||||
/**
|
|
||||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
|
||||||
* contributor license agreements. See the NOTICE file distributed with
|
|
||||||
* this work for additional information regarding copyright ownership.
|
|
||||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
|
||||||
* (the "License"); you may not use this file except in compliance with
|
|
||||||
* the License. You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package kafka.utils.json
|
|
||||||
|
|
||||||
import scala.collection.{Factory, Map, Seq}
|
|
||||||
import scala.jdk.CollectionConverters._
|
|
||||||
import com.fasterxml.jackson.databind.{JsonMappingException, JsonNode}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A type class for parsing JSON. This should typically be used via `JsonValue.apply`.
|
|
||||||
*/
|
|
||||||
trait DecodeJson[T] {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Decode the JSON node provided into an instance of `Right[T]`, if possible. Otherwise, return an error message
|
|
||||||
* wrapped by an instance of `Left`.
|
|
||||||
*/
|
|
||||||
def decodeEither(node: JsonNode): Either[String, T]
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Decode the JSON node provided into an instance of `T`.
|
|
||||||
*
|
|
||||||
* @throws JsonMappingException if `node` cannot be decoded into `T`.
|
|
||||||
*/
|
|
||||||
def decode(node: JsonNode): T =
|
|
||||||
decodeEither(node) match {
|
|
||||||
case Right(x) => x
|
|
||||||
case Left(x) => throw new JsonMappingException(null, x)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Contains `DecodeJson` type class instances. That is, we need one instance for each type that we want to be able to
|
|
||||||
* to parse into. It is a compiler error to try to parse into a type for which there is no instance.
|
|
||||||
*/
|
|
||||||
object DecodeJson {
|
|
||||||
|
|
||||||
implicit object DecodeBoolean extends DecodeJson[Boolean] {
|
|
||||||
def decodeEither(node: JsonNode): Either[String, Boolean] =
|
|
||||||
if (node.isBoolean) Right(node.booleanValue) else Left(s"Expected `Boolean` value, received $node")
|
|
||||||
}
|
|
||||||
|
|
||||||
implicit object DecodeDouble extends DecodeJson[Double] {
|
|
||||||
def decodeEither(node: JsonNode): Either[String, Double] =
|
|
||||||
if (node.isDouble || node.isLong || node.isInt)
|
|
||||||
Right(node.doubleValue)
|
|
||||||
else Left(s"Expected `Double` value, received $node")
|
|
||||||
}
|
|
||||||
|
|
||||||
implicit object DecodeInt extends DecodeJson[Int] {
|
|
||||||
def decodeEither(node: JsonNode): Either[String, Int] =
|
|
||||||
if (node.isInt) Right(node.intValue) else Left(s"Expected `Int` value, received $node")
|
|
||||||
}
|
|
||||||
|
|
||||||
implicit object DecodeLong extends DecodeJson[Long] {
|
|
||||||
def decodeEither(node: JsonNode): Either[String, Long] =
|
|
||||||
if (node.isLong || node.isInt) Right(node.longValue) else Left(s"Expected `Long` value, received $node")
|
|
||||||
}
|
|
||||||
|
|
||||||
implicit object DecodeString extends DecodeJson[String] {
|
|
||||||
def decodeEither(node: JsonNode): Either[String, String] =
|
|
||||||
if (node.isTextual) Right(node.textValue) else Left(s"Expected `String` value, received $node")
|
|
||||||
}
|
|
||||||
|
|
||||||
implicit def decodeOption[E](implicit decodeJson: DecodeJson[E]): DecodeJson[Option[E]] = (node: JsonNode) => {
|
|
||||||
if (node.isNull) Right(None)
|
|
||||||
else decodeJson.decodeEither(node).map(Some(_))
|
|
||||||
}
|
|
||||||
|
|
||||||
implicit def decodeSeq[E, S[+T] <: Seq[E]](implicit decodeJson: DecodeJson[E], factory: Factory[E, S[E]]): DecodeJson[S[E]] = (node: JsonNode) => {
|
|
||||||
if (node.isArray)
|
|
||||||
decodeIterator(node.elements.asScala)(decodeJson.decodeEither)
|
|
||||||
else Left(s"Expected JSON array, received $node")
|
|
||||||
}
|
|
||||||
|
|
||||||
implicit def decodeMap[V, M[K, +V] <: Map[K, V]](implicit decodeJson: DecodeJson[V], factory: Factory[(String, V), M[String, V]]): DecodeJson[M[String, V]] = (node: JsonNode) => {
|
|
||||||
if (node.isObject)
|
|
||||||
decodeIterator(node.fields.asScala)(e => decodeJson.decodeEither(e.getValue).map(v => (e.getKey, v)))
|
|
||||||
else Left(s"Expected JSON object, received $node")
|
|
||||||
}
|
|
||||||
|
|
||||||
private def decodeIterator[S, T, C](it: Iterator[S])(f: S => Either[String, T])(implicit factory: Factory[T, C]): Either[String, C] = {
|
|
||||||
val result = factory.newBuilder
|
|
||||||
while (it.hasNext) {
|
|
||||||
f(it.next()) match {
|
|
||||||
case Right(x) => result += x
|
|
||||||
case Left(x) => return Left(x)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Right(result.result())
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,27 +0,0 @@
|
||||||
/**
|
|
||||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
|
||||||
* contributor license agreements. See the NOTICE file distributed with
|
|
||||||
* this work for additional information regarding copyright ownership.
|
|
||||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
|
||||||
* (the "License"); you may not use this file except in compliance with
|
|
||||||
* the License. You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package kafka.utils.json
|
|
||||||
|
|
||||||
import scala.collection.Iterator
|
|
||||||
import scala.jdk.CollectionConverters._
|
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.node.ArrayNode
|
|
||||||
|
|
||||||
class JsonArray private[json] (protected val node: ArrayNode) extends JsonValue {
|
|
||||||
def iterator: Iterator[JsonValue] = node.elements.asScala.map(JsonValue(_))
|
|
||||||
}
|
|
|
@ -1,42 +0,0 @@
|
||||||
/**
|
|
||||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
|
||||||
* contributor license agreements. See the NOTICE file distributed with
|
|
||||||
* this work for additional information regarding copyright ownership.
|
|
||||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
|
||||||
* (the "License"); you may not use this file except in compliance with
|
|
||||||
* the License. You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package kafka.utils.json
|
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.JsonMappingException
|
|
||||||
|
|
||||||
import scala.jdk.CollectionConverters._
|
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.node.ObjectNode
|
|
||||||
|
|
||||||
import scala.collection.Iterator
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A thin wrapper over Jackson's `ObjectNode` for a more idiomatic API. See `JsonValue` for more details.
|
|
||||||
*/
|
|
||||||
class JsonObject private[json] (protected val node: ObjectNode) extends JsonValue {
|
|
||||||
|
|
||||||
def apply(name: String): JsonValue =
|
|
||||||
get(name).getOrElse(throw new JsonMappingException(null, s"No such field exists: `$name`"))
|
|
||||||
|
|
||||||
def get(name: String): Option[JsonValue] = Option(node.get(name)).map(JsonValue(_))
|
|
||||||
|
|
||||||
def iterator: Iterator[(String, JsonValue)] = node.fields.asScala.map { entry =>
|
|
||||||
(entry.getKey, JsonValue(entry.getValue))
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,116 +0,0 @@
|
||||||
/**
|
|
||||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
|
||||||
* contributor license agreements. See the NOTICE file distributed with
|
|
||||||
* this work for additional information regarding copyright ownership.
|
|
||||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
|
||||||
* (the "License"); you may not use this file except in compliance with
|
|
||||||
* the License. You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package kafka.utils.json
|
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.{JsonMappingException, JsonNode}
|
|
||||||
import com.fasterxml.jackson.databind.node.{ArrayNode, ObjectNode}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A simple wrapper over Jackson's JsonNode that enables type safe parsing via the `DecodeJson` type
|
|
||||||
* class.
|
|
||||||
*
|
|
||||||
* Typical usage would be something like:
|
|
||||||
*
|
|
||||||
* {{{
|
|
||||||
* val jsonNode: JsonNode = ???
|
|
||||||
* val jsonObject = JsonValue(jsonNode).asJsonObject
|
|
||||||
* val intValue = jsonObject("int_field").to[Int]
|
|
||||||
* val optionLongValue = jsonObject("option_long_field").to[Option[Long]]
|
|
||||||
* val mapStringIntField = jsonObject("map_string_int_field").to[Map[String, Int]]
|
|
||||||
* val seqStringField = jsonObject("seq_string_field").to[Seq[String]
|
|
||||||
* }}}
|
|
||||||
*
|
|
||||||
* The `to` method throws an exception if the value cannot be converted to the requested type. An alternative is the
|
|
||||||
* `toEither` method that returns an `Either` instead.
|
|
||||||
*/
|
|
||||||
trait JsonValue {
|
|
||||||
|
|
||||||
protected def node: JsonNode
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Decode this JSON value into an instance of `T`.
|
|
||||||
*
|
|
||||||
* @throws JsonMappingException if this value cannot be decoded into `T`.
|
|
||||||
*/
|
|
||||||
def to[T](implicit decodeJson: DecodeJson[T]): T = decodeJson.decode(node)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Decode this JSON value into an instance of `Right[T]`, if possible. Otherwise, return an error message
|
|
||||||
* wrapped by an instance of `Left`.
|
|
||||||
*/
|
|
||||||
def toEither[T](implicit decodeJson: DecodeJson[T]): Either[String, T] = decodeJson.decodeEither(node)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* If this is a JSON object, return an instance of JsonObject. Otherwise, throw a JsonMappingException.
|
|
||||||
*/
|
|
||||||
def asJsonObject: JsonObject =
|
|
||||||
asJsonObjectOption.getOrElse(throw new JsonMappingException(null, s"Expected JSON object, received $node"))
|
|
||||||
|
|
||||||
/**
|
|
||||||
* If this is a JSON object, return a JsonObject wrapped by a `Some`. Otherwise, return None.
|
|
||||||
*/
|
|
||||||
def asJsonObjectOption: Option[JsonObject] = this match {
|
|
||||||
case j: JsonObject => Some(j)
|
|
||||||
case _ => node match {
|
|
||||||
case n: ObjectNode => Some(new JsonObject(n))
|
|
||||||
case _ => None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* If this is a JSON array, return an instance of JsonArray. Otherwise, throw a JsonMappingException.
|
|
||||||
*/
|
|
||||||
def asJsonArray: JsonArray =
|
|
||||||
asJsonArrayOption.getOrElse(throw new JsonMappingException(null, s"Expected JSON array, received $node"))
|
|
||||||
|
|
||||||
/**
|
|
||||||
* If this is a JSON array, return a JsonArray wrapped by a `Some`. Otherwise, return None.
|
|
||||||
*/
|
|
||||||
def asJsonArrayOption: Option[JsonArray] = this match {
|
|
||||||
case j: JsonArray => Some(j)
|
|
||||||
case _ => node match {
|
|
||||||
case n: ArrayNode => Some(new JsonArray(n))
|
|
||||||
case _ => None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override def hashCode: Int = node.hashCode
|
|
||||||
|
|
||||||
override def equals(a: Any): Boolean = a match {
|
|
||||||
case a: JsonValue => node == a.node
|
|
||||||
case _ => false
|
|
||||||
}
|
|
||||||
|
|
||||||
override def toString: String = node.toString
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
object JsonValue {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create an instance of `JsonValue` from Jackson's `JsonNode`.
|
|
||||||
*/
|
|
||||||
def apply(node: JsonNode): JsonValue = node match {
|
|
||||||
case n: ObjectNode => new JsonObject(n)
|
|
||||||
case n: ArrayNode => new JsonArray(n)
|
|
||||||
case _ => new BasicJsonValue(node)
|
|
||||||
}
|
|
||||||
|
|
||||||
private class BasicJsonValue private[json] (protected val node: JsonNode) extends JsonValue
|
|
||||||
|
|
||||||
}
|
|
Loading…
Reference in New Issue