Basically I want null arrays to be deserialized as empty collection by Jackson with scala module. As I understand this is not implemented right now so I'm looking for a workaround. I think it could work if I have auxiliary constructor with Option[Seq[Any]] parameter. Like this:
case class Test (id: Int, seq: Seq[Any]){
#JsonCreator
def this(id: Int, seq: Option[Seq[Any]]) = this(id, seq.getOrElse(Seq()))
}
But it doesn't work since Jackson gives me this exception:
Caused by: java.lang.IllegalArgumentException: Conflicting
property-based creators: already had explicitly marked [constructor
for com.test.Main$Test, annotations: [null]], encountered [constructor
for com.test.Main$Test, annotations: {interface
com.fasterxml.jackson.annotation.JsonCreator=#com.fasterxml.jackson.annotation.JsonCreator(mode=DEFAULT)}]
It's interesting that if I remove id field it starts working:
case class Test (seq: Seq[Any]){
#JsonCreator
def this(seq: Option[Seq[Any]]) = this(seq.getOrElse(Seq()))
}
Could somebody explain me what is going on here?
Can I have overloaded constructor as json creator with case classes?
Maybe you see another workaround to my initial problem(deserializing null as empty collection)?
p.s. about my initial problem. I don't want to have Option[Seq[Any]] and I don't want to have getter because my constructor param will have ugly name like _seq and in scala parameter names is also a public API.
Related
I am seeing some strange (un)boxing behaviour in Scala I cannot explain.
Consider the following code:
case class SomeCaseClass(longOpt: Option[Long])
def someMethod(l: Long): Unit = ???
val x: SomeCaseClass = // Case class populated from JSON in some code I do not control
x.longOpt.map(l => someMethod(l))
The attempt to call someMethod yields the following error:
java.lang.ClassCastException java.lang.Integer cannot be cast to java.lang.Long
at scala.runtime.BoxesRuntime.unboxToLong(BoxesRuntime: 105)
Running the following:
x.longOpt.map(_.getClass).get
yields long
Obviously the conversion of JSON to the case class is the prime suspect but I would expect a runtime error instantiating the case class if the value was not of the correct type.
Can anyone explain what is happening here?
Obviously the conversion of JSON to the case class is the prime suspect but I would expect a runtime error instantiating the case class if the value was not of the correct type.
I want to understand why Scala does not complain when the case class is created, only when I attempt to manipulate the Option[Long] which it would appear does not contain a Long at all...
I guess the thing is in type erasure. For
case class SomeCaseClass(longOpt: Option[Long])
Option[Long] is just Option[_] at runtime. So there is no ClassCastException during instantiating the case class because it's not casting Option[Integer] to Option[Long], it's Option[_] to Option[_].
And when you work with the content of Option it's ClassCastException because you try to cast Integer to Long.
Actually, the main problem is still that there are no reified typeargs for classes in Kotlin. But here is why this bothers me in this specific case:
Suppose you have a wrapper class Wrapper that takes in a string content and a class* type and can output an object of class type retrieved by parsing content as JSON by demand by calling the function getObj():
class Wrapper<T>(private val content: String, /*private val type: KClass<*>*/) {
fun getObj(): T {
// ?
}
}
And I want to use kotlinx.serialization. Now, you might have noticed how I put an asterisk after "class" before. Here's the reason: Yes, Wrapper has to take the target class in some way, but how? Should it be just the typearg (won't work because type erausre) or a KClass reference (won't work because I need a reified typearg)?
The thing is that as far as I know, the only way to decode a generic JSON to a serializable target class is to use Json.decodeFromString<T>(content), where T is the target type and content is the JSON string. Now, T is defined to be reified (so that the type can be processed at runtime) and can only be filled with another reified typearg or an actual class reference. I can't use another reified typearg because I am in the context of a class and a class cannot have reified typeargs. I can also not use an actual class reference because the user of the class should be able to construct it with different targets, e.g. they decide what the target is, not me.
So, how do I do this with kotlinx.serialization? Is it even possible?
Ok so no one answered the question yet, but I also posted this question in the r/Kotlin subreddit. Here it is.
I actually got an answer there (credits to u/JakeWharton), and since you might get across this StackOverflow question because you googled the same question, you might be happy to find an answer here. So here's my try to paraphrase the answer:
So, basically, kotlinx-serialization does indeed not work with KClasses. But when you think about it, you only need the KClass to determine how to serialize it. And since that is determined at compile-time when you work with KXS, you actually just need to pass the serializer (the actual strategy defining how to serialize / deserialize your class). You can obtain a serializer for every class annotated with #Serializable by invoking .serializer() on it; the result will be of the type KSerializer<T>. So, instead of having
class Wrapper<T>(private val content: String, private val type: KClass<T>)
and constructing it via
val wrapper = Wrapper("{}", Foo::class)
You can do it like this:
class Wrapper<T>(private val content: String, private val serializer: KSerializer<T>)
and then construct it like this:
val wrapper = Wrapper("{}", Foo.serializer())
(supposing Foo is annotated with #Serializable)
you can then serialize and deserialize by using the KSerializer instead of a typearg, like this:
val obj: T = Json.decodeFromString(serializer, "[Your JSON String]")
val str: String = Json.encodeToString(serializer, obj)
And that's it! Just swap out your regular (K)Class approach by KSerializer and it'll work with KXS.
I'm developing cordapp using the example-cordapp project as a reference. I've been able to commit a transaction to the ledger and even run querias on the node to see if it's really there. However, when I try to run query from my Spring Boot application, I get this error.
Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request
processing failed; nested exception is
org.springframework.http.converter.HttpMessageConversionException: JSON mapping problem:
java.util.Collections$UnmodifiableRandomAccessList[0]->net.corda.core.contracts.StateAndRef["state"]-
>net.corda.core.contracts.TransactionState["data"]-
>com.mypackage.states.MyState["party"]; nested exception is
com.fasterxml.jackson.databind.JsonMappingException: object is not an instance of declaring class
(through reference chain: java.util.Collections$UnmodifiableRandomAccessList[0]-
>net.corda.core.contracts.StateAndRef["state"]->net.corda.core.contracts.TransactionState["data"]-
>com.mypackage.states.MyState["party"])] with root cause
java.lang.IllegalArgumentException: object is not an instance of declaring class
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_251]
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_251]
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~
[na:1.8.0_251]
at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_251]
Here's the request code
#GetMapping(value = [ "/api/v1/states" ], produces = [MediaType.APPLICATION_JSON_VALUE])
fun getMyIOUs(): ResponseEntity<List<StateAndRef<MyState>>> {
val myStates = proxy.vaultQueryBy<MyState>().states
return ResponseEntity.ok(myStates)
}
And here's the state code
#BelongsToContract(com.sentinel.contract.SharingInformationContract::class)
class SharingInformationState(
val party: Party,
val dataOwnerId: Long,
val dataBuyerId: Long,
override val linearId: UniqueIdentifier = UniqueIdentifier()) : LinearState, QueryableState {
override val participants: List<AbstractParty> = listOf(party)
override fun generateMappedObject(schema: MappedSchema): PersistentState {
return when (schema) {
SharingInformationSchemaV1 -> SharingInformationSchemaV1.PersistentSharingInformation(
party,
dataOwnerId,
dataBuyerId,
linearId.id
)
else -> throw IllegalArgumentException("Unrecognised schema $schema")
}
}
override fun supportedSchemas(): Iterable<MappedSchema> = listOf(SharingInformationSchemaV1)
}
There's little information about this issue on the internet. Some suggest it is connected to the classpath, that something is duplicated there, but I don't know how to check. Also, this error isn't connected to the Party type. I've tried to add #JsonIgnore on a party, but then it throws on the other field. Persistence of this field in mapping schema also doesn't matter. I've tried persisting and not persisting, it changes nothing. Thanks in advance!
I believe this is because you are missing Corda Jackson support library which is required to convert Corda objects to json.
Add this to your dependencies in the build.gradle
compile "net.corda:corda-jackson:$corda_release_version"
https://github.com/corda/samples-java/blob/master/Advanced/auction-cordapp/client/build.gradle#L19
Also, make sure you have a MappingJackson2HttpMessageConverter bean configured.
#Bean
public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter(){
ObjectMapper mapper = JacksonSupport.createDefaultMapper(partyAProxy());
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
converter.setObjectMapper(mapper);
return converter;
}
https://github.com/corda/samples-java/blob/master/Advanced/auction-cordapp/client/src/main/java/net/corda/samples/client/AppConfig.java#L48
The Exception java.lang.IllegalArgumentException: object is not an instance of declaring class is something that happens if a method is called by reflection on an object which is of the wrong type.
In conjunction with jackson that may happen because a generic is lying to you. Here is an example:
class A (val x: String)
class B (val y: String)
class C (val z: List<A>)
ObjectMapper().writeValueAsString(C(listOf(B("x")) as List<A>))
This causes a compile warning, but it compiles and initially runs because of type erasure. However we forcefully injected a List<B> in a place where actually a List<A> is expected. While type erasure does remove quite a bit of information, it does not do so completely. Reflection can still be used to determine that C.z is actually of type List<A>. Jackson uses this information and tries to serialize an object of type A but instead finds an object of type B in the list and fails with the given message.
Check that your data structure actually contains the types that you expect!
I have JUnit test like that:
Test fun testCategoriesLoading() {
val subscriber = TestSubscriber<List<ACategory>>()
service.categories().subscribe(subscriber)
subscriber.awaitTerminalEvent()
subscriber.assertNoErrors()
}
service is Retrofit, that uses GsonConverter to deserialize json into
data class ACategory(val id: String, val title: String, val parentId: String?, val hasChildren: Boolean)
instances.
Test is passing, even if ACategory filled with id = null, title = null etc.
So, as far as i know, gson using reflection, and kotlin lazily resolves this nullability constraints on first access.
Is there any way to force this resolve?
Some good-looking solution without direct access to fields manually? I really don't want to write every assert by hand.
You could use the new Kotlin reflection. If you have an instance of ACategory, call
ACategory::class.memberProperties
.filter { !it.returnType.isMarkedNullable }
.forEach {
assertNotNull(it.get(aCategory))
}
to access all properties that are marked as not nullable and assert they're not null. Make sure, you have the reflection lib on the classpath.
Make sure you're using M14.
We ended up with hack for data classes(only use case for us, so its ok).
Calling gsonConstructedObject.copy() reveals all exceptions
I'm using a custom class for JSONObject (madison.util.json.JSONObject) instead of the standard org.json.JSONObject and am trying to mock a constructor(String) call for JSONObject.class using PowerMockito.
PowerMockito.whenNew(JSONObject.class).withArguments(String.class).thenReturn(jsonStub);
I'm getting teh following error:
No constructor found in class 'madison.util.json.JSONObject' with parameter types: [ null ].
Can anybody advise what is the problem here?
Thanks
you pass a Class as argument not a String.
To pass a string without take care of its value, you can use:
PowerMockito.whenNew(JSONObject.class).withArguments(Matchers.anyString()).thenReturn(jsonStub);
Otherwise, If you need a String.class as argument try:
whenNew(MimeMessage.class).withParameterTypes(MyParameterType.class).withArguments(isA(MyParameter.class)).thenReturn(mimeMessageMock);
https://groups.google.com/forum/#!msg/powermock/ncH_2u39UBM/Rtk0-_FufzQJ