From 041d8019d6f73a31a591ffba22b0fdfc3e56a9c5 Mon Sep 17 00:00:00 2001 From: Ken Huang Date: Thu, 6 Mar 2025 14:11:20 +0800 Subject: [PATCH] KAFKA-18910 Remove kafka.utils.json (#19112) Reviewers: TengYao Chi , Chia-Ping Tsai --- .../scala/kafka/utils/json/DecodeJson.scala | 109 ---------------- .../scala/kafka/utils/json/JsonArray.scala | 27 ---- .../scala/kafka/utils/json/JsonObject.scala | 42 ------- .../scala/kafka/utils/json/JsonValue.scala | 116 ------------------ 4 files changed, 294 deletions(-) delete mode 100644 core/src/main/scala/kafka/utils/json/DecodeJson.scala delete mode 100644 core/src/main/scala/kafka/utils/json/JsonArray.scala delete mode 100644 core/src/main/scala/kafka/utils/json/JsonObject.scala delete mode 100644 core/src/main/scala/kafka/utils/json/JsonValue.scala diff --git a/core/src/main/scala/kafka/utils/json/DecodeJson.scala b/core/src/main/scala/kafka/utils/json/DecodeJson.scala deleted file mode 100644 index 9c7bec0bdd11a..0000000000000 --- a/core/src/main/scala/kafka/utils/json/DecodeJson.scala +++ /dev/null @@ -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()) - } - -} diff --git a/core/src/main/scala/kafka/utils/json/JsonArray.scala b/core/src/main/scala/kafka/utils/json/JsonArray.scala deleted file mode 100644 index c22eda8651d75..0000000000000 --- a/core/src/main/scala/kafka/utils/json/JsonArray.scala +++ /dev/null @@ -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(_)) -} diff --git a/core/src/main/scala/kafka/utils/json/JsonObject.scala b/core/src/main/scala/kafka/utils/json/JsonObject.scala deleted file mode 100644 index 9bf91ae1a6b0a..0000000000000 --- a/core/src/main/scala/kafka/utils/json/JsonObject.scala +++ /dev/null @@ -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)) - } - -} diff --git a/core/src/main/scala/kafka/utils/json/JsonValue.scala b/core/src/main/scala/kafka/utils/json/JsonValue.scala deleted file mode 100644 index ff62c6c12d138..0000000000000 --- a/core/src/main/scala/kafka/utils/json/JsonValue.scala +++ /dev/null @@ -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 - -}