I tried a lot to convert my scala list to Json using while loop; the code is as follows:
var json = null
while (list != null) {
json = new Gson().toJson(list)
}
the json variable must be accessed outside of the loop, so I declared it outside of the loop and initialized with null, but the Scala compiler gives me a type mismatch exception...
Why are you using a while loop to convert a single list to JSON? Until you explain why you need a loop (or, repeated conversions to JSON, more generally speaking), I'd suggest the following trivial snippet:
val json = new Gson().toJson(list)
Note that I've also changed var json to val json.
However, if all you want to know is how to get rid of the type mismatch exception, just change:
var json = null
to
var json: String = null
or
var json: String = _
If you don't declare json to be of type String, Scala will implicitly take it to be (i.e. infer) of type Null, and it's not possible to assign values of type String to a variable of type Null.
This function works fine for List and Map both using plain Scala constructs:
def toJson(a: Any): String = {
a match {
// number
case m: Number => m.toString
// string
case m: String => "\"" + m + "\""
case m: Map[AnyRef, AnyRef] => {
"{" + (m map { x => val key = x._1; toJson(key) + ": " + toJson(m(key)) } mkString (", ")) + "}"
}
case l: Seq[AnyRef] => { "[" + (l map (toJson(_)) mkString (",")) + "]" }
// for anything else: tuple
case m: Product => toJson(m.productIterator.toList)
case m: AnyRef => "\"" + m.toString + "\""
}
}
Complete example is located here: https://gist.github.com/tuxdna/7926531
Related
The problem I am trying to solve is perfectly described by the following text got from this link:
For a concrete example of when this could be useful, consider an API that supports partial updates of objects. Using this API, a JSON object would be used to communicate a patch for some long-lived object. Any included property specifies that the corresponding value of the object should be updated, while the values for any omitted properties should remain unchanged. If any of the object’s properties are nullable, then a value of null being sent for a property is fundamentally different than a property that is missing, so these cases must be distinguished.
That post presents a solution but using the kotlinx.serialization library, however, I must use gson library for now.
So I am trying to implement my own solution as I didn't find anything that could suit my use case (please let me know if there is).
data class MyObject(
val fieldOne: OptionalProperty<String> = OptionalProperty.NotPresent,
val fieldTwo: OptionalProperty<String?> = OptionalProperty.NotPresent,
val fieldThree: OptionalProperty<Int> = OptionalProperty.NotPresent
)
fun main() {
val gson = GsonBuilder()
.registerTypeHierarchyAdapter(OptionalProperty::class.java, OptionalPropertyDeserializer())
.create()
val json1 = """{
"fieldOne": "some string",
"fieldTwo": "another string",
"fieldThree": 18
}
"""
println("json1 result object: ${gson.fromJson(json1, MyObject::class.java)}")
val json2 = """{
"fieldOne": "some string",
"fieldThree": 18
}
"""
println("json2 result object: ${gson.fromJson(json2, MyObject::class.java)}")
val json3 = """{
"fieldOne": "some string",
"fieldTwo": null,
"fieldThree": 18
}
"""
println("json3 result object: ${gson.fromJson(json3, MyObject::class.java)}")
}
sealed class OptionalProperty<out T> {
object NotPresent : OptionalProperty<Nothing>()
data class Present<T>(val value: T) : OptionalProperty<T>()
}
class OptionalPropertyDeserializer : JsonDeserializer<OptionalProperty<*>> {
private val gson: Gson = Gson()
override fun deserialize(
json: JsonElement?,
typeOfT: Type?,
context: JsonDeserializationContext?
): OptionalProperty<*> {
println("Inside OptionalPropertyDeserializer.deserialize json:$json")
return when {
// Is it a JsonObject? Bingo!
json?.isJsonObject == true ||
json?.isJsonPrimitive == true-> {
// Let's try to extract the type in order
// to deserialize this object
val parameterizedType = typeOfT as ParameterizedType
// Returns an Present with the value deserialized
return OptionalProperty.Present(
context?.deserialize<Any>(
json,
parameterizedType.actualTypeArguments[0]
)!!
)
}
// Wow, is it an array of objects?
json?.isJsonArray == true -> {
// First, let's try to get the array type
val parameterizedType = typeOfT as ParameterizedType
// check if the array contains a generic type too,
// for example, List<Result<T, E>>
if (parameterizedType.actualTypeArguments[0] is WildcardType) {
// In case of yes, let's try to get the type from the
// wildcard type (*)
val internalListType = (parameterizedType.actualTypeArguments[0] as WildcardType).upperBounds[0] as ParameterizedType
// Deserialize the array with the base type Any
// It will give us an array full of linkedTreeMaps (the json)
val arr = context?.deserialize<Any>(json, parameterizedType.actualTypeArguments[0]) as ArrayList<*>
// Iterate the array and
// this time, try to deserialize each member with the discovered
// wildcard type and create new array with these values
val result = arr.map { linkedTreeMap ->
val jsonElement = gson.toJsonTree(linkedTreeMap as LinkedTreeMap<*, *>).asJsonObject
return#map context.deserialize<Any>(jsonElement, internalListType.actualTypeArguments[0])
}
// Return the result inside the Ok state
return OptionalProperty.Present(result)
} else {
// Fortunately it is a simple list, like Array<String>
// Just get the type as with a JsonObject and return an Ok
return OptionalProperty.Present(
context?.deserialize<Any>(
json,
parameterizedType.actualTypeArguments[0]
)!!
)
}
}
// It is not a JsonObject or JsonArray
// Let's returns the default state NotPresent.
else -> OptionalProperty.NotPresent
}
}
}
I got most of the code for the custom deserializer from here.
This is the output when I run the main function:
Inside OptionalPropertyDeserializer.deserialize json:"some string"
Inside OptionalPropertyDeserializer.deserialize json:"another string"
Inside OptionalPropertyDeserializer.deserialize json:18
json1 result object: MyObject(fieldOne=Present(value=some string), fieldTwo=Present(value=another string), fieldThree=Present(value=18))
Inside OptionalPropertyDeserializer.deserialize json:"some string"
Inside OptionalPropertyDeserializer.deserialize json:18
json2 result object: MyObject(fieldOne=Present(value=some string), fieldTwo=my.package.OptionalProperty$NotPresent#573fd745, fieldThree=Present(value=18))
Inside OptionalPropertyDeserializer.deserialize json:"some string"
Inside OptionalPropertyDeserializer.deserialize json:18
json3 result object: MyObject(fieldOne=Present(value=some string), fieldTwo=null, fieldThree=Present(value=18))
I am testing the different options for the fieldTwo and it is almost fully working, with the exception of the 3rd json, where I would expect that fieldTwo should be fieldTwo=Present(value=null) instead of fieldTwo=null.
And I see that in this situation, the custom deserializer is not even called for fieldTwo.
Can anyone spot what I am missing here? Any tip would be very appreciated!
I ended giving up of gson and move to moshi.
I implemented this behavior based on the solution presented in this comment.
I'm trying to make all keys in a json object formatted in PascalCase when serializing a case class. It looks like the right way to do this is to define a CustomKeySerializer from the org.json4s package and reformat keys as I wish. However, while I am able to get a CustomSerializer to work, I'm not able to get a CustomKeySerializer to actually get used when serializing a case class (with nested case classes of unknown types). My code looks like the following:
case object PascalCaseSerializer extends CustomKeySerializer[String](format => (
{ case _ => "this is the deserializer and I don't need it" },
{ case _ => "this does nothing" }
))
implicit val formats: Formats = DefaultFormats + PascalCaseSerializer
case class Foo(thingId: Int, eventData: Any)
case class Bar(numThings: Int)
val event = Foo(1, Bar(2))
val payloadJson = write(event) // """{"thingId":1,"eventData":{"numThings":2}}"""
What am I missing here?
It looks like you will have to use CustomSerializer. If you look at the Extraction.scala source at internalDecomposeWithBuilder, you may notice a piece of code that looks like:
while(iter.hasNext) {
iter.next() match {
case (k: String, v) => addField(k, v, obj)
case (k: Symbol, v) => addField(k.name, v, obj)
...
case (k, v) => {
val customKeySerializer = formats.customKeySerializer(formats)
if(customKeySerializer.isDefinedAt(k)) {
addField(customKeySerializer(k), v, obj)
} else {
fail("Do not know how to serialize key of type " + k.getClass + ". " +
"Consider implementing a CustomKeySerializer.")
}
}
}
}
It means that you can't use CustomKeySerializer[String] to override default behavior for String keys. You can only use CustomKeySerializer to add some behavior for key types not explicitly defined in this pattern matching.
If I have a JSON object like:
{
"test": 3
}
Then I would expect that extracting the "test" field as a String would fail because the types don't line up:
import org.json4s._
import org.json4s.jackson.JsonMethods
import org.json4s.JsonAST.JValue
def getVal[T: Manifest](json: JValue, fieldName: String): Option[T] = {
val field = json findField {
case JField(name, _) if name == fieldName => true
case _ => false
}
field.map {
case (_, value) => value.extract[T]
}
}
val json = JsonMethods.parse("""{"test":3}""")
val value: Option[String] = getVal[String](json, "test") // Was Some(3) but expected None
Is this automatic conversion from a JSON numeric to a String expected in Json4s? If so, are there any workarounds for this where the extracted field has to be of the same type that is specified in the type parameter to the extract method?
This is the default nature of most if not all of the parsers. If you request a value of type T and if the value can be safely cast to that specific type then the library would cast it for you. for instance take a look at the typesafe config with the similar nature of casting Numeric field to String.
import com.typesafe.config._
val config = ConfigFactory parseString """{ test = 3 }"""
val res1 = config.getString("test")
res1: String = 3
if you wanted not to automatically cast Integer/Boolean to String you could do something like this manually checking for Int/Boolean types as shown below.
if(Try(value.extract[Int]).isFailure || Try(value.extract[Boolean]).isFailure) {
throw RuntimeException(s"not a String field. try Int or Boolean")
} else {
value.extract[T]
}
One simple workaround is to create a custom serializer for cases where you want "strict" behavior. For example:
import org.json4s._
val stringSerializer = new CustomSerializer[String](_ => (
{
case JString(s) => s
case JNull => null
case x => throw new MappingException("Can't convert %s to String." format x)
},
{
case s: String => JString(s)
}
))
Adding this serializer to your implicit formats ensures the strict behavior:
implicit val formats = DefaultFormats + stringSerializer
val js = JInt(123)
val str = js.extract[String] // throws MappingException
Say, I have a function which accepts a function as one of the input parameters:
// modify passed string
func modifyString(paramString: String, modificationFunction: String -> String) -> String {
return modificationFunction(paramString)
}
Now I can define a function like this:
func addLearningSuffix(inputString: String) -> String {
var returnString = inputString + " is learning swift"
return returnString
}
And use it like this:
// adds suffix - is learning swift
modifyString("Miraaj", addLearningSuffix) // returns "Miraaj is learning swift"
In above case I just passed function name - addLearningSuffix as input parameter to the function - modifyString, but what if I want to define function in the same line while passing it as input parameter.
I know that functions are special case of closures, so I can pass inline closure like this:
// adds prefix - Miraaj
modifyString("is learning swift",{
(inputString: String) -> String in
let result = "Miraaj " + inputString
return result // returns "Miraaj is learning swift"
})
or like this:
modifyString("is learning swift"){
(inputString: String) -> String in
let result = "Miraaj " + inputString
return result
}
But why I can't pass inline function definition like this:
modifyString(" is learning swift", func addSomeOtherPrefix(inputString: String) -> String{
return "Miraaj" + inputString
})
Trying which compiler complains :(
Please suggest if I am doing any thing wrong.
Think about the difference between declaring an object and then using it:
var a = 100
abs(a)
vs using it directly:
abs(100)
Same difference applies between declaring then using a function:
func talk(x: String) { return x + " is talking" }
doThis(talk)
vs using it directly:
doThis( { x: String -> return x + " is talking" } )
Writing
doThis( func talk(x: String) { return x + " is talking" } )
is like writing
abs(var a = 100)
which doesn't make sense.
You can, but avoid the func keyword, that is, avoid declaring a fun, but use the anonymous nature of closures:
modifyString(" is learning swift", { inputString in "Miraaj" + inputString })
See http://fuckingswiftblocksyntax.com
I have a function that returns Long and a Json object which I would like to call multiple times using the same variable names.
def returnMultipleItems (): (Long, JsObject) = {
val number:Long = 123
val json = Json.obj(
"Name" -> "Tom",
"age" -> 42
)
return(number, json)
}
When calling the function like this it works fine.
var (number, json) = returnMultipleItems
println("Number = " + number, ", Json = " + json)
I would like to call the function two or more times using the same variable names. With this I get error messages like ";" is expected but "=" is found.
var number:Long = 0
var json:JsObject = Json.obj()
(number, json) = returnMultipleItems // Call the function
(number, json) = returnMultipleItems // Call the function again
May not be exactly what you're looking for but you could assign the variable to the tuple (instead of the contents) e.g.
var numJson = returnMultipleItems
println("Number = " + numJson._1, ", Json = " + numJson._2)
numJson = returnMultipleItems
println("Number = " + numJson._1, ", Json = " + numJson._2)
Scala does not accept multiple variable assigment. However, your first example works because Scala interprets the form var (x, y) = (1, 2) as pattern matching.
A full explanation is here and workarounds are here.