-1

Not sure where I am going wrong with this it is returning error:

No Json serializer as JsObject found for type List[QM_Category].

Try to implement an implicit OWrites or OFormat for this type.

[error] Json.stringify(Json.toJsObject(a.categories))

Is there a way to define a format for just List[QM_Category]? I thought the format for QM_Category would handle the case class and play is supposed to handle Lists... All I really want to do is take my List and convert it to json string. Pretty straight forward but I am not sure why Play Json doesnt like my format.

Here is my code:

case class QM_Answer (
    answerid: String,
    answerstring: String,
    answerscore: Int
);

case class QM_Question (
    questionid: String,
    questionscore: Int,
    questiongoal: Int,
    questionstring: String,
    questiontype: String,
    questioncomments: String,
    questionisna: Boolean,
    questionishidden: Boolean,
    failcategory: Boolean,
    failform: Boolean,
    answers: List[QM_Answer]
);

case class QM_Category (
    categoryid: String,
    categoryname: String,
    categoryscore: Int,
    categorygoal: Int,
    categorycomments: String,
    categoryishidden: Boolean,
    failcategory: Boolean,
    questions: List[QM_Question]
);

case class SurveySourceRaw (
    ownerid: String,
    formid: String,
    formname: String,
    sessionid: String,
    evaluator: String,
    userid: String,
    timelinekey: Long,
    surveyid: String,
    submitteddate: Long,
    month: String,
    channel: String,
    categories: List[QM_Category]
);

case class SurveySource (
    ownerid: String,
    formid: String,
    formname: String,
    sessionid: String,
    evaluator: String,
    userid: String,
    timelinekey: Long,
    surveyid: String,
    submitteddate: Long,
    month: String,
    channel: String,
    categories: String
);

implicit val qmAnswerFormat = Json.format[QM_Answer];
implicit val qmQuestionFormat = Json.format[QM_Question];
implicit val qmCategoryFormat = Json.format[QM_Category];
implicit val surveySourceRawFormat = Json.format[SurveySourceRaw];

var surveySourceRaw = sc
    .cassandraTable[SurveySourceRaw]("mykeyspace", "mytablename") 
    .select("ownerid",
            "formid",
            "formname",
            "sessionid",
            "evaluator",
            "userid",
            "timelinekey",
            "surveyid",
            "submitteddate",
            "month",
            "channel",
            "categories")

var surveyRelational = surveySourceRaw
        .map(a => SurveySource
            (
                a.ownerid,
                a.formid,
                a.formname,
                a.sessionid,
                a.evaluator,
                a.userid,
                a.timelinekey,
                a.surveyid,
                a.submitteddate,
                a.month,
                a.channel,
                Json.stringify(Json.toJsObject(a.categories))
            )) 


Gaël J
  • 11,274
  • 4
  • 17
  • 32
phattyD
  • 205
  • 3
  • 11
  • This of course works great: Json.stringify(Json.obj("categories" -> a.categories)) however It nests the actually jsonarray under the categories attribute when all I want is the jsonarray in string form. – phattyD Jul 08 '21 at 17:57
  • What output do you want? It's not clear. – Gaël J Jul 08 '21 at 18:26
  • @GaëlJ I want my categories of type List[QM_Category] from my SurveySourceRaw case class to be converted to stringified json. – phattyD Jul 09 '21 at 05:12

2 Answers2

2

The Play JSON format for a List[A], given a format for A, encodes/decodes a JSON array, e.g. for a List[String] [ "foo", "bar", "baz" ]. A JSON array is not a JSON object.

So if you want the List[QM_Category] to be a stringified JSON (but not necessarily a JSON object, e.g. it could be a string, array, etc.), you can use toJson:

Json.stringify(Json.toJson(a.categories))

Alternatively, if you want it to be a JSON object, you would need to define an OFormat (or an OReads/OWrites combination) for List[QM_Category]: an OFormat is a Format which requires that the JSON be an object with string attributes and JSON values (and so forth for OReads/OWrites).

Levi Ramsey
  • 18,884
  • 1
  • 16
  • 30
  • How does the OFormat differ from Format in its declaration? I defined the implicit format for the case class which included the List, but I think that is a level to high so I would need to declare a format specifically for the List[QM_Category]? How does one declare a format for that? I haven't seen examples for that anywhere in my searches, its always been for the whole class. – phattyD Jul 09 '21 at 05:15
  • Also, it appears that I will have to define the OFormat, since the code you provided gave me a runtime error: Serialization stack: - object not serializable (class: play.api.libs.json.OFormat$$anon$1, value: play.api.libs.json.OFormat$$anon$1@308a9264) - field (class: MasterCallSummaryCassQueue$$anonfun$29, name: qmCategoryFormat$1, type: interface play.api.libs.json.OFormat) - object (class MasterCallSummaryCassQueue$$anonfun$29, ) – phattyD Jul 09 '21 at 05:18
  • 1
    The key point of this answer is to use `toJson` rather than `toJsObject` as your data is not a JSON object but an array: using `toJson` will build the right type automatically. – Gaël J Jul 09 '21 at 16:34
  • An `OFormat` is a `Format` which can only work with JSON objects. `{ "foo": "bar", "baz": [ "qux", "quux" ] }` is a JSON object. `[ "foo", "bar", "baz" ]` is a JSON array. JSON objects (`JsObject` in Play) and JSON arrays (`JsArray` in Play) are both JSON (`JsValue` in Play) – Levi Ramsey Jul 09 '21 at 17:13
0

I'm almost embarresed to answer this but sometimes I make it overly complicated. The answer was to just read the column from cassandra as a string instead of a List[QM_Category]. The column in cassandra was defined as:

categories list<FROZEN<qm.category>>,

I wrongfully assumed I would need to read it in from Cassandra as a list of custom objects. I would then need to use play json to format that class into JSON and then stringify it.

case class SurveySourceRaw (
    ownerid: String,
    formid: String,
    formname: String,
    sessionid: String,
    evaluator: String,
    userid: String,
    timelinekey: Long,
    surveyid: String,
    submitteddate: Long,
    month: String,
    channel: String,
    categories: List[QM_Category]
);

When in reality, all I needed to do was read it from cassandra as a type String and it came in as a stringified json. Well played spark cassandra connector, well played.

case class SurveySourceRaw (
    ownerid: String,
    formid: String,
    formname: String,
    sessionid: String,
    evaluator: String,
    userid: String,
    timelinekey: Long,
    surveyid: String,
    submitteddate: Long,
    month: String,
    channel: String,
    categories: String
);
phattyD
  • 205
  • 3
  • 11