I use kotlinx.serialization library to serialize/deserialize JSONs. There is a JSON string:
{"id":"1"}
that can be also represented as
{"uid":"1"}
And I want to handle both names with one property, e.g.:
#Serializable
data class User(val id: String)
Is it possible to parse both JSONs using only one data class and its property?
Yes, you can use the #JsonNames annotation to provide alternative names in addition to the name of the property (see doc). You can also define more than one additional name in the annotation.
#OptIn(ExperimentalSerializationApi::class)
#Serializable
data class User(
#JsonNames("uid")
val id: String,
)
For serialization, the property name will be used. For deserialization, the JSON may contain either the property name or the additional name, both are mapped to the id property.
Related
I'm looking forward to deserialize the following JSON object into a single list with built-in Kotlin serialization.
{
items: CustomObject[]
}
#Serializable
data class CustomObject(val name: String)
The thing is, I don't want my list to be accessible through a wrapper object just for the sake of holding the list in a single field, which would end in something like that: CustomObjectList.items[]
Is there a way of specifying to deserialize the items field here directly into a List<CustomObject>? Thank you
As the title already explains, I would like to deserialize a json string that contains a key that starts with ##. With the ## my standard approach using case classes sadly does not work anymore.
val test = """{"##key": "value"}"""
case class Test(##key: String) // not possible
val gson = new GsonBuilder().create()
val res = gson.fromJson(test, classOf[Test])
How can work with the ## withtout preprocessing the input json string?
The simplest answer is to quote the field name:
case class Test(`##key`: String)
I experimented a bit but it seems that GSON doesn't interoperate well with Scala case classes (or the other way around, I guess it's a matter of perspective). I tried playing around with scala.beans.BeanProperty but it doesn't seem like it makes a difference.
A possible way to go is to use a regular class and the SerializedName annotation, as in this example:
import com.google.gson.{FieldNamingPolicy, GsonBuilder}
import com.google.gson.annotations.SerializedName
final class Test(k: String) {
#SerializedName("##key") val key = k
override def toString(): String = s"Test($key)"
}
val test = """{"##key": "foobar"}"""
val gson = new GsonBuilder().create()
val res = gson.fromJson(test, classOf[Test])
println(res)
You can play around with this code here on Scastie.
You can read more on SerializedName (as well as other naming-related GSON features) here on the user guide.
I'm not a Scala programmer, I just used javap and reflection to check what the Scala compiler generated and slightly "learnt" how some Scala internals work.
It does not work for you because of several reasons:
The Scala compiler puts case class elements annotations to the constructor parameters, whereas Gson #SerializedName can only work with fields and methods:
// does not work as expected
case class Test(#SerializedName("##key") `##key`: String)
From the plain Java perspective:
final Constructor<Test> constructor = Test.class.getDeclaredConstructor(String.class);
System.out.println(constructor);
System.out.println(Arrays.deepToString(constructor.getParameterAnnotations()));
public Test(java.lang.String)
[[#com.google.gson.annotations.SerializedName(alternate=[], value=##key)]]
Not sure why the Scala compiler does not replicate the annotations directly to the fields, but the Java language does not allow annotating parameters with the #SerializedName annotation causing a compilation error (JVM does not treats it as a failure either).
The field name is actually encoded in the class file.
From the Java perspective:
final Field field = Test.class.getDeclaredField("$at$atkey"); // the real name of the `##key` element
System.out.println(field);
System.out.println(Arrays.deepToString(field.getDeclaredAnnotations()));
private final java.lang.String Test.$at$atkey <- this is how the field can be accessed from Java
[] <- no annotations by default
Scala allows moving annotations to fields and this would make your code work accordingly to how Gson #SerializedName is designed (of course, no Scala in mind):
import scala.annotation.meta.field
...
case class Test(#(SerializedName#field)("##key") `##key`: String)
Test(value)
If for some/any reason you must use Gson and can't annotate each field with #SerializedName, then you can implement a custom type adapter, but I'm afraid that you have to have deep knowledge in how Scala works.
If I understand what Scala does, it annotates every generated class with the #ScalaSignature annotation.
The annotation provides the bytes() method that returns a payload that's most likely can be used to detect whether the annotated type is a case class, and probably how its members are declared.
I didn't find such a parser/decoder, but if you find one, you can do the following in Gson:
register a type adapter factory that checks whether it can handle it (basically, analyzing the #ScalaSignature annotation, I believe);
if it can, then create a type adapter that is aware of all case class fields, their names possibly handling the #SerializedName yourself, as you can neither extend Gson ReflectiveTypeAdapterFactory nor inject a name remapping strategy;
take transient fields (for good) and other exclusion strategies (for completeness) into account;
read/write each non-excluded field.
Too much work, right? So I see two easy options here: either use a Scala-aware JSON tool like other people are suggesting, or annotate each field that have such a special name.
So in this case, I want to consume a third party API to compare capitals with countries, I don't need any other attribute from the Json but those 2, however the API I'm using has a lot of extra attributes on it.
What is the easiest way to deserialize the Json into a class with just those 2 attributes I want?
I tried this but of course it didn't work:
Country country = restTemplate.getForObject( "https://restcountries.eu/rest/v2/capital/"+learning.getCapital(), Country.class);
I understand that this doesn't work because it's trying to map the Json attributes into the class which of course lacks the remaining attributes (It just has two string attributes called Name and Capital).
Annotate the class with #JsonIgnoreProperties:
#JsonIgnoreProperties(ignoreUnknown = true)
public class Country {
You could ignore unkown properties on object mapper for this rest template:
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
if you want this feature globally you can set in your application.properties
spring.jackson.deserialization.FAIL_ON_UNKNOWN_PROPERTIES=false
I am trying to use javax.persistence.* to auto create Table by uesing #Entity.
Here is some problem.
Is there anyway to convert the JsonNode to String By use Annotation.
edit: the Jpa is Spring-Data-Jpa and the JsonNode is fasterxml.jackson
You cannot use a JsonNode on entity column using Spring Data Jpa, You must use String and in another class you can write a method which converts string to Json (a reverse Jason to string) format and Resolved!
Annotate your Json property with #Transient (see https://stackoverflow.com/a/1281957/66686). This will make JPA ignore it.
Have another String property. In the getter and setter transform between String and Json representation.
If you have many properties like this you might want to use an embeddable or if you are using Hibernate a user type (other JPA providers might offer something similar). See this article for an example: https://theodoreyoung.wordpress.com/2012/02/07/custom-user-types-with-jpa-and-spring/
Read this to annotate your column correctly.
It's possible to use a json column with hibernate:
https://prateek-ashtikar512.medium.com/how-to-handle-json-in-postgresql-5e2745d5324
When parsing Json with the Jerkson library following the example in the docs:
case class Person(id: Long, name: String)
parse[Person]("""{"id":1,"name":"Coda"}""") //=> Person(1,"Coda")
If I try and deserialize Json that doesn't contain both the id and the name fields then an error is thrown saying they are needed. Is there a way of setting it up so that if the following Json for a Person was parsed:
{"id":2}
The name field could be defaulted to "John". (I thought this might be possible by setting a default in the parameter in the case class but no luck)
Check out this pull request I did for the Jerkson library. It adds support for case class default parameters.
Pay heed to user ksvladimir's comment, though, which I haven't had time to add to the pull request. (I'll update this answer when I do)