How can I modify val members during construction in Kotlin - constructor

In Java I'm able to modify final members in the constructor. Please see the following example
class Scratch {
private final String strMember;
public Scratch(String strParam) {
this.strMember = strParam.trim();
}
}
Is there a way in Kotlin to modify val members during construction, in this case to trim() them before the parameter value are assigned to the field.
If not, what is the recommended workaround to do so without generating too much overhead?

You can declare an argument to the constructor that isn't marked with val or var. This has the effect of being local to the constructor and lost once class construction is complete. Take that argument and set it to whatever you want.
class Scratch(str: String) {
private val strMember = str.trim()
}

Like this: constructor parameters are available during initialization of properties.
class Scratch(strParam:String) {
private val strMember = strParam.trim()
}

Try your strParam final property as follow
class Scratch(strParam : String) {
val strParam : String = strParam
get() = field.trim()
}
So, you can use them inside and outside your Scratch class

Related

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.

Kotlin: Generic function as return type?

In Kotlin, is it possible to declare a generic function type as the return type of a function?
What I want to achieve would look like this in Java:
interface Factory {
static Factory INSTANCE = new FactoryImpl();
<T> T create(String name, Class<T> type);
}
class PrefixedFactory implements Factory {
private final String prefix;
PrefixedFactory(String prefix) {
this.prefix = prefix;
}
#Override
public <T> T create(String name, Class<T> type) {
return Factory.INSTANCE.create(prefix + name, type);
}
}
(Note that in the example I access the Factory instance using the static field to avoid passing a generic function as a parameter, which would present its own problems in Kotlin).
I would like convert the prefixer to a kotlin function, but it seems to be impossible to declare a generic function as the return type:
fun prefixer(prefix: String): <T> (String, KClass<T>) -> T { TODO() }
This of course does not compile. It seems to me that this is a limitation compared to Java's functional interfaces. Is there a way to accomplish this, or a workaround?
(Edit) Clarification
I want the actual result function to be generic. If I do
fun <T: Any> prefixer(prefix: String): (String, KClass<T>) -> T { TODO() }
as the current answers suggest; I don't get a generic function, instead I get (String, KClass<Foo>) -> Foo if I call prefixer<Foo>(""). So that function can only be called with Foo, while the factory function prefixer in that case is generic, the result is not. I hope that clears up the misunderstandings.
My use case is in a Gradle plugin, where I wrote a helper method similar to this one that applies some defaults to each task created:
val myPrefix = "..."
val project: Project = <from context>
fun <T: Task> String.task(type: KClass<T>, doConfig: T.() -> Unit) {
project.tasks.create("$prefix$this", type.java, { it.doConfig() })
}
Note that the project comes in as closure. Now I want to reuse that helper in a different plugin, so I would like to create this function using a factory for different project instances.
You're doing it almost correctly. You only need to define the generic part at the prefixer function directly.
fun <T: Any> prefixer(prefix: String): (String, KClass<T>) -> T { TODO() }
Depending on you actual implementation, you could have a look at the reified keyword.
No, it isn't possible (as far as I know). The technical term for such a type is "higher-kinded type" and very few languages support them, on JVM I only know of Scala.
If someone asked me the same question without having an interface like Factory, I'd suggest creating exactly this interface as a workaround.
The following line does compile:
fun <T : Any> prefixer(prefix: String): (String, KClass<T>) -> T = TODO()
First, the generic deceleration should be right after the fun keyword.
Then it has has to be declared as type Any. The default is Any? but KClass only takes Any.
Although I was disappointed to read #Alexey's answer, I found a more streamlined workaround taking advantage of Kotlin's operators. The following makes it look more like a lambda when used:
private class Prefixer(private val: String) {
operator fun <T> invoke(name: String, type: Class<T>): T {
TODO()
}
}
To use it:
val createMy = Prefixer("MyPrefix")
val result = createMy("Configuration", Configuration::class.java)
Feel free to replace with KClass where necessary. I was actually using this for a slightly different purpose.

Use JSON.decode() in dart to instantiate class from json string

By browsing around I did successfully manage to create a class that can "opt in" dat:convert by exposing a Map toJson() method and can be json-ified with JSON.encode(myClass), more or less like the following:
//My dummy class
class MyClass{
String p1;
String p2;
Map toJson{
return {
'p1':this.p1,
'p2':this.p2
}
}
}
//so I can do
String jsonString = JSON.encode(myClass)
However I'd like to do this even the other way around, like:
String jsonString = '{"p1":"value","p2":"value"}'
MyClass instance = JSON.decode(jsonString)
But so far I've failed to find a way.
I know I can build a constructor for my class that initialises it from a map, something like:
String jsonString = '{"p1":"value","p2":"value"}'
MyClass instance = MyClass.fromMap(JSON.decode(jsonString))
However I was looking for a more "symmetric" way using just JSON.encode() and JSON.decode(), is it somehow doable? Am I missing something?
There is no standard way to encode the class in JSON. {"p1":"value","p2":"value"} doesn't contain any information about what class to instantiate. There is also no standard way to create a new class from as string (what library should be used when several contain a class with the same name, ...
As far as I know a reviver can be used for that purpose
reviver(var key, var value) {
// decode manually
}
final jsonDecoder = new JsonDecoder(reviver);
but the reviver would need to have some hardcoded logic how to recognize what JSON should result in what Dart class and how it should instantiate it and initialize the properties from the JSON.

Scala: serialize case class to JSON , alternate names

Any way to serialize a Scala case class to JSON and have the ability to provide custom serialized names ?
For example, In Java this can be done using the Gson library :
public class SomeClassWithFields {
#SerializedName("name") private final String someField;
private final String someOtherField;
public SomeClassWithFields(String a, String b) {
this.someField = a;
this.someOtherField = b;
}
}
I tried doing this in Scala :
case class SomeClassWithFields(#SerializedName("name")
someField:String)
but it seems to have no effect .
Any thoughts ?
Yes it can be done. Here's how:
case class SomeClassWithFields(#(SerializedName #scala.annotation.meta.field)("name") someField:String)
The syntax is strange (note that the outer '#' wraps the "SerializedName" and the scala field annotation) but it works fine. See more details at: https://issues.scala-lang.org/plugins/servlet/mobile#issue/SI-8975
Further improving Corindiano's answer by making it into a custom annotation, which can be (re)used elsewhere.
import scala.annotation.meta.field
case class SomeClassWithFields(#SerializedNameField(value = "name") someField:String) { ... }
object SomeClassWithFields {
type SerializedNameField = com.google.gson.annotations.SerializedName #field
}
You can do it with spray-json calling the jsonFormat overloads.
Here's how it would work with your example:
import spray.json._
case class SomeClassWithFields( someField:String)
object SomeClassJsonProtocol extends DefaultJsonProtocol {
implicit val someClassFormat = jsonFormat(SomeClassWithFields,"name")
}

Use the non-default constructor with Jerkson?

I need to serialize/deserialize a Scala class with structure something like the following:
#JsonIgnoreProperties(ignoreUnknown = true, value = Array("body"))
case class Example(body: Array[Byte]) {
lazy val isNativeText = bodyIsNativeText
lazy val textEncodedBody = (if (isNativeText) new String(body, "UTF-8") else Base64.encode(body))
def this(isNativeText: Boolean, textEncodedBody: String) = this((if(isNativeText) str.getBytes("UTF-8") else Base64.decode(textEncodedBody)))
def bodyIsNativeText: Boolean = // determine if the body was natively a string or not
}
It's main member is an array of bytes, which MIGHT represent a UTF-8 encoded textual string, but might not. The primary constructor accepts an array of bytes, but there is an alternate constructor which accepts a string with a flag indicating whether this string is base64 encoded binary data, or the actual native text we want to store.
For serializing to a JSON object, I want to store the body as a native string rather than a base64-encoded string if it is native text. That's why I use #JsonIgnoreProperties to not include the body property, and instead have a textEncodedBody that gets echoed out in the JSON.
The problem comes when I try to deserialize it like so:
val e = Json.parse[Example]("""{'isNativeText': true, 'textEncodedBody': 'hello'}""")
I receive the following error:
com.codahale.jerkson.ParsingException: Invalid JSON. Needed [body],
but found [isNativeText, textEncodedBody].
Clearly, I have a constructor that will work...it just is not the default one. How can I force Jerkson to use this non-default constructor?
EDIT: I've attempted to use both the #JsonProperty and #JsonCreator annotation, but jerkson appears to disregard both of those.
EDIT2: Looking over the jerkson case class serialization source code, it looks like a case class method with the same name as its field will be used in the way that a #JsonProperty would function - that is, as a JSON getter. If I could do that, it would solve my problem. Not being super familiar with Scala, I have no idea how to do that; is it possible for a case class to have a user-defined method with the same name as one of its fields?
For reference, here is the code below that leads me to this conclusion...
private val methods = klass.getDeclaredMethods
.filter { _.getParameterTypes.isEmpty }
.map { m => m.getName -> m }.toMap
def serialize(value: A, json: JsonGenerator, provider: SerializerProvider) {
json.writeStartObject()
for (field <- nonIgnoredFields) {
val methodOpt = methods.get(field.getName)
val fieldValue: Object = methodOpt.map { _.invoke(value) }.getOrElse(field.get(value))
if (fieldValue != None) {
val fieldName = methodOpt.map { _.getName }.getOrElse(field.getName)
provider.defaultSerializeField(if (isSnakeCase) snakeCase(fieldName) else fieldName, fieldValue, json)
}
}
json.writeEndObject()
}
Correct me if I'm wrong, but it looks like Jackson/Jerkson will not support arbitrarily nested JSON. There's an example on the wiki that uses nesting, but it looks like the target class must have nested classes corresponding to the nested JSON.
Anyway, if you're not using nesting with your case classes then simply declaring a second case class and a couple implicit conversions should work just fine:
case class Example(body: Array[Byte]) {
// Note that you can just inline the body of bodyIsNativeText here
lazy val isNativeText: Boolean = // determine if the body was natively a string or not
}
case class ExampleRaw(isNativeText: Boolean, textEncodedBody: String)
implicit def exampleToExampleRaw(ex: Example) = ExampleRaw(
ex.isNativeText,
if (ex.isNativeText) new String(ex.body, "UTF-8")
else Base64.encode(ex.body)
)
implicit def exampleRawToExample(raw: ExampleRaw) = Example(
if (raw.isNativeText) raw.textEncodedBody.getBytes("UTF-8")
else Base64.decode(textEncodedBody)
)
Now you should be able to do this:
val e: Example = Json.parse[ExampleRaw](
"""{'isNativeText': true, 'textEncodedBody': 'hello'}"""
)
You could leave the original methods and annotations you added to make the JSON generation continue to work with the Example type, or you could just convert it with a cast:
generate(Example(data): ExampleRaw)
Update:
To help catch errors you might want to do something like this too:
case class Example(body: Array[Byte]) {
// Note that you can just inline the body of bodyIsNativeText here
lazy val isNativeText: Boolean = // determine if the body was natively a string or not
lazy val doNotSerialize: String = throw new Exception("Need to convert Example to ExampleRaw before serializing!")
}
That should cause an exception to be thrown if you accidentally pass an instance of Example instead of ExampleRaw to a generate call.