extract case classes from json file scala play - json

im trying to extract my data from json into a case class without success.
the Json file:
[
{
"name": "bb",
"loc": "sss",
"elements": [
{
"name": "name1",
"loc": "firstHere",
"elements": []
}
]
},
{
"name": "ca",
"loc": "sss",
"elements": []
}
]
my code :
case class ElementContainer(name : String, location : String,elements : Seq[ElementContainer])
object elementsFormatter {
implicit val elementFormatter = Json.format[ElementContainer]
}
object Applicationss extends App {
val el = new ElementContainer("name1", "firstHere", Seq.empty)
val el1Cont = new ElementContainer("bb","sss", Seq(el))
val source:String=Source.fromFile("src/bin/elementsTree.json").getLines.mkString
val jsonFormat = Json.parse(source)
val r1= Json.fromJson[ElementContainer](jsonFormat)
}
after running this im getting inside r1:
JsError(List((/elements,List(ValidationError(List(error.path.missing),WrappedArray()))), (/name,List(ValidationError(List(error.path.missing),WrappedArray()))), (/location,List(ValidationError(List(error.path.missing),WrappedArray())))))
been trying to extract this data forever, please advise

You have location instead loc and, you'll need to parse file into a Seq[ElementContainer], since it's an array, not a single ElementContainer:
Json.fromJson[Seq[ElementContainer]](jsonFormat)
Also, you have the validate method that will return you either errors or parsed json object..

Related

Deep remove specific empty json array in circe scala

I want to remove deep empty json array from my json before/during processing by circe.
Incoming JSON
{
"config": {
"newFiles": [{
"type": "audio",
"value": "welcome1.mp3"
}],
"oldFiles": [],
"channel": "BC"
}
}
or
{
"config": {
"newFiles": [],
"oldFiles": [{
"type": "audio",
"value": "welcome1.mp3"
}],
"channel": "BC"
}
}
Resulted Json should look like
{
"config": {
"newFiles": [{
"type": "audio",
"value": "welcome1.mp3"
}],
"channel": "BC"
}
}
or
{
"config": {
"oldFiles": [{
"type": "audio",
"value": "welcome1.mp3"
}],
"channel": "BC"
}
}
What i understand that this can be done before decoding config as well as during decoding config.
The idea here is i want to handle only one of files (either new or old) at my case class level.
Method 1: Tried at config decoding level which works well.
case class File(`type`: String, value: String)
case class Config(files: List[File],
channel: String = "BC")
object Config{
implicit final val FileDecoder: Decoder[File] = deriveDecoder[File]
implicit val ConfigDecoder: Decoder[Config] = (h:HCursor) =>
for {
oldFiles <- h.get[List[File]]("oldFiles")
files <- if (oldFiles.isEmpty) h.get[List[File]]("newFiles") else h.get[List[File]]("oldFiles")
channel <- h.downField("channel").as[String]
}yield{
Config(files, channel)
}
}
case class Inventory(config: Config)
object Inventory {
implicit val InventoryDecoder: Decoder[Inventory] = deriveDecoder[Inventory]
}
Method 2: Tried before feeding into decoding which didn't worked out
Let me know what could be the elegant approach to handle it.
PS: I have multiple similar config decoders and if i handle this at config decoding level then there will be a lot of boiler plate code.
I have simplified the problem a little bit again, but combining this with the previous answer should be simple.
I also took advantage of cats.data.NonEmptyList
final case class Config(files: NonEmptyList[String], channel: String = "BC")
object Config {
implicit final val ConfigDecoder: Decoder[Config] =
(
(Decoder[NonEmptyList[String]].at(field = "newFiles") or Decoder[NonEmptyList[String]].at(field = "oldFiles")),
Decoder[String].at(field = "channel")
).mapN(Config.apply).at(field = "config")
}
This can be used like this:
val data =
"""[
{
"config": {
"newFiles": ["Foo", "Bar", "Baz"],
"oldFiles": [],
"channel": "BC"
}
},
{
"config": {
"newFiles": [],
"oldFiles": ["Quax"],
"channel": "BC"
}
}
]"""
parser.decode[List[Config]](data)
// res: Either[io.circe.Error, List[Config]] =
// Right(List(
// Config(NonEmptyList("Foo", "Bar", "Baz"), "BC"),
// Config(NonEmptyList("Quax"), "BC")
// ))
Note: I am assuming that at least one of the two lists must be non-empty and give priority to the new one.
You can see the code running here.

parsing json object with number as its key fields?

I'm trying to parse json into kotlin objects but the problem is that its key fields are numbers any idea how can parse them , I've tried serialized name but still facing problem.
The json response looks like this :
{
"Id": [{
"1": {
"name": "name1",
"class": "11a"
}
},
{
"2": {
"name": "name2",
"class": "11b"
}
}
]
}
I'm using gson and the main thing i'm trying to do is to store this number fields as some other string objects.
You can parse them into a list of maps, then "map" those to your data classes instead:
val input = """{
"Id": [{
"1": {
"name": "name1",
"class": "11a"
}
},
{
"2": {
"name": "name2",
"class": "11b"
}
}
]
}"""
val gson = Gson()
val parsed: Map<String, List<Map<String, Any>>> =
gson.fromJson(input, (object : TypeToken<Map<String, List<Map<String, Any>>>>(){}).type)
println(parsed["Id"]?.get(0)?.get("1")) // {name=name1, class=11a}
It will have some nasty generic signature, though.
If you're working with Kotlin, take a look at Klaxon, it will improve your experience.

How to insert an empty object into JSON using Circe?

I'm getting a JSON object over the network, as a String. I'm then using Circe to parse it. I want to add a handful of fields to it, and then pass it on downstream.
Almost all of that works.
The problem is that my "adding" is really "overwriting". That's actually ok, as long as I add an empty object first. How can I add such an empty object?
So looking at the code below, I am overwriting "sometimes_empty:{}" and it works. But because sometimes_empty is not always empty, it results in some data loss. I'd like to add a field like: "custom:{}" and then ovewrite the value of custom with my existing code.
Two StackOverflow posts were helpful. One worked, but wasn't quite what I was looking for. The other I couldn't get to work.
1: Modifying a JSON array in Scala with circe
2: Adding field to a json using Circe
val js: String = """
{
"id": "19",
"type": "Party",
"field": {
"id": 1482,
"name": "Anne Party",
"url": "https"
},
"sometimes_empty": {
},
"bool": true,
"timestamp": "2018-12-18T11:39:18Z"
}
"""
val newJson = parse(js).toOption
.flatMap { doc =>
doc.hcursor
.downField("sometimes_empty")
.withFocus(_ =>
Json.fromFields(
Seq(
("myUrl", Json.fromString(myUrl)),
("valueZ", Json.fromString(valueZ)),
("valueQ", Json.fromString(valueQ)),
("balloons", Json.fromString(balloons))
)
)
)
.top
}
newJson match {
case Some(v) => return v.toString
case None => println("Failure!")
}
We need to do a couple of things. First, we need to zoom in on the specific property we want to update, if it doesn't exist, we'll create a new empty one. Then, we turn the zoomed in property in the form of a Json into JsonObject in order to be able to modify it using the +: method. Once we've done that, we need to take the updated property and re-introduce it in the original parsed JSON to get the complete result:
import io.circe.{Json, JsonObject, parser}
import io.circe.syntax._
object JsonTest {
def main(args: Array[String]): Unit = {
val js: String =
"""
|{
| "id": "19",
| "type": "Party",
| "field": {
| "id": 1482,
| "name": "Anne Party",
| "url": "https"
| },
| "bool": true,
| "timestamp": "2018-12-18T11:39:18Z"
|}
""".stripMargin
val maybeAppendedJson =
for {
json <- parser.parse(js).toOption
sometimesEmpty <- json.hcursor
.downField("sometimes_empty")
.focus
.orElse(Option(Json.fromJsonObject(JsonObject.empty)))
jsonObject <- json.asObject
emptyFieldJson <- sometimesEmpty.asObject
appendedField = emptyFieldJson.+:("added", Json.fromBoolean(true))
res = jsonObject.+:("sometimes_empty", appendedField.asJson)
} yield res
maybeAppendedJson.foreach(obj => println(obj.asJson.spaces2))
}
}
Yields:
{
"id" : "19",
"type" : "Party",
"field" : {
"id" : 1482,
"name" : "Anne Party",
"url" : "https"
},
"sometimes_empty" : {
"added" : true,
"someProperty" : true
},
"bool" : true,
"timestamp" : "2018-12-18T11:39:18Z"
}

How to parse just part of JSON with Klaxon?

I'm trying to parse some JSON to kotlin objects. The JSON looks like:
{
data: [
{ "name": "aaa", "age": 11 },
{ "name": "bbb", "age": 22 },
],
otherdata : "don't need"
}
I just need to data part of the entire JSON, and parse each item to a User object:
data class User(name:String, age:Int)
But I can't find an easy way to do it.
Here's one way you can achieve this
import com.beust.klaxon.Klaxon
import java.io.StringReader
val json = """
{
"data": [
{ "name": "aaa", "age": 11 },
{ "name": "bbb", "age": 22 },
],
"otherdata" : "not needed"
}
""".trimIndent()
data class User(val name: String, val age: Int)
fun main(args: Array<String>) {
val klaxon = Klaxon()
val parsed = klaxon.parseJsonObject(StringReader(json))
val dataArray = parsed.array<Any>("data")
val users = dataArray?.let { klaxon.parseFromJsonArray<User>(it) }
println(users)
}
This will work as long as you can fit the whole json string in memory. Otherwise you may want to look into the streaming API: https://github.com/cbeust/klaxon#streaming-api

Ruby: How to parse json to specific types

I have a JSON that I want to parse in Ruby. Ruby is completely new to me, but I have to work with it :-)
Here is my litte snippet, that should do the parsing:
response = File.read("app/helpers/example_announcement.json")
JSON.parse(response)
this works pretty fine. The only downside is, I do not know the properties at the point where I use it, it is not typesafe. So I created the objects for it
class Announcements
##announcements = Hash # a map key => value where key is string and value is type of Announcement
end
class Announcement
##name = ""
##status = ""
##rewards = Array
end
And this is how the json looks like
{
"announcements": {
"id1" : {
"name": "The Diamond Announcement",
"status": "published",
"reward": [
{
"id": "hardCurrency",
"amount": 100
}
]
},
"id2": {
"name": "The Normal Announcement",
"players": [],
"status": "published",
"reward": []
}
}
}
So I tried JSON parsing like this
response = File.read("app/helpers/example_announcement.json")
JSON.parse(response, Announcements)
But this is not how it works^^can anybody help me with this?