I am trying to parse an API response mainly constructured like this:
{
"ApiFunc1":{
"SomeData1":"SomeValue1",
"SomeData2":"SomeValue2",
"SomeData3":"SomeValue3"
}
}
{
"ApiFunc2":{
"SomeData4":"SomeValue4",
"SomeData5":"SomeValue5",
"SomeData6":"SomeValue6"
}
}
.
.
.
I created a base class as follows:
class Model_BaseResponse<TResponse> : Serializable {
#SerializedName("ErrorMessage")
var errorMessage: String? = null
#SerializedName("value")
var data: TResponse? = null
}
Each ApiFunc returns different data. I want to create a base class where data: TResponse's #SerializedName("ApiFunc1") or #SerializedName("ApiFunc2") can be set in child class. Is there any way to that? I do it in regular way, defining the
#SerializedName("value")
var data: TResponse? = null
in every child class. Just been curious about that. Thanks in advance for any idea.
An annotation like SerializedData requires its arguments to be compile-time constants. Having a look at what "compile-time constant" means in Kotlin reveals:
Properties the value of which is known at compile time can be marked as compile time constants using the const modifier. Such properties need to fulfil the following requirements:
Top-level or member of an object
Initialized with a value of type String or a primitive type
No custom getter
Such properties can be used in annotations:
const val SUBSYSTEM_DEPRECATED: String = "This subsystem is deprecated"
#Deprecated(SUBSYSTEM_DEPRECATED) fun foo() { ... }
I believe the first requirement "Top-level or member of an object" cannot be fulfilled for your use case.
Related
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
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
I have a class of type A.
This class has several properties, let's call them prop1, prop2 and prop3.
When I'm calling an API, that returns a JSON string representing the object, some properties might be omitted if they are null. Further down the road, however, this object is used to construct a form dynamically (using Formik, but that's unrelated).
This framework expects all properties to be there, and some will be visible dynamically depending on other properties.
So my question, how can I parse a JSON response to my custom class, keeping default values in case properties are omitted in the API response?
What I've tried was:
static getCustomer(id) {
return fetch(process.env.MD_API_URL + 'customers/' + id, { mode: 'cors' })
.then(response => {
let cust = new Customer();
return response.json().then(x => cust = JSON.parse(x));
}).catch(error => {
return error;
});
}
But this returns undefined. Must be doing something wrong...
since typescript is not actually compiled but translated into javascript so all the javascript rules apply.
Therefore deserializing json wont actually create a new instance of the class in question but gives you an object you can "call" Customer during design time.
you could however create an object and then assign the json values like this:
export class Customer {
public id: number;
public name: string;
// your stuff here
public myDefaultProp: string = "default value";
public constructor(init?: Partial<Customer>) {
Object.assign(this, init);
}
}
your return then would look like this:
return response.json().then(x => new Customer(JSON.parse(x)));
added an example https://stackblitz.com/edit/typescript-16wlmg
This essentially just a matter of determining what to do in order to create an instance of a class, and map the properties of a JSON response towards your custom class, and there could be many different ways to solve this,
But I think (Factory function) is appropriate approach for this kind of task.
In daily life I need to read some json from ajax and cast it to some Typed object (including its METHODS). On internet I found and use following code to type casting:
export class Obj
{
public static cast<T>(obj, type: { new(...args): T} ): T
{
obj.__proto__ = type.prototype;
return obj;
}
}
As example It can be use in following way:
let objFromJson = { id: 666, name: "love" };
let building: Building = null;
building = Obj.cast(objFromJson, Building);
// On this point constructor for Building is not call - this is
// correct because we not create object but only make type casting
building.test('xx');
// on this point on console we should get:
// > "building.test a:xx, name:love"
// so object 'building' indeed have methods of Building Class
where (example is from 'head')
export class Building {
constructor(
public id: number,
public name: string,
) {
console.log('building.constructor: id:' + id + ', name' + name);
}
public test(a) {
console.log('building.test a:' + a + ', name:' + this.name);
}
}
Additional info: Instead of using cast<T>(obj, type: { new(...args): T} ): T we can use just cast<T>(obj, type): T but I read that second version will cause problem with arrow functions (https://stackoverflow.com/a/32186367/860099) - I don't understand why - ?
Questions: I not really understand how method Obj.cast works (and for instance how I can use ...args on calling it) - can someone explain it? Do someone know alternative function but not for casting but for CREATE object (so call constructor) form json data in similar handy way (eg. building = Obj.create(objFromJson, Building);
How cast works
Javascript uses prototypical inheritance. The __proto__ property, represents the prototype of the current object. This is what determines the type of the given object
For objects created using an object literal, this value is Object.prototype. For objects created using array literals, this value is Array.prototype. For functions, this value is Function.prototype. For objects created using new fun, where fun is one of the built-in constructor functions provided by JavaScript (Array, Boolean, Date, Number, Object, String, and so on — including new constructors added as JavaScript evolves), this value is always fun.prototype. For objects created using new fun, where fun is a function defined in a script, this value is the value of fun.prototype.
So when you change the __proto__ you change the prototype chain for the object basically changing it's type.
type: { new(...args): T} it the way you can represent a constructor function in typescript. As the above quote says for an object constructed by a function (such as type) the __ptoto__ should be the prototype of the function.
So when setting the __proto__, cast basically simulates the fact that the object was constructed using the type constructor function passed as a parameter.
Problems with this approach
The problems is that the constructor doesn't actually get invoked, you are just simulating the fact that the object was created using the given constructor. So any initialization that occurs in the constructor does not get executed. Arrow functions for example need to capture this, so they are not set on the prototype of the constructor function but rather on the instance of the object during the invocation of the constructor so if the constructor does not invoked, arrow functions are not initialized:
export class Building {
private otherField : string;
constructor(
public id: number,
public name: string,
) {
console.log('building.constructor: id:' + id + ', name' + name);
this.otherField = name+id;
// Ts adds in the code for initializing arrow functions in JS, but the idea is the same, this is where it would happen
}
public arrow = ()=> {};
public test(a) {
console.log('building.test a:' + a + ', name:' + this.name);
// both fields below will be undefined if cast was used.
console.log('building.otherField' + this.otherField + ', arrow:' + this.arrow); }
}
Alternative to cast
An alternative would be to create a new instance of the class and use Object.assign to assign the properties from the json object. At first glance this may seem slower, but the documentation says changing the __ptoto__ is very slow and not recommended
Changing the [[Prototype]] of an object is, by the nature of how modern JavaScript engines optimize property accesses, a very slow operation, in every browser and JavaScript engine.
export class Building {
public id: number;
public name: string;
constructor(data: Partial<Building>){
Object.assign(this, data)
console.log('building.constructor: id:' + this.id + ', name' + this.name);
}
public test(a) {
console.log('building.test a:' + a + ', name:' + this.name);
}
}
let objFromJson = { id: 666, name: "love" };
let building: Building = new Building(objFromJson);
If the class does not have any methods, and maybe you can change your design so it does not, then I would just use an interface to type the JSON object and keep using the original JSON object.
I am trying to develop a method in actionscript that takes a Class object as a parameter and will generate an instance of that class at runtime:
public function getComponent(componentType:Class):Object
{
return new componentType();
}
In some cases I may be passed a Class object that represents an interface instead. Naturally I cannot directly create an instance of an interface.
Is there some way to query the Class object and determine whether or not it represents an interface so that I can avoid trying to create an instance in this way?
Assume you follow a naming convention for your interface class, as in the standard IInterfaceName, name of the interface prefixed with a capital "I" following a capital letter and the rest of the class name. There are a couple of ways to do this. If you have a lot of different interfaces that could be passed as a parameter you could do this:
First import the flash.utils.getQualifiedClassName and in your function:
public function getComponent(componentType:Class):Object
{
var name:String = getQualifiedClassName( componentType ).replace(/.*\:+/g,"");
if ( name.search( /^\I[A-Z]/g ) != -1 ) {
trace( "parameter is an interface!" );
return null;
}
return new componentType();
}
You don't have to set the name variable, but this helps make the search a little more strict. You could just do this instead:
if ( getQualifiedClassName( componentType ).search( /\I[A-Z]/g ) != -1 ) {
trace( "parameter is an interface!" );
return null;
}
If you are not already aware, getQualifiedClassName returns the string format of the class name. The regular expressions check specifically for the capital IInterfaceName styled string.
Lastly, if you know it's only one interface, you could simple just do this:
if ( componentType == IMyinterface ) {
trace( "component is a IMyinterface" );
}
After some careful trial and error, I've come up with the following function:
public static function isInterface(clazz:Class):Boolean
{
return describeType(clazz).factory.extendsClass.(#type=="Object").length()==0;
}
Does anyone know if this condition always holds true for all actionscript interfaces?
I think this will work for you:
getQualifiedSuperclassName(classObject);
This will return null if you pass an interface, as interfaces does not have a superclass. It will always give you ANY kind of result for a class, as every class is basically an Object :)
Anyway, I don't think that passing such mixed values to a function is a good idea ;) And checking if the class starts with "I" is worse (sorry Bennett :))
p.s.
Keep in mind that describeType is EXTREMELY slow!