How to use PolymorphicJsonAdapterFactory with an interface in Moshi? - java

I have an interface with two concrete types as part of my model that I'd like to serialize/deserialize with Moshi. My issue is that I don't fully understand if PolymorphicJsonAdapterFactory is actually meant for my use case. I've looked at the samples and a few blog posts and (if I'm understanding them correctly) all of them seem to point to the fact that your interface is supposed to have a field in the interface that allows you to determine the type. I'm working in an existing codebase and so I can't easily add a field that would allow me to figure out what type it is by some string literal.
This is where I'm at with my Moshi code and I'm seeking validation on whether or not I'm using PolymorphicJsonAdapterFactory correctly. Note: I'm using java for the moshi portion of the code and for my model. My interface and it's concrete types are in kotlin
String json = ...;
Moshi moshi = new Moshi.Builder()
.add(PolymorphicJsonAdapterFactory.of(PersonInterface.java, "")
.withSubtype(BusinessPerson.java, "occupation")
.withSubtype(PolicePerson.java, "rank")
)
.build();
JsonAdapter<MyModel> jsonAdapter = moshi.adapter(MyModel.class);
MyModel myModel = jsonAdapter.fromJson(json);
Note: I'm using java for the moshi portion of the code and for my model. My interface and it's concrete types are in kotlin
MyModel is defined as the following
class MyModel {
String month;
PersonInterface person;
}
My interface and concrete classes in kotlin:
Interface PersonInterface {
val personsName: String?
}
data class BusinessPerson(
override val personsName: String,
val occupation: String?
) : PersonInterface
data class PolicePerson(
override val personsName: String,
val rank: String?
) : PersonInterface
The goal is to hopefully have Moshi be able to create a
class MyModel {
String month;
BusinessPerson person;
}
or a
class MyModel {
String month;
PolicePerson person;
}
depending on whether or not the person field contains an occupation (which means it's of type BusinessPerson) or if it contains the field rank (which means it's of type PolicePerson).

PolymorphicJsonAdapterFactory.of(PersonInterface.java, "")
.withSubtype(BusinessPerson.java, "occupation")
.withSubtype(PolicePerson.java, "rank")
means that JSON for every person is supposed to contain a key "" and "occupation" or "rank" is the value for this key, not a key name itself as you want. So e.g. it would encode a
BusinessPerson("John", "CEO")
as
{"": "occupation", "personsName": "John", "occupation": "CEO"}
I think you'll have to write your own adapter factory for this use-case if you want to avoid a discriminator field.

Related

How to deserialize field with Interface field in Java Jackson?

I'm trying to send json objects between two Spring Boot services with rpc so for that I need JacksonMessageConverter.
I need send object with field that is an Interface type of multiple DTO classes.
For instance (code in Kotlin):
Response class
class Response(
#JsonProperty("result")
val result: IExampleData?,
#JsonProperty("statusCode")
val statusCode: Int
)
ExampleData classes
interface IExampleData
class ExampleData1(
#JsonProperty("exampleString")
val exampleString: String
) : IExampleData
class ExampleData2(
#JsonProperty("exampleInt")
val exampleInt: Int
) : IExampleData
If I use #JsonDeserialize annotation for one of Example objects, it works fine with correct object:
Response class
...
#JsonDeserialize(`as` = ExampleData1::class)
val result: IExampleData?
...
But it won't work if service send ExampleData2. I need that for all of my objects that implements IExampleData.
I tried to use #JsonSubTypes annotation for result field like that:
...
#JsonSubTypes(
Type(value = ExampleData1::class, name = "exampleData1"),
Type(value = ExampleData2::class, name = "exampleData2"),
)
val result: IExampleData?
...
But it didn't work for me. I get next error:
com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `com.example.demo.dto.IExampleData` (no Creators, like default constructor, exist): abstract types either need to be mapped to concrete types, have custom deserializer, or contain additional type information
So how can I deserialize one of multiple objects for one field?

Map java Enum to Kotlin data class

I am making a call from service A which is in Kotlin to service B which is in Java. It return me an object which contains multiple fields. One of the fields returned in the Java object is an enum. In my kotlin code I have defined a DTO which maps the returned response to kotlin. I need to map this enum to a string value in kotlin.
DTO in Java:
public class PersonDTO
{
private Long id;
private String name;
private CountryCode countryCode;
}
The CountryCode is an enum.
Data class in Kotlin:
data class PersonDTO(
val id: Long? = null,
val name: String? = null,
val countryCode: String? = null //How to map the enum to string here..???
)
Any help would be appreciated.
Answer to the edited question: How to map a Java enum to a String?
you can call name() or toString() on an enum to get a String representation of it.
name() cannot be overwritten and always returns the textual representation of the value defined in the code, while toString() can be overwritten, so it might be depending on your use case what to use. Because of the fact that name() cannot be overwritten I prefer to always use name() wich can have less side effects or unexpected behavior when working with libraries which are not under your control.
Original Answer:
1 you don't have to do this. You can use the same Java class also in Kotlin code.
2 You could just reuse the enum, like in option 1) you can reuse the Java enum in Kotlin code:
data class PersonDTO(
val id: Long? = null,
val name: String? = null,
val countryCode: CountryCode
)
3 You can write a Kotlin enum with a mapping function to create the matching instance of the enum:
enum class KotlinCountryCode {
EXAMPLE;
fun fromJavaCountryCode(input: CountryCode): KotlinCountryCode? {
if (input.name() == EXAMPLE.name) {
return EXAMPLE
}
return null
}
}

Mapping POJOs to case classes with immutable lists during deserialisation

I am coming from Java background and trying to understand how to model Domain classes/POJOs in Scala.
I am trying to Deserialize JSON response from a RestAPI and my Java POJOs are as follows:
#Data
public class ColumnResponse {
private String id;
private String name;
private String type;
...
}
k
#Data
public class DataSetGetResponse {
private String id;
private List<ColumnResponse> columns;
...
}
Now I have created following Case Classes
case class DataSetGetResponse (id: String,
columns: List[ColumnResponse]
.... )
case class ColumnResponse (id: String,name: String ...)
I am trying to use https://sttp.readthedocs.io/en/latest/json.html#json4s library for HTTP communication and json4s for deserialization.
Questions:
1) In the DataSetGetResponse case class, field "columns" is a List.By default this is an immutable list. How the Deserialization library add new DataColumnGetResponse objects to this immutable list? Do I have to declare this as mutable ?
2) There is a field called 'type' field in the ColumnResponse POJO. In Scala 'type' is a reserved keyword.How to handle this case?
Answer the first one:
An immutable object can be mutated with the copy function:
dataSet.copy(columns = newResp :: dataSet.columns)
For more complex tasks you can use Lenses see for example here: enter link description here
Answer the second one:
If it is a reserved word you can do it like
case class ColumnResponse (id: String, name: String, `type`: String)
This answer addresses the following aspect of the question:
How the Deserialization library add new DataColumnGetResponse objects
to this immutable list?
Let us consider a simplified version of the problem:
JsonMethods.parse("""[1,2,3]""").extract[List[Int]]
How does json4s deserialise [1,2,3] into immutable List[Int]? First it parses the raw string into an intermediary AST (abstract syntax tree) data structure where it represents the list like so
case class JArray(arr: List[JValue]) extends JValue
We see here that arr is an immutable list. The key line that builds it up after parse executes is in JsonParser
def newValue(v: JValue): Unit = {
...
case a: JArray => vals.replace(JArray(v :: a.arr))
...
}
Note how the operator :: in v :: a.arr adds an element at the beginning of this list and returns a new list with v added in. This means since there are three elements in [1,2,3] the following three lists are created by json4s in the process of deserialisation
JArray(List(JInt(1))
JArray(List(JInt(2), JInt(1)))
JArray(List(JInt(3), JInt(2), JInt(1)))
Again note these are three separate lists.
Next, after internal AST is created, actual deserialisation to List[Int] takes place by calling extract[List[Int]]. The key component that does this for lists is CollectionBuilder
private class CollectionBuilder(json: JValue, tpe: ScalaType)(implicit formats: Formats) {
...
val array: Array[_] = json match {
case JArray(arr) => arr.map(extractDetectingNonTerminal(_, typeArg)).toArray
...
}
Note how we simply map over AST arr built up during parsing step and convert each element to the model of type typeArg, which in our simple case is Int but in your case would be DataColumnGetResponse.

Kotlin data class implementing Java interface

I'm trying to introduce Kotlin into my current project. I've decided to begin with entities, which seem to map perfectly to data classes.
For example I have a data class:
data class Video(val id: Long, val ownerId: Long, val title: String, val description: String? = null,
val imgLink: String? = null, val created: Date? = null, val accessKey: String? = null,
val views: Long? = null, val comments: Long? = null, val videoLink: String? = null): Entity
Which implements Java interface:
public interface Entity {
Long getId();
}
But for some reason compiler doesn't understand that method is implemented already:
Class 'Video' must be declared abstract or implement abstract member public abstract fun getId(): kotlin.Long! defined in net.alfad.data.Entity
Do I have to use any additional keywords for id param? What does "!" mean in the signature?
The problem here is that Kotlin loads the Java class Entity first and it sees getId as a function, not as a getter of some property. A property getter in a Kotlin class cannot override a function, so the property id is not bound as an implementation of the getId function.
To workaround this, you should override the original function getId in your Kotlin class. Doing so will result in JVM signature clash between your new function and id's getter in the bytecode, so you should also prevent the compiler from generating the getter by making the property private:
data class Video(
private val id: Long,
...
): Entity {
override fun getId() = id
...
}
Note that this answer has been adapted from here: https://stackoverflow.com/a/32971284/288456
If this is your whole data class then you're not overriding getId(). I see that you have a property called id and Kotlin should generate a getter for that but that won't be marked with the override keyword which you need to indicate that you're overriding an abstract function.
-- EDIT --
Alexander beat me to it! His answer is better anyway! ;)

Jackson Nested Object Deserialization into property

Suppose I have a json object that looks like:
{
id: 1,
name: "john doe"
spouse: 2
}
and the class I want it to deserialize it to:
class Person{
private Long id;
private String name;
private Person spouse;
//getters/setters
}
Is there any way to tell jackson to expand the spouse: 2 property into a new Person POJO with id=2 when deserializing the JSON?
I have run into this issue as a result of deserializing JSON into persistent entities and would like to be able to easily persist the relationships between these entities.
Aside from a full deserializer, there is a simpler way: define a POJO with a single int-arg constructor like so:
class Person {
int id;
public Person(int id) {
this.id = id;
}
}
This actually works, as Jackson will try to find limited number of special constructors (single-arg public constructors that that take String, int, long, double or boolean).
You can optionally also denote these with #JsonCreator -- and if constructor is not public, you must do it, to make it discoverable. But for public ctors this is not needed.
It is impossible of course for Jackson to infer a fully populated Person object representing the spouse from the number 2. You would likely need to register a custom deserializer that checks if the input is an integer, and if so, looks up the spouse from wherever it is stored. I have not done this kind of thing for classes that contain references to themselves (e.g. your Person contains a Person) so I can only give you rough guidance.
I believe this may only work with Jackson version 1.9 or later. Basically, you can register a module with the object mapper that tells Jackson to use a custom deserializer.
SimpleModule module = new SimpleModule("PeopleModule", new Version(1, 1, 0, null);
module.addDeserializer(Person.class, new JacksonPersonDeserializer());
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(module);
Alternately on the Person class itself, you can do something like:
class Person {
#JsonDeserialize(using=JacksonPersonDeserializer.class)
Person spouse;
}
This works before 1.9 but pollutes your object. Either way, you will need to write a custom deserializer.

Categories

Resources