3

Consider this JSON:

{
  "myDocument": {
    "static_key": "value",
    "dynamic_key": "value",
    "static_key2": "value2",
    "dynamic_key2": {
      "dynamic_key3": "value3"
    }
  }
}

The JSON documents that I'm going to process have some static keys (fields that I know that are going to be present always) but has some others that could be or not be present, with names unbeknownst in advance for mapping them to some case class.

I have the absolute path to the field in a String (retrieved from a configuration stored in a Database) with the following structure:

"myDocument.dynamic_key2.dynamic_key3"

I know that I need to have an ADT for mapping back and forth, so I came to this:

sealed trait Data

final case class StringTuple(key: String, value: String) extends Data

object StringTuple {

  implicit val encoder: Encoder[StringTuple] = deriveEncoder[StringTuple]
  implicit val decoder: Decoder[StringTuple] = deriveDecoder[StringTuple]
}

final case class NumericTuple(key: String, value: Double) extends Data

object NumericTuple {

  implicit val encoder: Encoder[NumericTuple] = deriveEncoder[NumericTuple]
  implicit val decoder: Decoder[NumericTuple] = deriveDecoder[NumericTuple]
}

final case class DateTuple(key: String, value: OffsetDateTime) extends Data

object DateTuple {

  implicit val encoder: Encoder[DateTuple] = deriveEncoder[DateTuple]
  implicit val decoder: Decoder[DateTuple] = deriveDecoder[DateTuple]
}

final case class TransformedJson(data: Data*)

object TransformedJson {

  def apply(data: Data*): TransformedJson = new TransformedJson(data: _*)

  implicit val encoder: Encoder[TransformedJson] = deriveEncoder[TransformedJson]
  implicit val decoder: Decoder[TransformedJson] = deriveDecoder[TransformedJson]
}

Based on this discussion, it makes no sense to use Map[String, Any] with circe, so I divided the three possible key-value cases that I will encounter when parsing individual fields:

  • A numeric field, that I'm going to parse as Double.
  • A String field parsed as is (String).
  • A date field parsed as OffsetDateTime.

For that reason, I've created three case classes that model these combinations (NumericTuple, StringTuple and DateTuple) and my idea is to produce an output JSON like this:

{
  "dynamic_key": "extractedValue",
  "dynamic_key3": "extractedValue3",
  ...
}

("Plain", with no nesting at all).

My idea is to create a list of Data objects to achieve this and I have something like this:

def extractValue(confElement: Conf, json: String) = {
    val cursor: HCursor = parse(json).getOrElse(Json.Null).hcursor
    val decodeDynamicParam = Decoder[NumericTuple].prepare(
      /*
          Here I think (not sure) that I can extract the value with the decoder,
          but, how can I extract the key name and set it, alongside with the extracted
          value?
       */
      _.downField(confElement.path)
    )
  }

Some considerations:

  1. Based on Travis' response to this question I'm trying to model the JSON as closely as possible for working with circe. That's why I tried with the tuples model.
  2. Based (again) on Travis's response to this SO question is that I'm trying with Decode.prepare(...) method. And here comes my question...

Question: How do I extract the specific key name of the current cursor position and map it to the Tuple? I need only the current key, not all the key set that .keys method of ACursor returns. With that key, I want to map manually the Tuple with the current key name and the extracted value.

For summing it up, my need is to transform a structure that has some unknown keys (name and position), extract their values based on the absolute-dot-separated path that I have and lift both the key name and the value name to a case class that I suffixed as Tuple.

Can you shed some light about this?

Thanks

Alejandro Echeverri
  • 1,328
  • 2
  • 19
  • 32

0 Answers0