I have a data frame with a schema that looks like this:
root
|-- Id: integer (nullable = true)
|-- FirstName: string (nullable = true)
|-- LastName: string (nullable = true)
|-- Age: integer (nullable = true)
I want to produce output like this (below) where each row is a JSON array
[1, "Smith", {"F": "John", "L":"Smith"}]
[2, "Doe", {"F": "Jane", "L":"Doe"}]
I tried it like this:
val df2 = df
.withColumn("MyStruct", to_json(struct(col("FirstName").as("F"), col("LastName").as("L"))))
.withColumn("Value", array($"Id",$"Last",$"MyStruct"))
.select(to_json($"Value").as("output"))
df2.write
.mode("overwrite")
.option("header", false)
.text("./df3")
This produces valid JSON, but the JSON blob/structure in each row is an encoded value. :(
["1","Smith","{\"F\":\"John\",\"L\":\"Smith\"}"]
["2","Doe","{\"F\":\"Jane\",\"L\":\"Doe\"}"]
Here's what I'm certain I am doing wrong: I am creating a struct wrapped with to_json, but I call to_json on this again (a second time), which makes it an an escaped JSON string.
When I call df2.write, I'm purposely writing text and not json. When I use JSON, then the ID and Last Name values, in what's meant to be a JSON array, are printed out as key/value pairs inside a JSON blob, which isn't what I want.
So then I tried to avoid the second parse_json call.
I swapped
val df2 = df
.withColumn("MyStruct", to_json(struct(col("FirstName").as("F"), col("LastName").as("L"))))
.withColumn("Value", array($"Id",$"Last",$"MyStruct"))
.select(to_json($"Value").as("output"))
with
val df2 = df
.withColumn("MyStruct", to_json(struct(col("FirstName").as("F"), col("LastName").as("L"))))
.withColumn("Value", array($"Id",$"Last",$"MyStruct"))
.select($"Value".cast("string").as("output"))
This looks a bit better:
[1, Smith, {"F":"John","L":"Smith"}]
[2, Doe, {"F":"Jane","L":"Doe"}]
The JSON blob is no longer escaped, but these records are no longer valid JSON. I could put quotation marks manually around the last name strings, but surely there's a cleaner, more idiomatic solution.