Angular 2: How to preserve class methods on deserialization? - json

Let's say I have a class Foo:
export class Foo {
name: string;
printName(): void {
console.log(this.name);
}
}
Now the problem is that when my FooService gets a Foo from my backend as JSON and creates a Foo object out of it, it doesn't have printName() because there's no such thing in the JSON object.
How should I arrange this (in the context of Angular 2)? Do I have to create my methods outside the class so that they just take a Foo as an argument?
In Java, for example, it's perfectly fine that DTO's have methods.

Usually you only transfer an object with the values over http not a class instance. You have to create the class instance yourself.
export class Foo {
constructor(name: string) {}
printName(): void {
console.log(this.name);
}
}
// data deserialized by angular from the request
let data = { name: 'John' };
let foo: Foo = new Foo(data.name);
If it helps you can make interfaces for the data you receive from the server and pass that to the constructor of the Foo class.
Note: keep in mind that there is no type casting in TS.
If you are doing something like
let foo: Foo = <Foo> data;
It is a type assertion, you just tell the compiler that data is of type Foo it doesn't do anything to the data object.

Related

Include class name (Kotlin data class) in JSON response in Quarkus

I'm new to Quarkus and Kotlin and truth be told, I'm not quite sure yet what goes on behind the scenes and which JSON library is actually responsible for rendering the JSON response from a resource when I set the #Produces(MediaType.APPLICATION_JSON) on my function. But I'm returning an instance of a data class that I created from that method and all of the fields in that data class are rendered in the response. However, I have multiple response classes and I would like to include the name of the class in the JSON response. What I have now is a String field that is simply hard coded to the name of the class but that is ugly as I have to repeat the class name:
data class StuffInitiatedResponse (
val id: String,
val projectId: String
) {
val operation = "StuffInitiatedResponse"
}
data class StuffCompletedResponse (
val id: String,
val projectId: String,
) {
val operation = "StuffCompletedResponse"
}
And in my service class:
#Path("/myservice")
class MyService {
#POST
#Path("{project}/{path:.*}")
#Produces(MediaType.APPLICATION_JSON)
fun initiateStuff(#PathParam project: String,
#PathParam path: String,
#QueryParam("completedId") completedId: String?) : StuffInitiatedResponse {
if (completedId == null) {
println("I've initiated stuff")
return StuffInitiatedResponse(UUID.randomUUID().toString(), project)
} else {
println("I've completed stuff")
return StuffCompletedResponse(completedId, project)
}
}
}
This produces what I expect but as I said, I'm annoyed that I have to repeat the class name in the "response" field of the data classes. Is there some way for me to have the class name embedded in the JSON?
The JSON library depends on the dependencies you defined. It can be either Jackson or Yasson.
I recommend using Jackson and, in this case, you can use the #JsonTypeInfo annotation on your serialized classes, which has some options to include the type in the JSON output.

Using a KClass reference as a reified parameter to deserialize from JSON

I'm trying to implement a general serialization framework to convert outgoing and incoming messages to json using the kotlinx serialialization. I'm developing a multiplatform app, so I'm trying to get it to run on KotlinJVM and KotlinJS.
For this, I add a type field to every message and use a map that maps each type string to a KClass. What's the type for that map? It contains KClass<> objects whose classes extend the Message class, therefore in java I'd specify my map as
Map<KClass<? extends Message>, String>.
How can I do that in Kotlin?
Afterwards I need to serialize and deserialize the message based on its key and therefore type. Java frameworks take a Class parameter for the type of the object I want to deserialize/instantiate (e.g. gson.fromJson(ClientMessage.class)). In Kotlin this is done using reified parameters Json.decodeFromString<Type>. I do not know the type of the message at compile time though and just have a reference to a KClass, how can I instantiate an object based on that?
#Serializable
open class Message(val type: String) {
companion object {
val messageTypes: Map<KClass<out Message>, String> = mapOf(
ClientLoginMessage::class to "clientLoginMessage",
Message::class to "message"
)
inline fun <reified T> getMessageTypeByClass(): String = messageTypes[T::class]!! // utility for defining the type in the constructors of the individual messages
}
fun toJson() = Json.encodeToString(this)
fun fromJson(json: String): Message? {
val plainMessage = Json.decodeFromString<Message>(json) // get type string from json
return messageTypes.entries.find { it.value == plainMessage.type }?.let {
// how can I use the KClass from it.key as reified parameter?
Json.decodeFromString<?????>(json)
}
}
}
#Serializable
class ClientLoginMessage
: Message(Message.getMessageTypeByClass<ClientLoginMessage>()) {}
Create a map of serializers like for types:
val serializers: Map<KClass<out Message>, KSerializer<out Message>> = mapOf(
ClientLoginMessage::class to ClientLoginMessage.serializer(),
Message::class to Message.serializer()
)
Pass in the serializer needed to Json.decodeFromString like this:
fun fromJson(json: String): Message? {
val plainMessage = Json.decodeFromString<Message>(json) // get type string from json
return messageTypes.entries.find { it.value == plainMessage.type }?.let {
// how can I use the KClass from it.key as reified parameter?
Json.decodeFromString(serializers.get(plainMessage.type)!!, json)
}
}
You might also want to have a look at the Kotlin built in handling of polymorphic classes: https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/polymorphism.md

Optimise JSON to Typescript Map

I'm passing an object from Java to Typescript that has "Map" properties. My typescript models are described by interfaces, not classes:
export interface Foo {
bar: Map<string, Bar>;
mapOfBar: Map<string, Map<string, Bar>>;
}
export interface Bar {
someField: string;
}
I wrote code to query and convert the JSON object to Foo:
import {HttpClient} from '#angular/common/http';
...
export class FooService {
constructor(private http: HttpClient) {
}
getFoo(): Observable<Foo> {
return this.http.get<Foo>('/some/route/to/FooEndpoint');
}
}
This returns me the object, but bar and mapOfBar are of type object, not Map. So after quite a bit of searching the internet I came up with this code:
getFoo(): Observable<Foo> {
return this.http
.get<Foo>('/some/route/to/FooEndpoint')
.pipe(
map((res: Foo) => {
res.bar = new Map(Object.entries(res.bar));
let actualMapOfBar = new Map(Object.entries(res.mapOfBar));
res.mapOfBar = new Map();
for (let [key, bar] of actualMapOfBar) {
res.mapOfBar.set(key, new Map(Object.entries(bar)));
}
return res;
})
);
}
This returns me bar and mapOfBar as Maps, which is great.
Call me a perfectionist, but somehow this feels clunky and wrong:
The line map((res: Foo) => { indicates that res is already implementing the Foo interface, but it doesn't - it only contains the properties, but not the correct types (yet).
This is quite a bit of conversion logic to set up, especially when dealing with deeply nested object trees.
Is there no easier way to implement this or can this be optimised? Is there ideally some "automagical" way of using the Typescript interface to convert the object?
(Angular 9.1, Typescript 3.8, rxjs 6.5)
EDIT:
This is an example response return by the endpoint:
{"bar":{"key1":{"someField":"A"},"key2":{"someField":"B"},"key3":{"someField":"C"}},"mapOfBar":{"A":{"key1":{"someField":"A"},"key2":{"someField":"B"},"key3":{"someField":"C"}},"B":{"key1":{"someField":"A"},"key2":{"someField":"B"},"key3":{"someField":"C"}},"C":{"key1":{"someField":"A"},"key2":{"someField":"B"},"key3":{"someField":"C"}}}}
No there is not. The line map((res: Foo) is -you- telling the compiler: "hey, this is of type 'Foo'", and the compiler accepts this, because he trusts you. However, you lied to the compiler, because the response of the API is just a plain JSON object. This cannot includes Map or any other class.
Like I said before, there is no automatic way to do the conversion. You will have to do this yourself. Which means, to make it type safe, you should have two interfaces. One being the FooResponse and one into which you want this Foo to be converted to. The reason this does not exist, is because TypeScript types only exist at compile time. During runtime, so when you digest the API, the types are lost, and so is your information for conversion. That's why you have to do it manually.
A small question. Why do you want them to be maps? Besides some relatively convenient API, you can also just keep it as the object it is, and change your typing to:
export interface Foo {
bar: Record<string, Bar>;
mapOfBar: Record<string, Record<string, Bar>>;
}
export interface Bar {
someField: string;
}
Is there no easier way to implement this or can this be optimised? Is there ideally some "automagical" way of using the Typescript interface to convert the object?
Yes, it is! It's called tapi.js - it's a lightweight automapper to/from JSON data.
npm i -D tapi.js
Then you can map your object in a number of ways, namely:
let typedObject = new YourClass().fromJSON(jsonData);
Or, more simply, you can map the contents of a promise like so:
http.YOUR_REQUEST_CODE_HERE
.as(YourClass)
.[then/pipe/whatever](typedObject => { ... })
You can read the docs for more info.

Kotlin class to match JSON with Nested Object for GSON builder

I have some incoming JSON that looks like this.
{ "status":200,
"transformation_percent":0.0,
"image_percent":24.51,
"bandwidth_percent":0.0,
"storage_percent":26.23,
"freeTrialPeriod":7889238000,
"freeTrialParams":"{\"cache_period\":604800000,\"cache_allowance\":5000,\"price_in_cents\":0}",
"basicPlanParams":"{\"cache_period\":604800000,\"cache_allowance\":10000,\"stripe_plan_id\":\"plan_blah\",\"price_in_cents\":100,\"currency\":\"eur\"}"
}
I am trying to construct a ServerParams Kotlin class that will match this incoming JSON, so that I can pass the class to the gsonBuilder like this:
val GSON = GsonBuilder().setLenient().create()
val server_params = GSON.fromJson(string, ServerParams::class.java)
I am struggling with the nested objects. I know how to write my own deserializer, so I can do that if I have to, but Kotlin has proven to be such an elegant language in my short experience that I thought there must be a better way of doing it. I also don't want to have to write matching deserializers for every API call!
I've tried inner classes, which didn't work. I've tried creating additional classes for the nested objects in the JSON string and that didn't work either.
Here is a minimal example of how you would do it:
class OuterClass(val name: String) {
var inner = InnerClass(2)
}
class InnerClass(val number: Int)
fun main() {
val gson = GsonBuilder().setLenient().create()
val outerClass = gson.fromJson("""{
name: 'test',
inner: {
number : 1
}
}""".trimMargin(), OuterClass::class.java)
}
You just put the instance of the inner class in a property of the outer class.
Since Gson mutates the classes via reflection you could also declare the inner class as lateninit which would make the instantiation of the inner class with a default value unecessary.
class OuterClass(val name: String) {
lateinit var inner: InnerClass
}
This, of course, only makes sense if you only instatiate the class via Gson.

Expressing custom per-class de/serialization methods in Scala

I'm building a JSON serialization tool and I want to be able to define custom serialize and deserialize methods on classes in Scala. Unfortunately, though, since Scala doesn't have static methods, I haven't been able to figure out a nice way to express the fromJSON method.
What I would like to write is:
class Foo(val x: Int) extends Serializable {
def toJSON = JInteger(x)
static val fromJSON: PartialFunction[JValue, Foo] = {
JInteger(x) => new Foo(x)
}
}
... but static doesn't exist. I could write the static method in a companion object:
object Foo {
val fromJSON: PartialFunction[JValue, Foo] = {
JInteger(x) => new Foo(x)
}
}
class Foo(val x: Int) extends Serializable {
def toJSON = JInteger(x)
}
but then the compiler won't check for its presence in every Serializable class, and mistakes could creep in at runtime if it accidentally wasn't defined.
I'm new to Scala — what's a good way to express this? I want to write my serialize/deserialize methods in or close to the classes they represent.