Grails Enumeration to JSON - json

I would like to change the way enums are marshalled to JSON. I am currently using default grails.converters.JSON ( as JSON) and for example in controller I use:
FilmKind.values() as JSON
The output of this is:
"kind":[{"enumType":"FilmKind","name":"DRAMA"},{"enumType":"FilmKind","name":"ACTION"}]
I would like to remove "enumType" and just return:
"kind":["DRAMA","ACTION"]
I am looking for a solution which would still allow me to use
as JSON
because I don't want to marshall each enumeration individually.

In case anyone is wandering how to convert all enum values to plain String values in a generic way:
class EnumTypeHidingJSONMarshaller {
void register() {
JSON.registerObjectMarshaller(Enum) { Enum someEnum ->
someEnum.toString()
}
}
}

because I don't want to marshall each enumeration individually.
Well, unless you want to write your own JSON converter, marshalling is the best approach here. Reason is because the only real other way is to do what Sergio is suggesting and you'll have to call that code everywhere you need it. And if FilmKind is a property of another class then his solution won't work at all really.
I would suggest Marshallers and here is how I would do it. Create a class called FilmKindMarsaller:
class FilmKindMarshaller {
void register() {
JSON.registerObjectMarshaller(FilmKind) { FilmKind filmKind ->
[
name: filmKind.toString()
]
}
}
}
Then add the following to your Bootstrap:
[ new FilmKindMarshaller() ].each { it.register() }
The above is so that you can just keep adding instances of each Marshaller you need.
Now, anytime FilmKind is JSON'ified, be that on its own or part of a parent class, you get the JSON you want, sans enumType.

You can register a custom object marshaller for your domain class to allow as JSON. In your Bootstrap.groovy, you can do something like this:
JSON.registerObjectMarshaller(FilmKind) {
def result = [:]
def props = ['name']
props.each { prop ->
result[prop] = it."$prop"
}
result
}

How about:
def display = [kind:[]]
FilmKind.values().each { val ->
display.kind.add(val.value)
}
render display as JSON

Related

How do I use Moshi to serialize a json string into org.json.JSONObject?

I have a JSON response from my server which is dynamic in nature and I cannot map it into a Kotlin Data Class.
I would like to create a org.json.JSONObject out of it and parse it from there.
I've looked around SO and Moshi's doc but couldn't find any easy way of achieving this.
Any suggestions?
I've stumbled upon the same problem recently. I needed to resend some of the data from one endpoint to another with adding some new stuff to it.
The response from the server looks like this:
{
"someProperty": "value",
"someOtherProperty": "otherValue",
"someDynamicProperty": {
// There may be anything including nested structures, not known beforehand
}
}
Normally Moshi doesn't have built-in support for something like this, but it allows you to build your own adapters and handle the parsing logic.
What you need is define the type that you want to receive as a result:
#JsonClass(generateAdapter = true)
data class CustomResponse(
val someProperty: String,
val someOtherProperty: String,
val someDynamicProperty: JSONObject?
)
Then, you need to create a custom adapter to handle the parsing:
internal object JSONObjectAdapter {
#FromJson
fun fromJson(reader: JsonReader): JSONObject? {
// Here we're expecting the JSON object, it is processed as Map<String, Any> by Moshi
return (reader.readJsonValue() as? Map<String, Any>)?.let { data ->
try {
JSONObject(data)
} catch (e: JSONException) {
// Handle error if arises
}
}
}
#ToJson
fun toJson(writer: JsonWriter, value: JSONObject?) {
value?.let { writer.value(Buffer().writeUtf8(value.toString())) }
}
}
Then, just add this adapter to Moshi builder on creation:
Moshi.Builder().add(JSONObjectAdapter).build()
or use Moshi annotations if you want to apply it only to some particular properties.

Is it possible to enable/disable a custom deserializer depending on the API endpoint being called?

I'm accessing a JSON API which has 2 kinds of endpoints:
the first kind returns a list of objects of the same type (Symptom, ChronicDisease...)
the second kind (a search function) returns a mixed list of objects of different types (those types are the same than can be returned by the first kind of API)
In the second case, each item of the list has a type field telling which is the type of the object. This field doesn't exist in the first case.
I would like to use the default deserializer for the first kind of API and a custom deserializer for the second kind of API. Is it possible?
If I only use the default deserializer, API calls of the first kind will work but I'm unable to perform a search.
If I enable the following deserializer, the search will work but the deserializer is also used when using the first kind of API and it fails because the type field is missing.
Custom deserializer I'd like to use:
class SearchableItemDeserializer : JsonDeserializer<SearchableItem>() {
override fun deserialize(p: JsonParser, ctxt: DeserializationContext): SearchableItem {
val root : JsonNode = p.readValueAsTree()
val type : String = root.get("type").asText()
when(type){
"symptom" -> {
return ObjectMapper().readValue(root.asText(), Symptom::class.java)
}
"symptom_group" -> {
return ObjectMapper().readValue(root.asText(), SymptomGroup::class.java)
}
"diagnosis" -> {
return ObjectMapper().readValue(root.asText(), Diagnose::class.java)
}
"chronic_disease" -> {
return ObjectMapper().readValue(root.asText(), ChronicDisease::class.java)
}
}
throw Exception("Unable to deserialize type $type")
}
}
Interface common to Symptom, SymptomGroup, Diagnose and ChronicDisease:
#JsonDeserialize(using = SearchableItemDeserializer::class)
interface SearchableItem
It's possible. You can extent Converter.Factory to create you custom converter. Probably most dumb and direct way would be to add check for specific retrofit annotation inside "requestBodyConverter" or "responseBodyConverter" methods.
Something like:
class CustomConverter : Converter.Factory() {
override fun responseBodyConverter(type: Type,
annotations: Array<Annotation>,
retrofit: Retrofit): Converter<ResponseBody, *>? {
return responseConverter(*annotations)
.responseBodyConverter(type, annotations, retrofit)
}
private fun responseConverter(vararg methodAnnotations: Annotation): Converter.Factory {
return when {
endpoint1(*methodAnnotations) -> converter1
endpoint2(*methodAnnotations) -> converter2
else -> defaultConverter
}
}
override fun requestBodyConverter(type: Type,
parameterAnnotations: Array<Annotation>,
methodAnnotations: Array<Annotation>,
retrofit: Retrofit): Converter<*, RequestBody>? {
//same approach here
}
fun endpoint1(vararg annotations: Annotation): Boolean {
//condition check here
}
fun endpoint2(vararg annotations: Annotation): Boolean {
//and here (if needed)
}
Just add your endpoints 1/2 implementation (probably just compare #Get() contents with certain pattern or something like that) and repeat same instruction for requestConverter.
When ready, just add it to retrofit:
return Retrofit.Builder()
.baseUrl(url)
.client(client)
.addConverterFactory(CustomConverter())
.build()

Gson - nice serialisation of Kotlin enums with a value

I have an enum with values
enum class Foo(val serverVal:Int) {
BAR(1),
BUG(2)
}
that I would like to use with a class:
data class C1(val fooVal:Foo)
I want to be able to serialise it with a code that looks as close as can be to:
Gson().toJson(C1(Foo.BAR))
That would yield
{"fooVal":1}
Instead of the default conversion, which is, of course {"fooVal":"BAR"}. If it was a string value I could have used #SerializedName, but I can't because it's an Int, not a String.
Is there a simple way to add something to Foo, in order to show Gson how to take the Int value from Foo entries rather than their name?
Examples I saw in Java include EnumAdapterFactory and TypeAdapter which are quite cumbersome and defeat the purpose of a pretty code.
I would love to get a solution that is maintained inside the data structure.
enum class SomeEnum(val i:Int) {
V1(1), V2(10), V3(5);
companion object {
#SerializeMeLikeThis
fun toJson() = i;
#DeserializeMeLikeThat
fun fromJson(v:Int) = values().find{it.i == v}?:whatever
}
}
An ugly way but still encapsulated-ish
data class C2(#SerializedName("foo") var serverFoo:Int) {
// Becomes a nightmare with many params.
constructor(f:Foo) : this(F.serverVal)
var foo:Foo
get() = Foo.values().find{serverFoo == it.serverVal}?:whatever
set(v) { serverFoo = v.serverVal }
}
So it can be called
Gson().toJson(C2(BAR))
and
// Result: Foo.BAR
Gson().fromJson("""{"foo":"1"}""", C2::class.java).foo
Well, you can live on it...but it... :-(
Any nice way?
Thanks

Grails JSON marshallers in domain class

I know in grails i can define diferent JSON marshallers and asign names to them for different uses, which is very nice. However i end with a lot of code in the Bootstrap section and i end with two places where i need to tweak when domain classes changes.
Ithink thi is not good enough and i wonder if it might be possible to define JSON marshallers in the domain class itself.
Do you think would it be a good practice? ... can you provide suggestions on the best approach to achieve this?
Thanks,
I wrote a plugin for this purpose specifically. It allows you to use annotations in domain classes, like this:
import grails.plugins.jsonapis.JsonApi
class User {
static hasMany = [
pets: Pet
]
#JsonApi
String screenName
#JsonApi('userSettings')
String email
#JsonApi(['userSettings', 'detailedInformation', 'social'])
String twitterUsername
#JsonApi(['detailedInformation', 'userSettings'])
Set pets
String neverGetsSerialized
#JsonApi('detailedInformation')
Integer getNumberOfTicklyAnimals() {
pets.count { it.likesTickling }
}
}
In your controller, you would then call JSON.use('detailedInformation') to activate a specific marshaller.
In Bootstrap.groovy write this code:
JSON.registerObjectMarshaller(YourClass) { YourClass yourClass->
Map result = [:]
result['yourClass.property'] = yourClass.property
def domain = new DefaultGrailsDomainClass(YourClass)
domain.persistentProperties.each { GrailsDomainClassProperty property, String propertyName = property.name ->
result[propertyName] = yourClass[(propertyName)]
}
return result
}
Code below add one property, you can name it how u want
result['yourClass.property'] = yourClass.property
This code add all properties by it's name to map:
domain.persistentProperties.each { GrailsDomainClassProperty property, String propertyName = property.name ->
result[propertyName] = yourClass[(propertyName)]
}

Jackson JSON to Java mapping for same attrubute with different data type

I have a JSON object which I don't have control of and want to map it to a Java object which is pre-created.
There is one attribute in the JSON object which can be a URL or it could be a JSONArray.
Class SomeClass {
private URL items;
public URL getURL() {
return items;
}
public void setURL(URL url) {
this.items = url;
}
}
Below is the JSON:
Case A:
{
...
items: http://someurl.abc.com/linktoitems,
...
}
OR
Case B
{
...
items: [
{ "id": id1, "name": name1 },
{ "id": id2, "name": name2 }
]
...
}
If i create the POJO to map for Case A, Case B fails and vice versa. In short, is there a way to map the JSON attribute to the POJO field with different data types? In that case I will create two separate fields in the POJO named,
private URL itemLink;
private Item[] itemList;
It depends on exact details, but if what you are asking is if it is possible to map either JSON String or JSON array into a Java property, yes this can be done.
Obvious way would be to define a custom deserializer which handles both kinds of JSON input.
But it is also possible to define Java type in such a way that it can be constructed both by setting properties (which works from JSON Object) and have a single-String-arg constructor or static single-String-arg factory method marked with #JsonCreator.
Yet another possibility is to use an intermediate type that can deserialized from any JSON: both java.lang.Object and JsonNode ("JSON tree") instances can be created from any JSON. From this value you would need to do manual conversion; most likely in setter, like so:
public void setItems(JsonNode treeRoot) { .... }
What will not work, however, is defining two properties with the same name.
One thing I don't quite follow is how you would convert from List to URL though. So maybe you actually do need two separate internal fields; and setter would just assign to one of those (and getter would return value of just one).