Can you override the stream writers in scala #serializable objects? - serialization

I now understand that scala #serializable objects can be used the same as a Java Serializable object. In a Java Serializable object there are methods you can override to change how the object streams: writeObject(ObjectOutputStream) / readObject(ObjectOutputStream).
Can you override or inject methods into a scala #serializable object allowing you to change how the object serializes?

Yes, you can use the same methods in Scala as in Java:
#throws(classOf[IOException])
private def writeObject(out: ObjectOutputStream): Unit = // ...
#throws(classOf[IOException])
private def readObject(in: ObjectInputStream): Unit = // ...

As already stated, you can define your own writeObject and readObject methods.
#throws(classOf[java.io.IOException])
private def writeObject(out : java.io.ObjectOutputStream) : Unit = /* your definition here */
However be careful when performing this on nested classes, objects or traits.
#serializable
class Foo(x : Int) {
#serializable object X { def y = x }
}
If I serialize object X, it will actually serialize the containing Foo class, so this must also be serializable. This can be a PITA to deal with in custom serialization methods, so here's fair warning.
Another pain-point can be closure serialization. Try to keep a mental model of what variables are being captured in serialized closures. Ensure that these variables are something you'd want sent over IO!

Related

How to properly register primitives and nulls in polymorphic serialization?

I need to set up a serialization/deserialization mechanism for a polymorphic class hierarchy that also includes primitives and nulls. There are container classes containing collections with polymorphic objects, primitives, and nulls. And, the subclasses for these objects are spread across modules (therefore sealed is not an option).
I have been reading through the kotlinx.serialization polymorphism docs trying to come up with a solution. I've been able to make some incremental progress by working through that tutorial but I seem to still be hitting a wall when I try to put everything together.
The code I am posting here is a minimal example that brings together everything I need. If I can get this example to work, that should cover everything I need for my real project. This example does run without error but introduces some unnecessary readability and efficiency issues.
All classes in my custom class hierarchy are serializable data classes. The outermost container object that needs to be serialized/deserialized is a map wrapper. This map has keys which are each an instance of one of these data classes. And the values of this map can be primitives, nulls, or instances of one of my data classes. I think my main challenge here is to include those primitives and nulls in my polymorphic serialization in a clean way.
The goal of my code below is to represent this problem in the simplest way possible and to serialize and deserialize one container object successfully.
There are two main issues in the code:
I've had to replace null with FakeNull. Without this, I get null cannot be cast to non-null type kotlin.Any. This will reduce the readability and simplicity of my code and I suspect it could decrease efficiency as well.
I've had to add StringClassSerializer and DoubleClassSerializer and wrapper classes. I would also need to add serializers like these for every primitive class. If I don't register these primitives as subclasses of Any, I get Class 'String' is not registered for polymorphic serialization in the scope of 'Any'.. And if I try to register them with their default serializers (like subclass(String::class, String.serializer())) I get Serializer for String of kind STRING cannot be serialized polymorphically with class discriminator.. The problem with using serializers like StringClassSerializer and wrappers like StringWrapper is that it removes the efficiency and readability benefits of using primitives.
The json comes out looking like:
{"type":"MapContainer","map":[{"type":"SubA","data":1.0},{"type":"StringWrapper","s":"valueA"},{"type":"SubB","data":2.0},{"type":"DoubleWrapper","d":2.0},{"type":"SubB","data":3.0},{"type":"SubA","data":1.0},{"type":"SubB","data":4.0},{"type":"matt.play.FakeNull"}]}
I don't like the way this looks. I want the nulls to simply be null and the primitives to simply be primitives.
import kotlinx.serialization.KSerializer
import kotlinx.serialization.PolymorphicSerializer
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.descriptors.buildClassSerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
import kotlinx.serialization.json.Json
import kotlinx.serialization.modules.SerializersModule
import kotlinx.serialization.modules.polymorphic
import kotlinx.serialization.modules.subclass
import kotlin.collections.set
#Serializable
abstract class SuperClass
#Serializable
#SerialName("SubA")
data class SubA(val data: Double): SuperClass()
#Serializable
#SerialName("SubB")
data class SubB(val data: Double): SuperClass()
#Serializable
#SerialName("MapContainer")
data class MapContainer<K: SuperClass, V>(val map: Map<K, V>): Map<K, V> by map
#Serializable
#SerialName("StringWrapper")
data class StringWrapper(val s: String)
#Serializable
#SerialName("DoubleWrapper")
data class DoubleWrapper(val d: Double)
object StringClassSerializer: KSerializer<String> {
override val descriptor = buildClassSerialDescriptor("string")
override fun deserialize(decoder: Decoder) = decoder.decodeSerializableValue(StringWrapper.serializer()).s
override fun serialize(encoder: Encoder, value: String) =
encoder.encodeSerializableValue(StringWrapper.serializer(), StringWrapper(value))
}
object DoubleClassSerializer: KSerializer<Double> {
override val descriptor = buildClassSerialDescriptor("double")
override fun deserialize(decoder: Decoder) = decoder.decodeSerializableValue(DoubleWrapper.serializer()).d
override fun serialize(encoder: Encoder, value: Double) =
encoder.encodeSerializableValue(DoubleWrapper.serializer(), DoubleWrapper(value))
}
#Serializable
object FakeNull
fun main() {
val theMap = mutableMapOf<SuperClass, Any?>()
theMap[SubA(1.0)] = "valueA"
theMap[SubB(2.0)] = 2.0
theMap[SubB(3.0)] = SubA(1.0)
theMap[SubB(4.0)] = FakeNull /*wish I could make this just `null`*/
val theMapContainer = MapContainer(theMap)
val format = Json {
allowStructuredMapKeys = true
ignoreUnknownKeys = true
serializersModule = SerializersModule {
polymorphic(SuperClass::class) {
subclass(SubA::class)
subclass(SubB::class)
}
polymorphic(Any::class) {
/*I wish I could remove all of this primitive wrapper stuff*/
default {
when (it) {
StringWrapper::class.simpleName -> StringClassSerializer
DoubleWrapper::class.simpleName -> DoubleClassSerializer
else -> throw RuntimeException("unknown type: ${it}?")
}
}
subclass(String::class, StringClassSerializer)
subclass(Double::class, DoubleClassSerializer)
subclass(SubA::class)
subclass(SubB::class)
subclass(FakeNull::class)
}
polymorphic(
MapContainer::class, MapContainer::class, actualSerializer = MapContainer.serializer(
PolymorphicSerializer(SuperClass::class),
PolymorphicSerializer(Any::class)
) as KSerializer<MapContainer<*, *>>
)
}
}
val encoded = format.encodeToString(PolymorphicSerializer(MapContainer::class), theMapContainer)
println("\n\n${encoded}\n\n")
val decoded = format.decodeFromString(PolymorphicSerializer(MapContainer::class), encoded)
if (theMapContainer != decoded) {
throw RuntimeException("the decoded object is not the same as the original")
} else {
println("success")
}
}
Primitives (such as strings, numbers, and enums) by default are serialized as JSON primitives (e.g., "answer" or 42), not JSON objects ({ ... }). This is why they don't support polymorphic serialization; there is no "space" to place the type information in (the class discriminator).
There is no JSON object to place the class discriminator in, e.g., {"type": "fully.qualified.Name"} by default.
But, kotlinx serialization does allow you to write custom serializers, which allows you to work around this. I wrote a custom serializer for enums since I wanted to register enums as concrete types in polymophic serialization. It sounds like you should be able to do something similar. (Disclosure: I only read your problem description in detail; not your ongoing attempts/solution.)
A serializer which supports registering [Enum]s as subclasses in polymorphic serialization when class discriminators are used.
When class discriminators are used, an enum is not encoded as a structure which the class discriminator can be added to.
An exception is thrown when initializing [Json]: " "Serializer for of kind ENUM cannot be serialized polymorphically with class discriminator."
This serializer encodes the enum as a structure with a single value holding the enum value.
Use this serializer to register the enum in the serializers module, e.g.:
subclass( <enum>::class, PolymorphicEnumSerializer( <enum>.serializer() )
This custom serializer can possibly be generalized to any primitive type and thus support your use case.

How to set serializer to an internal class extending a public interface?

I'm trying to create a serializer using kotlinx.serialization for Compose Desktop classes, I have this :
#Serializer(forClass = MutableState::class)
class MutableStateSerializer<T>(private val dataSerializer: KSerializer<T>) : KSerializer<MutableState<T>> {
override fun deserialize(decoder: Decoder) = mutableStateOf(decoder.decodeSerializableValue(dataSerializer))
override val descriptor: SerialDescriptor = dataSerializer.descriptor
override fun serialize(encoder: Encoder, value: MutableState<T>) = encoder.encodeSerializableValue(dataSerializer, value.value)
}
That should be used for instances of MutableState class (as the #Serializer annotation says), but I have to put an explicit serializer for each properties otherwise I get this error :
xception in thread "main" kotlinx.serialization.SerializationException: Class 'SnapshotMutableStateImpl' is not registered for polymorphic serialization in the scope of 'MutableState'.
Mark the base class as 'sealed' or register the serializer explicitly
Code used :
#Serializable
class Test {
var number = mutableStateOf(0)
}
fun main() {
val json = Json { prettyPrint = true }
val serialized = json.encodeToString(Test())
println(serialized)
}
I have to put this annotation on my property :
#Serializable(with = MutableStateSerializer::class)
Isn't there a way to automatically link my serializer to the MutableState interface ? As the SnapshotMutableStateImpl is internal I can't set it to this class.
What you want is currently not possible. Other people seem to have requested a feature similar to what you need on GitHub: Global Custom Serializers.
Currently, for 3rd party classes, you need to specify the serializer in one of three ways:
Pass the custom serializer to the encode/decode method in case you are serializing it as the root object.
Specify the serializer on the property using #Serializable, as you do now.
Specify the serializer to be used by a full file using #file:UseSerializers.
Note that due to type inference, number will be attempted to be serialized as the return type of mutableStateOf. If you specify the type as an interface instead (does it have a supertype?), using polymorphic serialization, you could try to register the concrete type and pass your custom serializer there for the concrete type. Not really what this feature is designed for, but I believe it may work if you don't want to specify your serializer in multiple places. However, the serialized form will then include a type discriminator everywhere.

How do I get the class name from a type name?

I am trying to deserialize a Json string into an object of type OperationResult<String> using Jackson with Kotlin.
I need to construct a type object like so:
val mapper : ObjectMapper = ObjectMapper();
val type : JavaType = mapper.getTypeFactory()
.constructParametricType(*/ class of OperationResult */,,
/* class of String */);
val result : OperationResult<String> = mapper.readValue(
responseString, type);
I've tried the following but they do not work.
val type : JavaType = mapper.getTypeFactory()
.constructParametricType(
javaClass<OperationResult>,
javaClass<String>); // Unresolved javaClass<T>
val type : JavaType = mapper.getTypeFactory()
.constructParametricType(
OperationResult::class,
String::class);
How do I get a java class from the type names?
You need to obtain instance of Class not KClass. To get it you simply use ::class.java instead of ::class.
val type : JavaType = mapper.typeFactory.constructParametricType(OperationResult::class.java, String::class.java)
Kotlin has a few things that become a concern when using Jackson, GSON or other libraries that instantiate Kotlin objects. One, is how do you get the Class, TypeToken, TypeReference or other specialized class that some libraries want to know about. The other is how can they construct classes that do not always have default constructors, or are immutable.
For Jackson, a module was built specifically to cover these cases. It is mentioned in #miensol's answer. He shows an example similar to:
import com.fasterxml.jackson.module.kotlin.* // added for clarity
val operationalResult: OperationalResult<Long> = mapper.readValue(""{"result":"5"}""")
This is actually calling an inline extension function added to ObjectMapper by the Kotlin module, and it uses the inferred type of the result grabbing the reified generics (available to inline functions) to do whatever is needed to tell Jackson about the data type. It creates a Jackson TypeReference behind the scenes for you and passes it along to Jackson. This is the source of the function:
inline fun <reified T: Any> ObjectMapper.readValue(content: String): T = readValue(content, object: TypeReference<T>() {})
You can easily code the same, but the module has a larger number of these helpers to do this work for you. In addition it handles being able to call non-default constructors and static factory methods for you as well. And in Jackson 2.8.+ it also can deal more intelligently with nullability and default method parameters (allowing the values to be missing in the JSON and therefore using the default value). Without the module, you will soon find new errors.
As for your use of mapper.typeFactory.constructParametricType you should use TypeReference instead, it is much easier and follows the same pattern as above.
val myTypeRef = object: TypeReference<SomeOtherClass>() {}
This code creates an anonymous instance of a class (via an object expression) that has a super type of TypeRefrence with your generic class specified. Java reflection can then query this information.
Be careful using Class directly because it erases generic type information, so using SomeOtherClass::class or SomeOtherClass::class.java all lose the generics and should be avoided for things that require knowledge of them.
So even if you can get away with some things without using the Jackson-Kotlin module, you'll soon run into a lot of pain later. Instead of having to mangle your Kotlin this module removes these types of errors and lets you do things more in the "Kotlin way."
The following works as expected:
val type = mapper.typeFactory.constructParametricType(OperationalResult::class.java, String::class.java)
val operationalResult = mapper.readValue<OperationalResult<String>>("""{"result":"stack"}""", type)
println(operationalResult.result) // -> stack
A simpler alternative to deserialize generic types using com.fasterxml.jackson.core.type.TypeReference:
val operationalResult = mapper.readValue<OperationalResult<Double>>("""{"result":"5.5"}""",
object : TypeReference<OperationalResult<Double>>() {})
println(operationalResult.result) // -> 5.5
And with the aid of jackson-kotlin-module you can even write:
val operationalResult = mapper.readValue<OperationalResult<Long>>("""{"result":"5"}""")
println(operationalResult.result)

Why do we use "companion object" as a kind of replacement for Java static fields in Kotlin?

What is the intended meaning of "companion object"? So far I have been using it just to replace Java's static when I need it.
I am confused with:
Why is it called "companion"?
Does it mean that to create multiple static properties, I have to group it together inside companion object block?
To instantly create a singleton instance that is scoped to a class, I often write
:
companion object {
val singleton by lazy { ... }
}
which seems like an unidiomatic way of doing it. What's the better way?
What is the intended meaning of "companion object"? Why is it called "companion"?
First, Kotlin doesn't use the Java concept of static members because Kotlin has its own concept of objects for describing properties and functions connected with singleton state, and Java static part of a class can be elegantly expressed in terms of singleton: it's a singleton object that can be called by the class' name. Hence the naming: it's an object that comes with a class.
Its name used to be class object and default object, but then it got renamed to companion object which is more clear and is also consistent with Scala companion objects.
Apart from naming, it is more powerful than Java static members: it can extend classes and interfaces, and you can reference and pass it around just like other objects.
Does it mean that to create multiple static properties, I have to group it together inside companion object block?
Yes, that's the idiomatic way. Or you can even group them in non-companion objects by their meaning:
class MyClass {
object IO {
fun makeSomethingWithIO() { /* ... */ }
}
object Factory {
fun createSomething() { /* ... */ }
}
}
To instantly create a singleton instance that is scoped to a class, I often write /*...*/ which seems like an unidiomatic way of doing it. What's the better way?
It depends on what you need in each particular case. Your code suits well for storing state bound to a class which is initialized upon the first call to it.
If you don't need it to be connected with a class, just use object declaration:
object Foo {
val something by lazy { ... }
}
You can also remove lazy { ... } delegation to make the property initialize on first class' usage, just like Java static initializers
You might also find useful ways of initializing singleton state.
Why is it called "companion"?
This object is a companion of the instances.
IIRC there was lengthy discussion here: upcoming-change-class-objects-rethought
Does it mean that to create multiple static properties, I have to group it together inside companion object block?
Yes. Every "static" property/method needs to be placed inside this companion.
To instantly create a singleton instance that is scoped to a class, I often write
You do not create the singleton instance instantly. It is created when accessing singleton for the first time.
which seems like an unidiomatic way of doing it. What's the better way?
Rather go with object Singleton { } to define a singleton-class. See: Object Declarations
You do not have to create an instance of Singleton, just use it like that Singleton.doWork()
Just keep in mind that Kotlin offers other stuff to organize your code. There are now alternatives to simple static functions e.g. you could use Top-Level-Functions instead.
When the classes/objects with related functionalities belong together, they are like companions of each other. A companion means a partner or an associate, in this case.
Reasons for companionship
Cleaner top-level namespace
When some independent function is intended to be used with some specific class only, instead of defining it as a top-level function, we define it in that particular class. This prevents the pollution of top-level namespace and helps with more relevant auto-completion hints by IDE.
Packaging convenience
It's convenient to keep the classes/objects together when they are closely related to each other in terms of the functionality they offer to each other. We save the effort of keeping them in different files and tracking the association between them.
Code readability
Just by looking at the companionship, you get to know that this object provides helper functionality to the outer class and may not be used in any other contexts. Because if it was to be used with other classes, it would be a separate top level class or object or function.
Primary purpose of companion object
Problem: companion class
Let's have a look at the kinds of problems the companion objects solve. We'll take a simple real world example. Say we have a class User to represent a user in our app:
data class User(val id: String, val name: String)
And an interface for the data access object UserDao to add or remove the User from the database:
interface UserDao {
fun add(user: User)
fun remove(id: String)
}
Now since the functionalities of the User and implementation of the UserDao are logically related to each other, we may decide to group them together:
data class User(val id: String, val name: String) {
class UserAccess : UserDao {
override fun add(user: User) { }
override fun remove(id: String) { }
}
}
Usage:
fun main() {
val john = User("34", "John")
val userAccess = User.UserAccess()
userAccess.add(john)
}
While this is a good setup, there are several problems in it:
We have an extra step of creating the UserAccess object before we can add/remove a User.
Multiple instances of the UserAccess can be created which we don't want. We just want one data access object (singleton) for User in the entire application.
There is a possibility of the UserAccess class to be used with or extended with other classes. So, it doesn't make our intent clear of exactly what we want to do.
The naming userAccess.add() or userAccess.addUser() doesn't seem very elegant. We would prefer something like User.add().
Solution: companion object
In the User class, we just replace the two words class UserAccess with the two other words companion object and it's done! All the problems mentioned above have been solved suddenly:
data class User(val id: String, val name: String) {
companion object : UserDao {
override fun add(user: User) { }
override fun remove(id: String) { }
}
}
Usage:
fun main() {
val john = User("34", "John")
User.add(john)
}
The ability to extend interfaces and classes is one of the features that sets the companion objects apart from Java's static functionality. Also, companions are objects, we can pass them around to the functions and assign them to variables just like all the other objects in Kotlin. We can pass them to the functions that accept those interfaces and classes and take advantage of the polymorphism.
companion object for compile-time const
When the compile-time constants are closely associated with the class, they can be defined inside the companion object.
data class User(val id: String, val name: String) {
companion object {
const val DEFAULT_NAME = "Guest"
const val MIN_AGE = 16
}
}
This is the kind of grouping you have mentioned in the question. This way we prevent the top-level namespace from polluting with the unrelated constants.
companion object with lazy { }
The lazy { } construct is not necessary to get a singleton. A companion object is by default a singleton, the object is initialized only once and it is thread safe. It is initialized when its corresponding class is loaded. Use lazy { } when you want to defer the initialization of the member of the companion object or when you have multiple members that you want to be initialized only on their first use, one by one:
data class User(val id: Long, val name: String) {
companion object {
val list by lazy {
print("Fetching user list...")
listOf("John", "Jane")
}
val settings by lazy {
print("Fetching settings...")
mapOf("Dark Theme" to "On", "Auto Backup" to "On")
}
}
}
In this code, fetching the list and settings are costly operations. So, we use lazy { } construct to initialize them only when they are actually required and first called, not all at once.
Usage:
fun main() {
println(User.list) // Fetching user list...[John, Jane]
println(User.list) // [John, Jane]
println(User.settings) // Fetching settings...{Dark Theme=On, Auto Backup=On}
println(User.settings) // {Dark Theme=On, Auto Backup=On}
}
The fetching statements will be executed only on the first use.
companion object for factory functions
Companion objects are used for defining factory functions while keeping the constructor private. For example, the newInstance() factory function in the following snippet creates a user by generating the id automatically:
class User private constructor(val id: Long, val name: String) {
companion object {
private var currentId = 0L;
fun newInstance(name: String) = User(currentId++, name)
}
}
Usage:
val john = User.newInstance("John")
Notice how the constructor is kept private but the companion object has access to the constructor. This is useful when you want to provide multiple ways to create an object where the object construction process is complex.
In the code above, consistency of the next id generation is guaranteed because a companion object is a singleton, only one object will keep track of the id, there won't be any duplicate ids.
Also notice that companion objects can have properties (currentId in this case) to represent state.
companion object extension
Companion objects cannot be inherited but we can use extension functions to enhance their functionality:
fun User.Companion.isLoggedIn(id: String): Boolean { }
The default class name of the companion object is Companion, if you don't specify it.
Usage:
if (User.isLoggedIn("34")) { allowContent() }
This is useful for extending the functionality of the companion objects of third party library classes. Another advantage over Java's static members.
When to avoid companion object
Somewhat related members
When the functions/properties are not closely related but only somewhat related to a class, it is recommended that you use top-level functions/properties instead of companion object. And preferably define those functions before the class declaration in the same file as that of class:
fun getAllUsers() { }
fun getProfileFor(userId: String) { }
data class User(val id: String, val name: String)
Maintain single responsibility principle
When the functionality of the object is complicated or when the classes are big, you may want to separate them into individual classes. For example, You may need a separate class to represent a User and another class UserDao for database operations. A separate UserCredentials class for functions related to login. When you have a huge list of constants that are used in different places, you may want to group them in another separate class or file UserConstants. A different class UserSettings to represent settings. Yet another class UserFactory to create different instances of the User and so on.
That's it! Hope that helps make your code more idiomatic to Kotlin.
Why is it called "companion"?
An object declaration inside a class can be marked with the companion keyword:
class MyClass {
companion object Factory {
fun create(): MyClass = MyClass()
}
}
Members of the companion object can be called by using simply the class name as the qualifier:
val instance = MyClass.create()
If you only use 'object' without 'companion', you have to do like this:
val instance = MyClass.Factory.create()
In my understanding, 'companion' means this object is companion with the outter class.
We can say that companion is same as "Static Block" like Java, But in case of Kotlin there is no Static Block concept, than companion comes into the frame.
How to define a companion block:
class Example {
companion object {
fun display(){
//place your code
}
}
}
Calling method of companion block, direct with class name
Example.Companion.display

What is the rationale behind having companion objects in Scala?

Is there a case where a companion object (singleton) for a class is needed? Why would I want to create a class, say Foo and also create a companion object for it?
The companion object basically provides a place where one can put "static-like" methods. Furthermore, a companion object, or companion module, has full access to the class members, including private ones.
Companion objects are great for encapsulating things like factory methods. Instead of having to have, for example, Foo and FooFactory everywhere, you can have a class with a companion object take on the factory responsibilities.
Companion objects are useful for storing state and methods that are common to all instances of a class but they do not use static methods or fields. They use regular virtual methods which can be overridden through inheritance. Scala truly has nothing static. There are lots of ways you can use this but here's a simple example.
abstract class AnimalCounter
{
var animals = 0
def name: String
def count()
{
animals += 1
println("%d %ss created so far".format(animals, name))
}
}
abstract class Animal
{
def companion: AnimalCounter
companion.count()
}
object Dog extends AnimalCounter
{
val name = "dog"
}
class Dog extends Animal
{
def companion = Dog
}
object Cat extends AnimalCounter
{
val name = "cat"
}
class Cat extends Animal
{
def companion = Cat
}
Which produces this output:
scala> new Dog
1 dogs created so far
scala> new Cat
1 cats created so far
scala> new Dog
2 dogs created so far
scala> new Cat
2 cats created so far
...and it's a good place to store static factory methods (not that DP) for accompanied classes. If you name those overloaded factory methods apply(/.../) you will be able to create/initialize you class
without 'new' (not really that important)
with different possible sets of parameters (compare to what Bloch writes in Effective Java about telescoping constructor)
with the ability to to decide which derived class you want to create instead of the abstract (accompanied) one
Example code:
abstract class AbstractClass;
class RealThing(s: String) extends AbstractClass;
class AlternativeThing(i: Int) extends AbstractClass;
object AbstractClass {
def apply(s: String) = {
new RealThing(s)
}
def apply(i: Int) = {
new AlternativeThing(i)
}
}
// somewhere else you can
val vs = AbstractClass("asdf") // gives you the RealThing wrapped over string
val vi = AbstractClass(123) // gives you AlternativeThing wrapped over int
I wouldn't call the object/base class AbstractXxxxx because it doesn't looks bad: like creating something abstract. Give those names a real meaning.
Consider using immutable, method less, case classes and seal the abstract base class.
In addition to the things Saem said in his reply, the Scala compiler also looks for implicit conversions of types in the corresponding companion objects (of either the source or the target), so the conversions don't need to be imported.
About the reason for singleton objects in general Programming in Scala says:
As mentioned in Chapter 1, one way in which Scala is more object-oriented than Java is that classes in Scala cannot have static members. Instead, Scala has singleton objects (p. 65).
I always see companion objects as a bridge to write both functional and object oriented code in Scala. Many times we just need pure functions which take some input and provide a processing result. Putting those relevant functions in the companion object makes it easy to look up and use, for myself as well as some one building on top of my code.
Moreover, it is a language provided feature to write the singleton pattern without doing anything. This is especially useful when you need a singleton to encapsulate a delegator for the life of JVM. For example, writing a simple HTTP client library in Scala where you can encapsulate an underlying Java implementation based delegator and let consumers of your API live in pure world.
If you define class and object in same file with same name, they known as companion class and object. Scala don't have static as JAVA keyword, You can take as replacement of static with companion class and object in Scala.
For more detail information please check article
class and object keyword in scala programming
At first, it provides a clear separation of static vs non static methods methods.Also provide a simple way to create singleton class.
It also can inherit methods from other classes and/or traits, which cannot be done with Java static methods.and can be passed as a parameter.