I'm converting a list of Foo objects to a JSON string. I need to parse the JSON string back into a list of Foos. However in the following example, parsing gives me a list of JSONObjects instead of Foos.
Example
List list = [new Foo("first"), new Foo("second")]
def jsonString = (list as JSON).toString()
List parsedList = JSON.parse(jsonString) as List
println parsedList[0].getClass() // org.codehaus.groovy.grails.web.json.JSONObject
How can I parse it into Foos instead?
Thanks in advance.
I had a look at the API docs for JSON and there doesn't appear to be any way to parse to a JSON string to a specific type of object.
So you'll just have to write the code yourself to convert each JSONObject to a Foo. Something like this should work:
import grails.converters.JSON
import org.codehaus.groovy.grails.web.json.*
class Foo {
def name
Foo(name) {
this.name = name
}
String toString() {
name
}
}
List list = [new Foo("first"), new Foo("second")]
def jsonString = (list as JSON).toString()
List parsedList = JSON.parse(jsonString)
// Convert from a list of JSONObject to a list of Foo
def foos = parsedList.collect {JSONObject jsonObject ->
new Foo(name: jsonObject.get("name"))
}
A more general solution would be to add a new static parse method such as the following to the JSON metaClass, that tries to parse the JSON string to a List of objects of a particular type:
import grails.converters.JSON
import org.codehaus.groovy.grails.web.json.*
class Foo {
def name
Foo(name) {
this.name = name
}
String toString() {
name
}
}
List list = [new Foo("first"), new Foo("second")]
def jsonString = (list as JSON).toString()
List parsedList = JSON.parse(jsonString)
// Define the new method
JSON.metaClass.static.parse = {String json, Class clazz ->
List jsonObjs = JSON.parse(json)
jsonObjs.collect {JSONObject jsonObj ->
// If the user hasn't provided a targetClass read the 'class' proprerty in the JSON to figure out which type to convert to
def targetClass = clazz ?: jsonObj.get('class') as Class
def targetInstance = targetClass.newInstance()
// Set the properties of targetInstance
jsonObj.entrySet().each {entry ->
if (entry.key != "class") {
targetInstance."$entry.key" = entry.value
}
}
targetInstance
}
}
// Try the new parse method
List<Foo> foos = JSON.parse(jsonString, Foo)
// Confirm it worked
assert foos.every {Foo foo -> foo.class == Foo && foo.name in ['first', 'second'] }
You can try out the code above in the groovy console. A few warnings
I have only performed very limited testing on the code above
There are two JSON classes in the latest Grails release, I'm assuming you're using the one that is not deprecated
If you are doing this in a Grails controller, and Foo IS indeed a domain object, don't forget that armed with your JSON map, you can also do:
List list = [new Foo("first"), new Foo("second")]
def jsonString = (list as JSON).toString()
List parsedList = JSON.parse(jsonString) as List
Foo foo = new Foo()
bindData(foo, parsedList[0]);
I've taken this code and extended it to work with nested structures. It relies on a 'class' attribute existing in the JSON. If there's a better way by now in Grails please let me know.
// The default JSON parser just creates generic JSON objects. If there are nested
// JSON arrays they are not converted to theirs types but are left as JSON objects
// This converts nested JSON structures into their types.
// IT RELIES ON A PROPERTY 'class' that must exist in the JSON tags
JSON.metaClass.static.parseJSONToTyped = {def jsonObjects ->
def typedObjects = jsonObjects.collect {JSONObject jsonObject ->
if(!jsonObject.has("class")){
throw new Exception("JSON parsing failed due to the 'class' attribute missing: " + jsonObject)
}
def targetClass = grailsApplication.classLoader.loadClass(jsonObject.get("class"))
def targetInstance = targetClass.newInstance()
// Set the properties of targetInstance
jsonObject.entrySet().each {entry ->
// If the entry is an array then recurse
if(entry.value instanceof org.codehaus.groovy.grails.web.json.JSONArray){
def typedSubObjects = parseJSONToTyped(entry.value)
targetInstance."$entry.key" = typedSubObjects
}
else if (entry.key != "class") {
targetInstance."$entry.key" = entry.value
}
}
targetInstance
}
return typedObjects
}
As of Grails 2.5, this is possible:
Period test = new Period()
test.periodText = 'test'
String j = test as JSON
def p = JSON.parse(j)
test = p.asType(Period)
println(test.periodText)
Output:
test
I am unsure of when it became an option.
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.
Is it possible to invoke jsonStringify() to get a properly formatted JSON string outside a Gatling EL?
I need to convert a Map into its JSON String to calculate a signature.
val scn = scenario("My Scenario")
.exec(buildPayload)
.exec(http("Post")
.post("/api/postSomething")
.asJson
.body(StringBody("${payload.jsonStringify()}"))
def buildPayload: Expression[Session] = session => {
val header = Map(...)
val data = Map(...)
val signature = calculateSignature(JsonStringify(data)) // << is it possible??
val payload = Map(
"header" -> header,
"data" -> data,
"signature" -> signature
)
session.set("payload", payload)
}
def calculateSignature(payload: String): String = {
...
}
Do you see any other approach?
var jsonElements = List[String]()
val params = Map("host"->host)
for((key,value)<-mapSql){
val map = Map("metric"->key,"timestamp"->new Date().getTime,"value"->value,"tags"->params)
jsonElements=JsonUtility.toJSONString(map) :: jsonElements
}
val entity = new StringEntity(JsonUtility.toJSONString(jsonElements))
println("json elements final list is "+jsonElements)
println("json elements final JSON Obj is "+JsonUtility.toJSONString(jsonElements))
entity.setContentType(new BasicHeader("Content-Type", "application/json"))
val postRequest: HttpPost = new HttpPost(putURL)
postRequest.setEntity(entity)
val postResponse: CloseableHttpResponse = httpclient.execute(postRequest)
I basically need to add values to a list and then send them together in a JSON Array.
However this is introducing unnecessary escape characters "/" in the output which is rendering the post request useless and I am getting an error to the API hit. the following is the response :
json elements final list is List({"metric":"replicationLag","timestamp":1410179907871,"value":0.0,"tags":{"host":"tg-em-db01.nm.xxxx.com"}}, {"metric":"status","timestamp":1410179907824,"value":1,"tags":{"host":"tg-em-db01.nm.xxxxx.com"}})
json elements final JSON Obj is ["{\"metric\":\"replicationLag\",\"timestamp\":1410179907871,\"value\":0.0,\"tags\":{\"host\":\"tg-em-db01.nm.xxxx.com\"}}","{\"metric\":\"status\",\"timestamp\":1410179907824,\"value\":1,\"tags\":{\"host\":\"tg-em-db01.nm.xxxxx.com\"}}"]
I can replace and remove all the escape characters by the replaceAll function but I do not want to do that. is there a better way to append objects to an already existing JSON object and then change it to an array ( which i can easily do by new JsonArray(List(JsonObj)) ) so that i dont get any escape characters anywhere.
Something like this :
val params = Map("host"->host)
var map = Map[String,Any]()
for((key,value)<-mapSql){
map ++= Map("metric"->key,"timestamp"->new Date().getTime,"value"->value,"tags"->params)
}
val entity = new StringEntity(JsonUtility.toJSONString(List(map)))
println("json elements final list is "+map)
println("json elements final JSON Obj is "+JsonUtility.toJSONString(List(map)))
is giving me this as an ouput :
json elements final list is Map(metric -> replicationLag, timestamp -> 1410180939983, value -> 0.0, tags -> Map(host -> tg-em-db01.nm.xxxx.com))
json elements final JSON Obj is [{"metric":"replicationLag","timestamp":1410180939983,"value":0.0,"tags":{"host":"tg-em-db01.nm.xxxxx.com"}}]
But I need something like this :
[ {"metric":blah blah} , {"metric":blah blah} ]
Is there a way to append to maps such that the same key values are not clubbed ?
Thanks in advancE!
var jsonElements = List[Map[String, Any]]()
val params = Map("host" -> host)
for ((key, value) <- mapSql) {
val map = Map("metric" -> key, "timestamp" -> new Date().getTime, "value" -> value, "tags" -> params)
jsonElements = map :: jsonElements
}
val entity = new StringEntity(JsonUtility.toJSONString(jsonElements))
entity.setContentType(new BasicHeader("Content-Type", "application/json"))
val postRequest: HttpPost = new HttpPost(putURL)
postRequest.setEntity(entity)
val postResponse: CloseableHttpResponse = httpclient.execute(postRequest)
object JsonUtility {
def toJSONString(obj:Any):String = {
compact(JsonAST.render(decompose(obj)))
}
}
First off here's my code:
// Description.scala
package com.wausoft.jsonrpc.model
import com.google.gson.annotations.SerializedName
class Description {
#SerializedName("Language")
var language = ""
#SerializedName("Description")
var description = ""
}
// Item.scala
package com.wausoft.jsonrpc.model
import com.google.gson.annotations.SerializedName
class Item {
#SerializedName("Number")
var number = 0
#SerializedName("Description")
var description: List[Description] = Nil
}
// Result.scala
package com.wausoft.jsonrpc.model
import com.google.gson.annotations.SerializedName
class Result {
#SerializedName("Type")
var typeNum = 0
#SerializedName("Description")
var description: List[Description] = Nil
#SerializedName("Items")
var items: List[Item] = Nil
}
// Response.scala
package com.wausoft.jsonrpc.model
class Response {
var jsonRPC = ""
var result: List[Result] = Nil
var id = 0
}
// main file
package com.wausoft.jsonrpc
import scala.io.Source
import com.wausoft.jsonrpc.model._
import com.google.gson._
object Program {
def getJson(file: String): String = Source.fromFile(file)("UTF-8").mkString
def parseJFile(json: String): Response = new Gson().fromJson(json, classOf[Response])
// insert main method here
}
I get the error in the title when I try to parse the data to a POSO, it works fine if I replace all of the lists with arrays, but the thing that bothers me is that this works:
var lst: List[Int] = Nil
lst = List(1,2,3,4,5)
lst foreach print // produces 12345
If the above example works, why doesn't my code? I mean I'm basically doing the exact same, with the only exception that I let Gson handle the list making, is it because it's wrapped in a class, or did I miss something?
See How can I use Gson in Scala to serialize a List?.
Quote from the question: "Gson doesn't know about Scala's collection classes". This answer shows how to serialize a class with a Scala List member to JSON using GSON. The former is the source of the problem, the later is the solution.
I have some history data, that I want to convert to json. So these are Lists of lists. Their type is List[List[Position]] where Position is a simple case class. I wrote a formatter to help Json.toJson cope. I was expecting an output of exactly one outer array with two inner arrays that contain 3 objects each. What I got instead was this. Note the additional array wrappings.
[[[[{"amount":1.0,"minAmount":2.0,"price":3.0,"volume":4.0},
{"amount":5.0,"minAmount":6.0,"price":7.0,"volume":8.0},
{"amount":9.0,"minAmount":10.0,"price":11.0,"volume":12.0}]],
[[{"amount":0.1,"minAmount":0.2,"price":0.3,"volume":0.4},
{"amount":5.0,"minAmount":6.0,"price":7.0,"volume":8.0},
{"amount":9.0,"minAmount":10.0,"price":11.0,"volume":12.0}]]]]
I don't know where the wrapping arrays come from. Can somebody help me out here? This is a test with the wrapper I am using:
class ApplicationSpec extends Specification {
implicit object PositionFormat extends Format[List[List[Position]]] {
def writes(historyList: List[List[Position]]) : JsValue = {
Json.arr(historyList.map{
o => Json.arr(o.map{ p =>
Json.obj(
"amount" -> JsNumber(p.amount),
"minAmount" -> JsNumber(p.minAmount),
"price" -> JsNumber(p.price),
"volume" -> JsNumber(p.volume)
)
})
})
}
def reads(json: JsValue): JsResult[List[List[Position]]] = ???
}
"Application" should {
"Convert position data to json" in {
val l1 = ListBuffer(new Position(1.0D,2.0D,3.0D,4.0D),
new Position(5.0D,6.0D,7.0D,8.0D),
new Position(9.0D,10.0D,11.0D,12.0D)).toList
val l2 = ListBuffer(new Position(0.1D,0.2D,0.3D,0.4D),
new Position(5.0D,6.0D,7.0D,8.0D),
new Position(9.0D,10.0D,11.0D,12.0D)).toList
val obj = ListBuffer(l1,l2).toList
val json = Json.toJson(obj)
var string: String = json.toString()
println(string)
}
}
}
It seems that Json.arr takes it's arguments and returns a JsValue for a JSON array of them. It looks like you could simply do with Json.toJson:
Here's how arr is meant to be used:
// Json.arr
Json.arr("one", "two")
// Gives you
// play.api.libs.json.JsArray = ["one","two"]
If you instead do:
// vs
val l = List("one", "two")
Json.arr(l)
// Gives you
// play.api.libs.json.JsArray = [["one","two"]]
// ... a nested array, which is what you don't want.
What you need is:
// Json.toJson
Json.toJson(l)
// Gives you:
// play.api.libs.json.JsValue = ["one","two"]