I'm using Groovy, i've tried to create a simple function which will construct a Json object from a provided Json string, then i'm trying to print this string but unfortunate it's adding Square brackets to the output.
Here's a snippet from my code:
def JsonBuilder ConstructJsonObject (jsonStr) {
def jsonToReturn = new JsonBuilder();
def root = jsonToReturn(jsonStr);
return jsonToReturn;
}
String jsonStr = "{id: '111'}";
println(jsonStr);
def jsonObject = ConstructJsonObject(jsonStr);
println(jsonObject.toPrettyString());
And here's the output:
{id: '111'}
[
"{id: '111'}"
]
It's returning an Array and not a pure Json.
If you change your input to be valid json (with double quotes round the keys and values), you can do:
import groovy.json.*
String jsonStr = '{"id": "111"}'
println new JsonBuilder(new JsonSlurper().parseText(jsonStr)).toPrettyString()
To print
{
"id": "111"
}
Related
How to add JSON String in the POST request using kotlin and ktor?
Printing it out the Json string read from file or even constructed string with Kotlin in the client, the content looks like JSON.
Still, the server cannot recognize the string as JSON, and when I print it in the server, each double quota is back slashed.
The client obviously adds the back slashes, thus the request is not formatted as it should.
Client Kotlin - Ktor code:
import com.google.gson.*
import io.ktor.client.*
import io.ktor.http.*
...
val client = HttpClient(OkHttp) {
install(JsonFeature) {
serializer = GsonSerializer()
}
}
val fileContent = MyClass::class.java.getResource("myfile").readText()
println("fileContent string = $fileContent")
val out = client.post<String> {
url(url)
contentType(ContentType.Application.Json)
body = fileContent
}
the print out looks like this :
{ "name": "myname", "value": "myvalue" }
but the server (I use hookbin by the way to really print out the data without Jackson conversions) prints out:
{ \"name\": \"myname\", \"value\": \"myvalue\" }
The solution is to pass to the HTTP POST request a JSON object not a String object. They look the same when you print them, but of course they are not equally interpreted by the JVM. So, we have just to parse the string. I use the GSON library.
Add the JsonParser -> parseString method and use its object in the client:
import com.google.gson.JsonParser
val fileContent = MyClass::class.java.getResource("myfile").readText()
println("fileContent string = $fileContent")
var bodyAsJsonObject = JsonParser.parseString(fileContent).asJsonObject
println("bodyAsJsonObject = $bodyAsJsonObject")
val out = client.post<String> {
url(url)
contentType(ContentType.Application.Json)
body = bodyAsJsonObject
}
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 want to parse a nested JSON structure in Groovy. I would like to parse a sub element structure and then return the string in JSON format.
The Nested JSON structure:
{
"username": "test",
"token": "test1",
"url": "http://www.abc.to",
"testsession":
{
"serverName": "0.0.0.0",
"serverPort": 22,
"remoteUsername": "admin",
"remotePassword": "admin"
},
"deviceapp":
{
"repo": "abc-mvn-a-test-local",
"path": "com/test\/test2\/test3\/mob",
"platform": "ANDROID"
}
}
my code below using JSONSlurper isn't quite giving me what i want:
def slurper = new JsonSlurper().parseText(json)
String deviceAppParsed = slurper.deviceapp
println "deviceAppParsed " + deviceAppParsed
// returns deviceAppParsed {repo=oxp-mvn-a-rel-local, path=com/nagra/opentv/experience/mob, platform=ANDROID}
def jsonDeviceApp = JsonOutput.toJson(deviceAppParsed)
println "IS IT JSON? " + jsonDeviceApp
// returns IS IT JSON "{repo=oxp-mvn-a-rel-local, path=com/nagra/opentv/experience/mob, platform=ANDROID}"
How can i parse the json to retrieve the nested deviceapp structure in raw JSON? Thanks.
:
def slurper = new JsonSlurper().parseText(json)
String deviceAppParsed = slurper.deviceapp
def jsonDeviceApp = JsonOutput.toJson(deviceAppParsed)
I expected println jsonDeviceApp to return:
{"repo": "abc-mvn-a-test-local","path": "com/test\/test2\/test3\/mob","platform": "ANDROID"}
instead it returned:
"{repo=oxp-mvn-a-rel-local, path=com/nagra/opentv/experience/mob, platform=ANDROID}"
just replace String to def in the following line:
String deviceAppParsed = slurper.deviceapp
by using string you are converting Object returned by slurper.deviceapp to string
should be:
def deviceAppParsed = slurper.deviceapp
in this case last line will print json
{"repo":"abc-mvn-a-test-local","path":"com/test/test2/test3/mob","platform":"ANDROID"}
I have the following Groovy script (not a Grails app) that is returning a JSON-like, but it is not strictly valid JSON.
String baseURL = 'https://test.com'
File userFile = new File("./user.json")
def client = new HTTPBuilder(baseUrl)
client.headers['Content-Type'] = 'application/json'
client.request(GET, JSON) { req ->
requestContentType = JSON
headers.Accept = 'application/json'
response.success = { resp, json ->
userFile.append json.toString()
println JsonOutput.toJson(json.toString())
}
}
I am trying to create a JSON output file. I have tried using JsonOutput.prettyPrint and I looked at JsonBuilder, but that looks like I would have to build the JSON structure manually when Groovy should support the output. This is what I am getting back.
{AssetNumber=AssetNumber1, DeviceFriendlyName=FriendlyName1, PhoneNumber=17035551231, SerialNumber=SerialNumber1, Udid=Udid1, UserEmailAddress=user1#email.com, UserId=userId1, UserName=userName1}
As I said, this is JSON-like, but not strictly valid. I was expecting something like:
{"AssetNumber": "AssetNumber1", "DeviceFriendlyName": "FriendlyName1"....}
Any ideas?
It works perfectly fine (groovy v 2.3.6):
import groovy.json.*
def pretty = JsonOutput.prettyPrint(JsonOutput.toJson([1:2]))
assert pretty == """{
"1": 2
}"""
In this closure:
response.success = { resp, json ->
userFile.append json.toString()
println JsonOutput.toJson(json.toString())
}
You're getting an instance of Map under json variable. You do not need to turn it into a string. Instead use:
userFile.append JsonOutput.toJson(json)
println JsonOutput.toJson(json)
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.