1 /***********************************************************************
2  * Copyright (c) 2013-2025 General Atomics Integrated Intelligence, Inc.
3  * All rights reserved. This program and the accompanying materials
4  * are made available under the terms of the Apache License, Version 2.0
5  * which accompanies this distribution and is available at
6  * https://www.apache.org/licenses/LICENSE-2.0
7  ***********************************************************************/
8 
9 package org.locationtech.geomesa.convert.json
10 
11 import com.google.gson._
12 import com.google.gson.stream.{JsonReader, JsonToken}
13 import com.jayway.jsonpath.spi.json.GsonJsonProvider
14 import com.jayway.jsonpath.{Configuration, JsonPath, PathNotFoundException}
15 import com.typesafe.config.Config
16 import org.geotools.api.feature.simple.SimpleFeatureType
17 import org.locationtech.geomesa.convert._
18 import org.locationtech.geomesa.convert2.AbstractConverter.BasicOptions
19 import org.locationtech.geomesa.convert2.transforms.Expression
20 import org.locationtech.geomesa.convert2.{AbstractConverter, ConverterConfig, ConverterName, Field}
21 import org.locationtech.geomesa.utils.collection.CloseableIterator
22 
23 import java.io._
24 import java.nio.charset.Charset
25 
26 class JsonConverter(sft: SimpleFeatureType, config: JsonConverter.JsonConfig, fields: Seq[JsonConverter.JsonField], options: BasicOptions)
27     extends AbstractConverter[JsonElement, JsonConverter.JsonConfig, JsonConverter.JsonField, BasicOptions](sft, config, fields, options) {
28 
29   import scala.collection.JavaConverters._
30 
31   private val featurePath = config.featurePath.map(JsonPath.compile(_))
32 
33   override protected def parse(is: InputStream, ec: EvaluationContext): CloseableIterator[JsonElement] =
34     new JsonConverter.JsonIterator(is, options.encoding, ec)
35 
36   override protected def values(parsed: CloseableIterator[JsonElement],
37                                 ec: EvaluationContext): CloseableIterator[Array[Any]] = {
38     val array = Array.ofDim[Any](2)
39     featurePath match {
40       case None =>
41         parsed.map { element =>
42           array(0) = element
43           array
44         }
45       case Some(path) =>
46         parsed.flatMap { element =>
47           array(1) = element
48           path.read[JsonArray](element, JsonConverter.JsonConfiguration).iterator.asScala.map { e =>
49             array(0) = e
50             array
51           }
52         }
53     }
54   }
55 }
56 
57 object JsonConverter extends GeoJsonParsing {
58 
59   private [json] val JsonConfiguration =
60     Configuration.builder()
61         .jsonProvider(new GsonJsonProvider)
62         .options(com.jayway.jsonpath.Option.DEFAULT_PATH_LEAF_TO_NULL)
63         .build()
64 
65   private val LineRegex = """JsonReader at line (\d+)""".r
66 
67   case class JsonConfig(
68       `type`: String,
69       converterName: Option[String],
70       featurePath: Option[String],
71       idField: Option[Expression],
72       caches: Map[String, Config],
73       userData: Map[String, Expression]
74     ) extends ConverterConfig with ConverterName
75 
76   sealed trait JsonField extends Field
77 
78   case class DerivedField(name: String, transforms: Option[Expression]) extends JsonField {
79     override val fieldArg: Option[Array[AnyRef] => AnyRef] = None
80   }
81 
82   abstract class TypedJsonField(val jsonType: String) extends JsonField {
83 
84     def path: String
85     def pathIsRoot: Boolean
86 
87     private val i = if (pathIsRoot) { 1 } else { 0 }
88     private val jsonPath = JsonPath.compile(path)
89 
90     protected def unwrap(elem: JsonElement): AnyRef
91 
92     override val fieldArg: Option[Array[AnyRef] => AnyRef] = Some(values)
93 
94     private def values(args: Array[AnyRef]): AnyRef = {
95       val e = try { jsonPath.read[JsonElement](args(i), JsonConfiguration) } catch {
96         case _: PathNotFoundException => JsonNull.INSTANCE
97       }
98       if (e.isJsonNull) { null } else { unwrap(e) }
99     }
100   }
101 
102   case class StringJsonField(name: String, path: String, pathIsRoot: Boolean, transforms: Option[Expression])
103       extends TypedJsonField("string") {
104     override def unwrap(elem: JsonElement): AnyRef = elem.getAsString
105   }
106 
107   case class FloatJsonField(name: String, path: String, pathIsRoot: Boolean, transforms: Option[Expression])
108       extends TypedJsonField("float") {
109     override def unwrap(elem: JsonElement): AnyRef = Float.box(elem.getAsFloat)
110   }
111 
112   case class DoubleJsonField(name: String, path: String, pathIsRoot: Boolean, transforms: Option[Expression])
113       extends TypedJsonField("double") {
114     override def unwrap(elem: JsonElement): AnyRef = Double.box(elem.getAsDouble)
115   }
116 
117   case class IntJsonField(name: String, path: String, pathIsRoot: Boolean, transforms: Option[Expression])
118       extends TypedJsonField("int") {
119     override def unwrap(elem: JsonElement): AnyRef = Int.box(elem.getAsInt)
120   }
121 
122   case class BooleanJsonField(name: String, path: String, pathIsRoot: Boolean, transforms: Option[Expression])
123       extends TypedJsonField("boolean") {
124     override def unwrap(elem: JsonElement): AnyRef = Boolean.box(elem.getAsBoolean)
125   }
126 
127   case class LongJsonField(name: String, path: String, pathIsRoot: Boolean, transforms: Option[Expression])
128       extends TypedJsonField("long") {
129     override def unwrap(elem: JsonElement): AnyRef = Long.box(elem.getAsBigInteger.longValue())
130   }
131 
132   case class GeometryJsonField(name: String, path: String, pathIsRoot: Boolean, transforms: Option[Expression])
133       extends TypedJsonField("geometry") {
134     override def unwrap(elem: JsonElement): AnyRef = parseGeometry(elem)
135   }
136 
137   case class ArrayJsonField(name: String, path: String, pathIsRoot: Boolean, transforms: Option[Expression])
138       extends TypedJsonField("array") {
139     override def unwrap(elem: JsonElement): AnyRef = elem.getAsJsonArray
140   }
141 
142   case class ObjectJsonField(name: String, path: String, pathIsRoot: Boolean, transforms: Option[Expression])
143       extends TypedJsonField("object") {
144     override def unwrap(elem: JsonElement): AnyRef = elem.getAsJsonObject
145   }
146 
147   /**
148     * Parses an input stream into json elements
149     *
150     * @param is input
151     * @param encoding encoding
152     * @param ec context
153     */
154   class JsonIterator private [json] (is: InputStream, encoding: Charset, ec: EvaluationContext)
155       extends CloseableIterator[JsonElement] {
156 
157     private val reader = new JsonReader(new InputStreamReader(is, encoding))
158     reader.setLenient(true)
159 
160     override def hasNext: Boolean = reader.peek() != JsonToken.END_DOCUMENT
161     override def next(): JsonElement = {
162       val res = JsonParser.parseReader(reader)
163       // extract the line number, only accessible from reader.toString
164       LineRegex.findFirstMatchIn(reader.toString).foreach(m => ec.line = m.group(1).toLong)
165       res
166     }
167 
168     override def close(): Unit = reader.close()
169   }
170 }
Line Stmt Id Pos Tree Symbol Tests Code
31 234 1584 - 1603 Apply com.jayway.jsonpath.JsonPath.compile com.jayway.jsonpath.JsonPath.compile(x$1)
31 235 1561 - 1604 Apply scala.Option.map JsonConverter.this.config.featurePath.map[com.jayway.jsonpath.JsonPath](((x$1: String) => com.jayway.jsonpath.JsonPath.compile(x$1)))
34 236 1750 - 1766 Select org.locationtech.geomesa.convert2.AbstractConverter.BasicOptions.encoding JsonConverter.this.options.encoding
34 237 1715 - 1771 Apply org.locationtech.geomesa.convert.json.JsonConverter.JsonIterator.<init> new JsonConverter.JsonIterator(is, JsonConverter.this.options.encoding, ec)
38 238 1968 - 1969 Literal <nosymbol> 2
38 239 1951 - 1970 ApplyToImplicitArgs scala.Array.ofDim scala.Array.ofDim[Any](2)((ClassTag.Any: scala.reflect.ClassTag[Any]))
39 240 1975 - 1986 Select org.locationtech.geomesa.convert.json.JsonConverter.featurePath JsonConverter.this.featurePath
41 242 2022 - 2100 Apply org.locationtech.geomesa.utils.collection.CloseableIterator.map parsed.map[Array[Any]](((element: com.google.gson.JsonElement) => { array.update(0, element); array }))
41 243 2022 - 2100 Block org.locationtech.geomesa.utils.collection.CloseableIterator.map parsed.map[Array[Any]](((element: com.google.gson.JsonElement) => { array.update(0, element); array }))
42 241 2056 - 2074 Apply scala.Array.update array.update(0, element)
46 248 2134 - 2356 Apply org.locationtech.geomesa.utils.collection.CloseableIterator.flatMap parsed.flatMap[Array[Any]](((element: com.google.gson.JsonElement) => { array.update(1, element); scala.collection.JavaConverters.asScalaIteratorConverter[com.google.gson.JsonElement](path.read[com.google.gson.JsonArray](element, JsonConverter.JsonConfiguration).iterator()).asScala.map[Array[Any]](((e: com.google.gson.JsonElement) => { array.update(0, e); array })) }))
46 249 2134 - 2356 Block org.locationtech.geomesa.utils.collection.CloseableIterator.flatMap parsed.flatMap[Array[Any]](((element: com.google.gson.JsonElement) => { array.update(1, element); scala.collection.JavaConverters.asScalaIteratorConverter[com.google.gson.JsonElement](path.read[com.google.gson.JsonArray](element, JsonConverter.JsonConfiguration).iterator()).asScala.map[Array[Any]](((e: com.google.gson.JsonElement) => { array.update(0, e); array })) }))
47 244 2172 - 2190 Apply scala.Array.update array.update(1, element)
48 245 2201 - 2272 Apply com.google.gson.JsonArray.iterator path.read[com.google.gson.JsonArray](element, JsonConverter.JsonConfiguration).iterator()
48 247 2201 - 2346 Apply scala.collection.Iterator.map scala.collection.JavaConverters.asScalaIteratorConverter[com.google.gson.JsonElement](path.read[com.google.gson.JsonArray](element, JsonConverter.JsonConfiguration).iterator()).asScala.map[Array[Any]](((e: com.google.gson.JsonElement) => { array.update(0, e); array }))
49 246 2304 - 2316 Apply scala.Array.update array.update(0, e)
63 250 2462 - 2617 Apply com.jayway.jsonpath.Configuration.ConfigurationBuilder.build com.jayway.jsonpath.Configuration.builder().jsonProvider(new com.jayway.jsonpath.spi.json.GsonJsonProvider()).options(DEFAULT_PATH_LEAF_TO_NULL).build()
65 251 2645 - 2675 Literal <nosymbol> "JsonReader at line (\\d+)"
65 252 2645 - 2677 Select scala.collection.immutable.StringLike.r scala.Predef.augmentString("JsonReader at line (\\d+)").r
79 253 3151 - 3155 Select scala.None scala.None
87 254 3310 - 3320 Select org.locationtech.geomesa.convert.json.JsonConverter.TypedJsonField.pathIsRoot TypedJsonField.this.pathIsRoot
87 255 3324 - 3325 Literal <nosymbol> 1
87 256 3324 - 3325 Block <nosymbol> 1
87 257 3335 - 3336 Literal <nosymbol> 0
87 258 3335 - 3336 Block <nosymbol> 0
88 259 3383 - 3387 Select org.locationtech.geomesa.convert.json.JsonConverter.TypedJsonField.path TypedJsonField.this.path
88 260 3366 - 3388 Apply com.jayway.jsonpath.JsonPath.compile com.jayway.jsonpath.JsonPath.compile(TypedJsonField.this.path)
92 261 3509 - 3515 Apply org.locationtech.geomesa.convert.json.JsonConverter.TypedJsonField.values TypedJsonField.this.values(args)
92 262 3504 - 3516 Apply scala.Some.apply scala.Some.apply[Array[AnyRef] => AnyRef]({ ((args: Array[AnyRef]) => TypedJsonField.this.values(args)) })
95 263 3626 - 3627 Select org.locationtech.geomesa.convert.json.JsonConverter.TypedJsonField.i TypedJsonField.this.i
95 264 3621 - 3628 Apply scala.Array.apply args.apply(TypedJsonField.this.i)
95 265 3630 - 3647 Select org.locationtech.geomesa.convert.json.JsonConverter.JsonConfiguration JsonConverter.this.JsonConfiguration
95 266 3594 - 3648 Apply com.jayway.jsonpath.JsonPath.read TypedJsonField.this.jsonPath.read[com.google.gson.JsonElement](args.apply(TypedJsonField.this.i), JsonConverter.this.JsonConfiguration)
95 267 3594 - 3648 Block com.jayway.jsonpath.JsonPath.read TypedJsonField.this.jsonPath.read[com.google.gson.JsonElement](args.apply(TypedJsonField.this.i), JsonConverter.this.JsonConfiguration)
96 268 3700 - 3717 Select com.google.gson.JsonNull.INSTANCE com.google.gson.JsonNull.INSTANCE
96 269 3700 - 3717 Block com.google.gson.JsonNull.INSTANCE com.google.gson.JsonNull.INSTANCE
98 270 3736 - 3748 Apply com.google.gson.JsonElement.isJsonNull e.isJsonNull()
98 271 3752 - 3756 Literal <nosymbol> null
98 272 3752 - 3756 Block <nosymbol> null
98 273 3766 - 3775 Apply org.locationtech.geomesa.convert.json.JsonConverter.TypedJsonField.unwrap TypedJsonField.this.unwrap(e)
98 274 3766 - 3775 Block org.locationtech.geomesa.convert.json.JsonConverter.TypedJsonField.unwrap TypedJsonField.this.unwrap(e)
104 275 3993 - 4009 Apply com.google.gson.JsonElement.getAsString elem.getAsString()
109 276 4227 - 4242 Apply com.google.gson.JsonElement.getAsFloat elem.getAsFloat()
109 277 4217 - 4243 Apply scala.Float.box scala.Float.box(elem.getAsFloat())
114 278 4464 - 4480 Apply com.google.gson.JsonElement.getAsDouble elem.getAsDouble()
114 279 4453 - 4481 Apply scala.Double.box scala.Double.box(elem.getAsDouble())
119 280 4693 - 4706 Apply com.google.gson.JsonElement.getAsInt elem.getAsInt()
119 281 4685 - 4707 Apply scala.Int.box scala.Int.box(elem.getAsInt())
124 282 4931 - 4948 Apply com.google.gson.JsonElement.getAsBoolean elem.getAsBoolean()
124 283 4919 - 4949 Apply scala.Boolean.box scala.Boolean.box(elem.getAsBoolean())
129 284 5164 - 5196 Apply java.math.BigInteger.longValue elem.getAsBigInteger().longValue()
129 285 5155 - 5197 Apply scala.Long.box scala.Long.box(elem.getAsBigInteger().longValue())
134 286 5411 - 5430 Apply org.locationtech.geomesa.convert.json.GeoJsonParsing.parseGeometry JsonConverter.this.parseGeometry(elem)
139 287 5638 - 5657 Apply com.google.gson.JsonElement.getAsJsonArray elem.getAsJsonArray()
144 288 5867 - 5887 Apply com.google.gson.JsonElement.getAsJsonObject elem.getAsJsonObject()
157 289 6243 - 6245 Select org.locationtech.geomesa.convert.json.JsonConverter.JsonIterator.is JsonIterator.this.is
157 290 6247 - 6255 Select org.locationtech.geomesa.convert.json.JsonConverter.JsonIterator.encoding JsonIterator.this.encoding
157 291 6221 - 6256 Apply java.io.InputStreamReader.<init> new java.io.InputStreamReader(JsonIterator.this.is, JsonIterator.this.encoding)
157 292 6206 - 6257 Apply com.google.gson.stream.JsonReader.<init> new com.google.gson.stream.JsonReader(new java.io.InputStreamReader(JsonIterator.this.is, JsonIterator.this.encoding))
158 293 6262 - 6285 Apply com.google.gson.stream.JsonReader.setLenient JsonIterator.this.reader.setLenient(true)
160 294 6323 - 6362 Apply java.lang.Object.!= JsonIterator.this.reader.peek().!=(END_DOCUMENT)
162 295 6443 - 6449 Select org.locationtech.geomesa.convert.json.JsonConverter.JsonIterator.reader JsonIterator.this.reader
162 296 6420 - 6450 Apply com.google.gson.JsonParser.parseReader com.google.gson.JsonParser.parseReader(JsonIterator.this.reader)
164 297 6555 - 6570 Apply com.google.gson.stream.JsonReader.toString JsonIterator.this.reader.toString()
164 298 6595 - 6605 Apply scala.util.matching.Regex.MatchData.group m.group(1)
164 299 6595 - 6612 Select scala.collection.immutable.StringLike.toLong scala.Predef.augmentString(m.group(1)).toLong
164 300 6585 - 6612 Apply org.locationtech.geomesa.convert.EvaluationContext.line_= JsonIterator.this.ec.line_=(scala.Predef.augmentString(m.group(1)).toLong)
164 301 6528 - 6613 Apply scala.Option.foreach JsonConverter.this.LineRegex.findFirstMatchIn(JsonIterator.this.reader.toString()).foreach[Unit](((m: scala.util.matching.Regex.Match) => JsonIterator.this.ec.line_=(scala.Predef.augmentString(m.group(1)).toLong)))
168 302 6664 - 6678 Apply com.google.gson.stream.JsonReader.close JsonIterator.this.reader.close()