kotlinx.serialization.MissingFieldException: Field 'X' is required for type with serial name but it was missing Error in kotlin - json

I am making a dictionary application and I have my own json dataset inside the asset file. I read from this data set for the first time and save it to the room, and then I read from the room for other times. However, I am encountering this error.
E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.enestigli.dictionaryapp, PID: 1868
kotlinx.serialization.MissingFieldException: Field 'example' is required for type with serial name 'com.enestigli.dictionaryapp.data.locale.datamodel.Idioms', but it was missing
at kotlinx.serialization.internal.PluginExceptionsKt.throwMissingFieldException(PluginExceptions.kt:20)
at com.enestigli.dictionaryapp.data.locale.datamodel.Idioms.<init>(Idiom.kt:28)
at com.enestigli.dictionaryapp.data.locale.datamodel.Idioms$$serializer.deserialize(Idiom.kt:28)
at com.enestigli.dictionaryapp.data.locale.datamodel.Idioms$$serializer.deserialize(Idiom.kt:28)
at kotlinx.serialization.json.internal.PolymorphicKt.decodeSerializableValuePolymorphic(Polymorphic.kt:59)
at kotlinx.serialization.json.internal.StreamingJsonDecoder.decodeSerializableValue(StreamingJsonDecoder.kt:36)
at kotlinx.serialization.encoding.AbstractDecoder.decodeSerializableValue(AbstractDecoder.kt:43)
at kotlinx.serialization.encoding.AbstractDecoder.decodeSerializableElement(AbstractDecoder.kt:70)
at kotlinx.serialization.encoding.CompositeDecoder$DefaultImpls.decodeSerializableElement$default(Decoding.kt:535)
at kotlinx.serialization.internal.ListLikeSerializer.readElement(CollectionSerializers.kt:80)
at kotlinx.serialization.internal.AbstractCollectionSerializer.readElement$default(CollectionSerializers.kt:51)
at kotlinx.serialization.internal.AbstractCollectionSerializer.merge(CollectionSerializers.kt:36)
at kotlinx.serialization.internal.AbstractCollectionSerializer.deserialize(CollectionSerializers.kt:43)
at kotlinx.serialization.json.internal.PolymorphicKt.decodeSerializableValuePolymorphic(Polymorphic.kt:59)
at kotlinx.serialization.json.internal.StreamingJsonDecoder.decodeSerializableValue(StreamingJsonDecoder.kt:36)
at kotlinx.serialization.encoding.AbstractDecoder.decodeSerializableValue(AbstractDecoder.kt:43)
at kotlinx.serialization.encoding.AbstractDecoder.decodeSerializableElement(AbstractDecoder.kt:70)
at com.enestigli.dictionaryapp.data.locale.datamodel.IdiomItem$$serializer.deserialize(Idiom.kt:15)
at com.enestigli.dictionaryapp.data.locale.datamodel.IdiomItem$$serializer.deserialize(Idiom.kt:15)
at kotlinx.serialization.json.internal.PolymorphicKt.decodeSerializableValuePolymorphic(Polymorphic.kt:59)
at kotlinx.serialization.json.internal.StreamingJsonDecoder.decodeSerializableValue(StreamingJsonDecoder.kt:36)
at kotlinx.serialization.encoding.AbstractDecoder.decodeSerializableValue(AbstractDecoder.kt:43)
at kotlinx.serialization.encoding.AbstractDecoder.decodeSerializableElement(AbstractDecoder.kt:70)
at kotlinx.serialization.encoding.CompositeDecoder$DefaultImpls.decodeSerializableElement$default(Decoding.kt:535)
at kotlinx.serialization.internal.ListLikeSerializer.readElement(CollectionSerializers.kt:80)
at kotlinx.serialization.internal.AbstractCollectionSerializer.readElement$default(CollectionSerializers.kt:51)
at kotlinx.serialization.internal.AbstractCollectionSerializer.merge(CollectionSerializers.kt:36)
at kotlinx.serialization.internal.AbstractCollectionSerializer.deserialize(CollectionSerializers.kt:43)
at kotlinx.serialization.json.internal.PolymorphicKt.decodeSerializableValuePolymorphic(Polymorphic.kt:59)
at kotlinx.serialization.json.internal.StreamingJsonDecoder.decodeSerializableValue(StreamingJsonDecoder.kt:36)
at com.enestigli.dictionaryapp.data.locale.datamodel.IdiomDataModel$$serializer.deserialize-zhuhEdE(Idiom.kt:8)
at com.enestigli.dictionaryapp.data.locale.datamodel.IdiomDataModel$$serializer.deserialize(Idiom.kt:8)
at kotlinx.serialization.json.internal.PolymorphicKt.decodeSerializableValuePolymorphic(Polymorphic.kt:59)
at kotlinx.serialization.json.internal.StreamingJsonDecoder.decodeSerializableValue(StreamingJsonDecoder.kt:36)
at kotlinx.serialization.json.Json.decodeFromString(Json.kt:100)
at com.enestigli.dictionaryapp.data.locale.datasource.AssetDataSource.getIdioms-zhuhEdE(AssetsDataSource.kt:84)
E/AndroidRuntime: at com.enestigli.dictionaryapp.data.repository.IdiomsRepositoryImpl.initData(IdiomsRepositoryImpl.kt:21)
at com.enestigli.dictionaryapp.domain.use_case.idiom.insert.InsertIdiomsUseCase.initIdiomData(InsertIdiomsUseCase.kt:12)
at com.enestigli.dictionaryapp.presentation.SplashScreen.SplashScreenViewModel$insertAllDataToRoomDb$1.invokeSuspend(SplashScreenViewModel.kt:38)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
at android.os.Handler.handleCallback(Handler.java:942)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loopOnce(Looper.java:201)
at android.os.Looper.loop(Looper.java:288)
at android.app.ActivityThread.main(ActivityThread.java:7898)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:936)
Suppressed: kotlinx.coroutines.DiagnosticCoroutineContextException: [StandaloneCoroutine{Cancelling}#5aeb15c, Dispatchers.Main.immediate]
my json dataset looks like this
[
{
"letter": "A",
"idioms": [
{
"idiom": "above board",
"meaning": "If something is above board, it's been done in a legal and honest way.",
"examples": [
"I'm sure the deal was completely above board as I know James well and he'd never do anything illegal or corrupt.",
"The minister claimed all the appointments were above board and denied claims that some positions had been given to his friends."
]
},
{
"idiom": "above the law",
"meaning": "If someone is above the law, they are not subject to the laws of a society.",
"examples": [
"Just because his father is a rich and powerful man, he seems to think he's above the law and he can do whatever he likes.",
"In a democracy, no-one is above the law - not even a president or a prime-minister."
]
},
{
"idiom": "Achilles' heel",
"meaning": "An Achilles' heel is a weakness that could result in failure.",
"examples": [
"He's a good golfer, but his Achilles' heel is his putting and it's often made him lose matches.",
"The country's dependence on imported oil could prove to be its Achilles' heel if prices keep on rising."
]
},
My Data Model
#JvmInline
#Serializable
value class IdiomDataModel(
val allData: List<IdiomItem>
)
#Serializable
data class IdiomItem(
val letter: String,
val idioms: List<Idioms>
) {
fun toEntity() = IdiomEntity(
letter = letter,
idioms = idioms
)
}
#Serializable
data class Idioms(
val idiom: String,
val meaning: String,
val example: List<String>
)
Entity
#Entity(tableName = "idioms")
data class IdiomEntity(
#ColumnInfo(name = "letter") val letter:String,
#ColumnInfo(name = "idioms") val idioms:List<Idioms>,
#PrimaryKey(autoGenerate = true) val uid:Int? = null
)
I think the val example: List<String> in the Idioms data class is from this part. Could it be that this does not exactly match the example list in the Json dataset?

In your Idioms data class, rename example to examples and it should work.
Old:
#Serializable
data class Idioms(
val idiom: String,
val meaning: String,
val example: List<String>
)
New
#Serializable
data class Idioms(
val idiom: String,
val meaning: String,
val examples: List<String>
)

Related

JSON Object Property with space in Scala using Json4s(Jackson)

I have a JSON String in the given below form:
var jStr =
""" {
|"company":{
|"company name":"ABCD"
|},
|"person":[
|{"name":"john",
|"age":"28"
|},
|{
|"name":"AWQ",
|"age":"45"
|}
|]
|}
""".stripMargin
See the property "company name". Due to this I cannot extract it from its json form to its case class without changing the "company name" to "company_name"(or anything else). Below is the code:
import com.fasterxml.jackson.annotation.{JsonCreator, JsonProperty}
import org.json4s._
import org.json4s.native.JsonMethods._
val parseJson = parse(jStr)
var obj = parseJson.extract[Info] // Exception Here
case class Company(cname : String)
case class Info(company: Company,person: List[Person])
case class Person(name : String , age : String)
I tried using #JsonProperty and #JsonCreator but they too failed.
#JsonCreator
case class Company( #JsonProperty("company name") cname : String)
case class Info(company: Company, person: List[Person])
case class Person(name : String , age : String)
I need to map a json property having spaces to its respective case class(which obviously can't have a space!) using Json4s in Scala.
Stacktrace:
Exception in thread "main" org.json4s.package$MappingException: No usable value for company
No usable value for cname
Did not find value which can be converted into java.lang.String
at org.json4s.reflect.package$.fail(package.scala:95)
at org.json4s.Extraction$ClassInstanceBuilder.org$json4s$Extraction$ClassInstanceBuilder$$buildCtorArg(Extraction.scala:548)
at org.json4s.Extraction$ClassInstanceBuilder$$anonfun$3.applyOrElse(Extraction.scala:572)
at org.json4s.Extraction$ClassInstanceBuilder$$anonfun$3.applyOrElse(Extraction.scala:570)
at scala.PartialFunction.$anonfun$runWith$1$adapted(PartialFunction.scala:145)
at scala.collection.mutable.ResizableArray.foreach(ResizableArray.scala:62)
at scala.collection.mutable.ResizableArray.foreach$(ResizableArray.scala:55)
at scala.collection.mutable.ArrayBuffer.foreach(ArrayBuffer.scala:49)
at scala.collection.TraversableLike.collect(TraversableLike.scala:407)
at scala.collection.TraversableLike.collect$(TraversableLike.scala:405)
at scala.collection.AbstractTraversable.collect(Traversable.scala:108)
at org.json4s.Extraction$ClassInstanceBuilder.instantiate(Extraction.scala:570)
at org.json4s.Extraction$ClassInstanceBuilder.result(Extraction.scala:630)
at org.json4s.Extraction$.$anonfun$extract$10(Extraction.scala:416)
at org.json4s.Extraction$.$anonfun$customOrElse$1(Extraction.scala:637)
at scala.PartialFunction.applyOrElse(PartialFunction.scala:127)
at scala.PartialFunction.applyOrElse$(PartialFunction.scala:126)
at scala.PartialFunction$$anon$1.applyOrElse(PartialFunction.scala:257)
at org.json4s.Extraction$.customOrElse(Extraction.scala:637)
at org.json4s.Extraction$.extract(Extraction.scala:408)
at org.json4s.Extraction$.extract(Extraction.scala:40)
at org.json4s.ExtractableJsonAstNode.extract(ExtractableJsonAstNode.scala:21)
at JsonTest.convertToJSON(JsonTest.scala:100)
at Main$.main(Main.scala:8)
at Main.main(Main.scala)
Caused by: org.json4s.package$MappingException: No usable value for cname
Did not find value which can be converted into java.lang.String
at org.json4s.reflect.package$.fail(package.scala:95)
at org.json4s.Extraction$ClassInstanceBuilder.org$json4s$Extraction$ClassInstanceBuilder$$buildCtorArg(Extraction.scala:548)
at org.json4s.Extraction$ClassInstanceBuilder$$anonfun$3.applyOrElse(Extraction.scala:572)
at org.json4s.Extraction$ClassInstanceBuilder$$anonfun$3.applyOrElse(Extraction.scala:570)
at scala.PartialFunction.$anonfun$runWith$1$adapted(PartialFunction.scala:145)
at scala.collection.mutable.ResizableArray.foreach(ResizableArray.scala:62)
at scala.collection.mutable.ResizableArray.foreach$(ResizableArray.scala:55)
at scala.collection.mutable.ArrayBuffer.foreach(ArrayBuffer.scala:49)
at scala.collection.TraversableLike.collect(TraversableLike.scala:407)
at scala.collection.TraversableLike.collect$(TraversableLike.scala:405)
at scala.collection.AbstractTraversable.collect(Traversable.scala:108)
at org.json4s.Extraction$ClassInstanceBuilder.instantiate(Extraction.scala:570)
at org.json4s.Extraction$ClassInstanceBuilder.result(Extraction.scala:630)
at org.json4s.Extraction$.$anonfun$extract$10(Extraction.scala:416)
at org.json4s.Extraction$.$anonfun$customOrElse$1(Extraction.scala:637)
at scala.PartialFunction.applyOrElse(PartialFunction.scala:127)
at scala.PartialFunction.applyOrElse$(PartialFunction.scala:126)
at scala.PartialFunction$$anon$1.applyOrElse(PartialFunction.scala:257)
at org.json4s.Extraction$.customOrElse(Extraction.scala:637)
at org.json4s.Extraction$.extract(Extraction.scala:408)
at org.json4s.Extraction$ClassInstanceBuilder.org$json4s$Extraction$ClassInstanceBuilder$$buildCtorArg(Extraction.scala:534)
... 23 more
Caused by: org.json4s.package$MappingException: Did not find value which can be converted into java.lang.String
at org.json4s.reflect.package$.fail(package.scala:95)
at org.json4s.Extraction$.$anonfun$convert$2(Extraction.scala:735)
at scala.Option.getOrElse(Option.scala:189)
at org.json4s.Extraction$.convert(Extraction.scala:735)
at org.json4s.Extraction$.$anonfun$extract$10(Extraction.scala:410)
at org.json4s.Extraction$.$anonfun$customOrElse$1(Extraction.scala:637)
at scala.PartialFunction.applyOrElse(PartialFunction.scala:127)
at scala.PartialFunction.applyOrElse$(PartialFunction.scala:126)
at scala.PartialFunction$$anon$1.applyOrElse(PartialFunction.scala:257)
at org.json4s.Extraction$.customOrElse(Extraction.scala:637)
at org.json4s.Extraction$.extract(Extraction.scala:408)
at org.json4s.Extraction$ClassInstanceBuilder.org$json4s$Extraction$ClassInstanceBuilder$$buildCtorArg(Extraction.scala:534)
... 42 more
Process finished with exit code 1

Emit Python embedded object as native JSON in YAML document

I'm importing webservice tests from Excel and serialising them as YAML.
But taking advantage of YAML being a superset of JSON I'd like the request part of the test to be valid JSON, i.e. to have delimeters, quotes and commas.
This will allow us to cut and paste requests between the automated test suite and manual test tools (e.g. Postman.)
So here's how I'd like a test to look (simplified):
- properties:
METHOD: GET
TYPE: ADDRESS
Request URL: /addresses
testCaseId: TC2
request:
{
"unitTypeCode": "",
"unitNumber": "15",
"levelTypeCode": "L",
"roadNumber1": "810",
"roadName": "HAY",
"roadTypeCode": "ST",
"localityName": "PERTH",
"postcode": "6000",
"stateTerritoryCode": "WA"
}
In Python, my request object has a dict attribute called fields which is the part of the object to be serialised as JSON. This is what I tried:
import yaml
def request_presenter(dumper, request):
json_string = json.dumps(request.fields, indent=8)
return dumper.represent_str(json_string)
yaml.add_representer(Request, request_presenter)
test = Test(...including embedded request object)
serialised_test = yaml.dump(test)
I'm getting:
- properties:
METHOD: GET
TYPE: ADDRESS
Request URL: /addresses
testCaseId: TC2
request: "{
\"unitTypeCode\": \"\",\n
\"unitNumber\": \"15\",\n
\"levelTypeCode": \"L\",\n
\"roadNumber1\": \"810\",\n
\"roadName\": \"HAY\",\n
\"roadTypeCode\": \"ST\",\n
\"localityName\": \"PERTH\",\n
\"postcode\": \"6000\",\n
\"stateTerritoryCode\": \"WA\"\n
}"
...only worse because it's all on one line and has white space all over the place.
I tried using the | style for literal multi-line strings which helps with the line breaks and escaped quotes (it's more involved but this answer was helpful.) However, escaped or multiline, the result is still a string that will need to be parsed separately.
How can I stop PyYaml analysing the JSON block as a string and make it just accept a block of text as part of the emitted YAML? I'm guessing it's something to do with overriding the emitter but I could use some help. If possible I'd like to avoid post-processing the serialised test to achieve this.
Ok, so this was the solution I came up with. Generate the YAML with a placemarker ahead of time. The placemarker marks the place where the JSON should be inserted, and also defines the root-level indentation of the JSON block.
import os
import itertools
import json
def insert_json_in_yaml(pre_insert_yaml, key, obj_to_serialise):
marker = '%s: null' % key
marker_line = line_of_first_occurrence(pre_insert_yaml, marker)
marker_indent = string_indent(marker_line)
serialised = json.dumps(obj_to_serialise, indent=marker_indent + 4)
key_with_json = '%s: %s' % (key, serialised)
serialised_with_json = pre_insert_yaml.replace(marker, key_with_json)
return serialised_with_json
def line_of_first_occurrence(basestring, substring):
"""
return line number of first occurrence of substring
"""
lineno = lineno_of_first_occurrence(basestring, substring)
return basestring.split(os.linesep)[lineno]
def string_indent(s):
"""
return indentation of a string (no of spaces before a nonspace)
"""
spaces = ''.join(itertools.takewhile(lambda c: c == ' ', s))
return len(spaces)
def lineno_of_first_occurrence(basestring, substring):
"""
return line number of first occurrence of substring
"""
return basestring[:basestring.index(substring)].count(os.linesep)
embedded_object = {
"unitTypeCode": "",
"unitNumber": "15",
"levelTypeCode": "L",
"roadNumber1": "810",
"roadName": "HAY",
"roadTypeCode": "ST",
"localityName": "PERTH",
"postcode": "6000",
"stateTerritoryCode": "WA"
}
yaml_string = """
---
- properties:
METHOD: GET
TYPE: ADDRESS
Request URL: /addresses
testCaseId: TC2
request: null
after_request: another value
"""
>>> print(insert_json_in_yaml(yaml_string, 'request', embedded_object))
- properties:
METHOD: GET
TYPE: ADDRESS
Request URL: /addresses
testCaseId: TC2
request: {
"unitTypeCode": "",
"unitNumber": "15",
"levelTypeCode": "L",
"roadNumber1": "810",
"roadName": "HAY",
"roadTypeCode": "ST",
"localityName": "PERTH",
"postcode": "6000",
"stateTerritoryCode": "WA"
}
after_request: another value

Add optional property with json transformers in playframework 2.4

I can't understand, how can i add optional property with json transformer.
I want merge two json objects (list and calendars) without or with dynamic list of properties (for example without owner):
calendar1 = {id:1, name: "first", description:"my first calendar", owner: 1}
calendar2 = {id:2, name: "second", owner: 1}
list = [{id: 1, settings: []}, {id: 2, settings: []}]
and result must be
{calendars:
[
{id:1, name: "first", description:"my first calendar", settings: []},
{id:2, name: "second", settings: []}
]
}
I'll assume the following json trees
val calendar1 = Json.parse("""{"id":1, "name": "first", "description":"my first calendar", "owner": 1}""")
val calendar2 = Json.parse("""{"id":2, "name": "second", "owner": 1}""")
You need to add settings to each calendar, then remove the owner if it exists.
Putting a value in branch settings is explained in the documentation
val addSettings = __.json.update((__ \ "settings").json.put(Json.arr()))
Dropping the owner is also explained
val removeOwner = (__ \ "owner").json.prune
Now you can define the transformer to be applied to each of your calendar object
val transformer = addSettings andThen removeOwner
With that in place there are multiple options depending on how your data is actually modeled. If you have a Seq of calendars as in
val calendars = Seq(calendar1, calendar2)
you can do
val normalizedCalendars = calendars.map(_.transform(transformer))
This gives you a Seq[JsResult[JsObject]] which you want to transform into a JsResult[Seq[JsObject]].
I am pretty sure there is a way to do it using play's functional syntax (see play.api.libs.functional and play.api.libs.functional.syntax) but this part of play is not well documented and I haven't gotten around to studying Applicatives yet even though I have a feel for what they do.
Instead, I rely on the following code inspired by scala's Future#sequence
def sequence[A, M[X] <: TraversableOnce[X]](in: M[JsResult[A]])(implicit cbf: CanBuildFrom[M[JsResult[A]], A, M[A]]): JsResult[M[A]] = {
val empty: JsResult[mutable.Builder[A, M[A]]] = JsSuccess(cbf(in))
in.foldLeft(empty) {(jracc,jrel) => (jracc,jrel) match {
case (JsSuccess(builder,_), JsSuccess(a,p)) =>JsSuccess(builder+=a, p)
case (ra#JsError(builderErrors), re#JsError(errors)) =>JsError.merge(ra, re)
case (JsSuccess(_,_), re#JsError(errors)) => re
case (ra#JsError(builderErrors), JsSuccess(_,_)) => ra
}} map (_.result())
}
With that you can write :
val calendarArray = sequence(normalizedCalendars).map(v=>Json.obj("calendars"->JsArray(v)))
which will give you a JsResult[JsObject]. As long as your original calendars are indeed JsObjects you will get a JsSuccess. You can verify the output structure with :
calendarArray.foreach(println)
which returns :
{"calendars":[{"id":1,"name":"first","description":"my first calendar","settings":[]},{"id":2,"name":"second","settings":[]}]}
which is the same as what you asked modulo some whitespace
{
"calendars":[
{"id":1,"name":"first","description":"my first calendar","settings":[]},
{"id":2,"name":"second","settings":[]}
]
}
Start with:
scala> case class Calendar(id:Int,name:String,description:Option[String],owner:Int)
defined class Calendar
scala> case class CalendarRow(id:Int,name:String,description:Option[String],settings:Seq[String]=Seq.empty)
defined class CalendarRow
scala> def append(calendars:Calendar*) = calendars.map(c => CalendarRow(c.id,c.name,c.description))
append: (calendars: Calendar*)Seq[CalendarRow]
scala> val calendar1 = Calendar(1,"first",Option("my first calendar"),1)
calendar1: Calendar = Calendar(1,first,Some(my first calendar),1)
scala> val calendar2 = Calendar(2, "second",None,1)
calendar2: Calendar = Calendar(2,second,None,1)
scala> val list = append(calendar1,calendar2)
list: Seq[CalendarRow] = ArrayBuffer(CalendarRow(1,first,Some(my first calendar),List()), CalendarRow(2,second,None,List()))
Many thanks to #Jean and comments in "Unveiling Play 2.1 Json API - Part 3 : JSON Transformers"
It's hard understanding things like and, andThen, andKeep, keepAnd in JSON transformers for me (I can not find any detailed descriptions with examples), but i found some templates for my question:
Optional property in JSON:
With Reader
(__ \ "id").json.pick[JsString].flatMap{
case id if id.equals(JsString(accountId)) =>
(__ \ "primary").json.put(JsBoolean(true))
case _ =>
Reads.pure(Json.obj())
}
With Json.obj()
(__ \ "id").json.pick[JsString].map{
case id if id.equals(JsString(accountId)) =>
Json.obj("primary" -> true)
case _ =>
Json.obj()
}

How to send Json from client with missing fields for its corresponding Case Class after using Json.format function

I have a case Class and its companion object like below. Now, when I send JSON without id, createdAt and deletedAt fields, because I set them elsewhere, I get [NoSuchElementException: JsError.get] error. It's because I do not set above properties.
How could I achieve this and avoid getting the error?
case class Plan(id: String,
companyId: String,
name: String,
status: Boolean = true,
#EnumAs planType: PlanType.Value,
brochureId: Option[UUID],
lifePolicy: Seq[LifePolicy] = Nil,
createdAt: DateTime,
updatedAt: DateTime,
deletedAt: Option[DateTime]
)
object Plan {
implicit val planFormat = Json.format[Plan]
def fromJson(str: JsValue): Plan = Json.fromJson[Plan](str).get
def toJson(plan: Plan): JsValue = Json.toJson(plan)
def toJsonSeq(plan: Seq[Plan]): JsValue = Json.toJson(plan)
}
JSON I send from client
{
"companyId": "e8c67345-7f59-466d-a958-7c722ad0dcb7",
"name": "Creating First Plan with enum Content",
"status": true,
"planType": "Health",
"lifePolicy": []
}
You can introduce another case class just to handle serialization from request:
like this
case class NewPlan(name: String,
status: Boolean = true,
#EnumAs planType: PlanType.Value,
brochureId: Option[UUID],
lifePolicy: Seq[LifePolicy] = Nil
)
and then use this class to populate your Plan class.
The fundamental issue is that by the time a case class is instantiated to represent your data, it must be well-typed. To shoe horn your example data into your example class, the types don't match because some fields are missing. It's literally trying to call the constructor without enough arguments.
You've got a couple options:
You can make a model that represents the incomplete data (as grotrianster suggested).
You can make the possible missing fields Option types.
You can custom-write the Reads part of your Format to introduce intelligent values or dummy values for the missing ones.
Option 3 might look something like:
// Untested for compilation, might need some corrections
val now: DateTime = ...
val autoId = Reads[JsObject] {
case obj: JsObject => JsSuccess(obj \ 'id match {
case JsString(_) => obj
case _ => obj.transform(
__.update((__ \ 'id).json.put("")) andThen
__.update((__ \ 'createdTime).json.put(now)) andThen
__.update((__ \ 'updatedTime).json.put(now))
)
})
case _ => JsError("JsObject expected")
}
implicit val planFormat = Format[Plan](
autoId andThen Json.reads[Plan],
Json.writes[Plan])
Once you do this once, if the issue is the same for all your other models, you can probably abstract it into some Format factory utility function.
This may be slightly cleaner for autoId:
val autoId = Reads[JsObject] {
// Leave it alone if we have an ID already
case obj: JsObject if (obj \ 'id).asOpt[String].isSome => JsSuccess(obj)
// Insert dummy values if we don't have an `id`
case obj: JsObject => JsSuccess(obj.transform(
__.update((__ \ 'id).json.put("")) andThen
__.update((__ \ 'createdTime).json.put(now)) andThen
__.update((__ \ 'updatedTime).json.put(now))
))
case _ => JsError("JsObject expected")
}

Null values in JsObject for Option using play framework 2.1

I've got a case class with some optionals:
case class Person (
name: String,
nationality: Option[String],
email: Option[String],
gender: Option[String]
)
Using play 2.1.3 I'm trying to create a JSON looking like:
{"name": "Joe", "email": "john#doe.com"}
for an object:
val user = new User("Joe, None, Some("john#doe.com"), Some("male"))
with:
val myJson = Json.obj("name" -> user.name,
"nationality" -> user.nationality, "email" -> user.email)
I however get:
{"name": "Joe", "nationality": null, "email": "john#doe.com"}
How can I avoid the nationality with null value in the JSON?
After realizing that the problem was related to the play JSON handling, I managed to find a solution inspired by I need advice on Play's Json and elegant Option handling in the Writes trait. I'm not convinced that this is the most elegant solution out there, but it works:
def writes(person: Person): JsValue = {
JsObject(
Seq[(String, JsValue)]() ++
Some(person.name).map("name" -> JsString(_)) ++
person.email.map("email" -> JsString(_))
)
}