Specify class fields to be serialized to JSON in Ktor - json

Serving JSON content in Ktor as described in HTTP API - Quick Start - Ktor, as shown in the examples, works for common collections (lists, maps, etc.) and data classes. However, if I want to serialize a class that is not a data class and has fields that I want to exclude, how do I specify the fields to be serialized and their serialized names? Assume that I am using Gson, can I do it in the same way as serializing a class object using Gson directly?

Using Gson, you have a couple of options to the best of my knowledge.
1. Using Transient
If you mark a field with #Transient (transient in Java) this will be excluded from serialization:
data class Foo(
#Transient val a: Int,
val b: Int)
Here, b will be serialized and a will not.
This comes with a huge downside - almost every framework in java takes #Transient into account and sometimes you don't want it to be serialized by Gson, but you might want to persist it to the database for example (if you'd be using the same class for both). To account for this, there's another option, using #Expose.
2. Using Expose
You need to create the gson instance using the builder:
val gson = GsonBuilder()
.excludeFieldsWithoutExposeAnnotation()
.create();
Now, fields without #Expose won't be serialized:
data class Foo(
val a: Int,
#Expose val b: Int)
Again, a will not be serialized, but b will.
3. Using exclusion strategies
A more advanced method is the usage of exclusion strategies. This allows for loads of introspections on the fields. From custom annotations to the field name or type.
Again, you need to create a gson with a builder:
val gson = GsonBuilder()
.addSerializationExclusionStrategy(strategyInstance)
.create();
And you define a strategy like:
object : ExclusionStrategy() {
override fun shouldSkipField(field: FieldAttributes): Boolean {
}
override fun shouldSkipClass(clazz: Class<*>): Boolean {
}
}
inside shouldSkipField you return true when you don't want to serialize the field and false when you do. Because it receives a FieldAttributes you can get a lot of properties from the field such as name and annotations. This allows for very fine-grained control.
Lastly, you can set this strategy for deserialization as well and for both - addDeserializationExclusionStrategy and setExclusionStrategies.

Related

Kotlin: unit test assert object after gson

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

How to add extra fields to an Object during Jackson's json serialization?

I need to add new property to an object, when serializing to JSON. The value for the property is calculated on runtime and does not exist in the object. Also the same object can be used for creation of different JSON with different set ot fields (kind of having a base class with subclasses, but I don't want to create ones just for JSON generation).
What is the best way of doing that, which doesn't involve creation of custom serializer class, which will take care of serializing of whole set of object's fields? Or may be it is possible to inherit some "basic" serializer, and simply take it's output and add new field to it somehow?
I learned about mixins, and looks like it is possible to rename/hide some fields, however it seems not be possible to add an extra one.
Can you not just add a method in value class? Note that it does not have to be either public, or use getter naming convention; you could do something like:
public class MyStuff {
// ... the usual fields, getters and/or setters
#JsonProperty("sum") // or whatever name you need in JSON
private int calculateSumForJSON() {
return 42; // calculate somehow
}
}
Otherwise you could convert POJO into JSON Tree value:
JsonNode tree = mapper.valueToTree(value);
and then modify it by adding properties etc.
2021 calling...
Simplest way I found to do this is #JsonUnwrapped:
public class Envelope<T> {
#JsonUnwrapped // content's fields are promoted alongside the envelope's
public T content;
// Transmission specific fields
public String url;
public long timestamp;
}
This works (bi-directionally) so long as Envelope's fieldnames do not clash with those of content. Also has a nice feature of keeping the transmission properties at the end of the serialised JSON.
One option is to add a field for this property and set it on the object before writing to JSON. A second option, if the property can be computed from other object properties you could just add a getter for it, for example:
public String getFullName() {
return getFirstName() + " " + getLastName();
}
And even though there's no matching field Jackson will automatically call this getter while writing the JSON and it will appear as fullName in the JSON output. If that won't work a third option is to convert the object to a map and then manipulate it however you need:
ObjectMapper mapper //.....
MyObject o //.....
long specialValue //.....
Map<String, Object> map = mapper.convertValue(o, new TypeReference<Map<String, Object>>() { });
map.put("specialValue", specialValue);
You're question didn't mention unmarshalling but if you need to do that as well then the first option would work fine but the second two would need some tweaking.
And as for writing different fields of the same object it sounds like a job for #JsonView

force jackson mapper to always add class type on writeValue without annotations

Is it possible to configure jackson to always add the type of the serialized object to the generated json output.
For example:
package org.acme;
class ClassA
{
String a;
String b;
}
and I want the generated json to be:
["org.acme.ClassA",{"a":"str1","b":"str2"}]
You can do that with enableDefaultTyping() of the ObjectMapper
e.g.
mapper.enableDefaultTyping(DefaultTyping.OBJECT_AND_NON_CONCRETE);
See ObjectMapper API
If your are free to change from Jackson and do not especially need the format to match the one your are showing you can try Genson http://code.google.com/p/genson.
For example if your requirement is to be able to deserialize interfaces or abstract classes based on the original type of the object you serialized you can do:
interface Entity {}
static class Person implements Entity {}
Genson genson = new Genson.Builder().setWithClassMetadata(true).create();
// json will be equal to {"#class":"my.package.Person"}
String json = genson.serialize(new Person());
// and now Genson is able to deserialize it back to Person using the information
// in the Json Object
Person person = (Person) genson.deserialize(json, Entity.class);
Another nice feature is the ability to define aliases for your classes, so you show less information in the json stream but also this allows you to do refactoring without worring of existing json streams (for example if you store it in a database).
Genson genson = new Genson.Builder().addAlias("person", Person.class).create();
// json value is {"#class": "person"}
String json = genson.serialize(new Person());
Have a look at the wiki.

rest template serialize/deserialize Map objects with variosu key , value pairs

I am using resttemplate with jackson to marshall/unmarshall java/json objects.
What would be the best strategy to serialize/deserialize
a Map that may contain key value pairs such that keys are strings and values could
be various types for example an ArrayList of custom objects
I did some research on this site and found the use of #JsonAnyGetter #JsonAnySetter
could be used in this situation, but wasnt sure of how to deserialize in the context
of resttemplate getforobject method. Would one have to write a custom httpmessageconverter
to accomplish the deserialization?
Thanks in advance.
We'll assume you have a response like this:
{ key1: "something", key2: 3}
You'll want to have a DTO that has those fields:
class CustomResponse {
private String key1;
private long key2;
}
Make sure you add getters and setters for the above.
Now make your request:
restTemplate.postForObject(url, requestObject, CustomResponse.class);
The request object can be either a DTO like the above or just use Arrays and Maps to construct the requestObject.
You should add this annotation to your response DTOs. This ensures that if there are fields in the response that don't map in your DTO, they will be ignored.
#JsonIgnoreProperties(ignoreUnknown = true)

Prevent certain fields from being serialized

In the Play framework i have a few models that have fields which are object references to other models. When i use renderJSON, i don't want those object references to be included. Currently for my needs i create a separate view model class which contains the fields i want, and in the controller i create instances of this view class as needed. Ideally i would like to be able to use the model class itself without having to write the view class.
Is there a way to annotate a field so that it will not be serialized when using renderJSON?
because play uses Gson for its Json serialization you can try the following:
public static void test()
{
Object foo = new SomeObject("testData");
Gson gson = new GsonBuilder()
.excludeFieldsWithModifiers(Modifier.TRANSIENT)
.create();
renderJSON(gson.toJson(foo));
}
now each field marked as transient will not be serialized. There is also another (better) way. You can use the com.google.gson.annotations.Expose annotation to mark each field you want to serialize.
public static void test()
{
Object foo = new SomeObject("testData");
Gson gson = new GsonBuilder()
.excludeFieldsWithoutExposeAnnotation()
.create();
renderJSON(gson.toJson(foo));
}
Using FlexJSON with play is another option, explained in this article: http://www.lunatech-research.com/archives/2011/04/20/play-framework-better-json-serialization-flexjson
Not sure why no one has written the most direct solution to this answer so I will do it here:
Simply mark the fields you do not want serialized via Gson as transient.
Here's an example:
public class Animal
{
private String name = "dog";
transient private int port = 80;
private String species = "canine";
transient private String password = "NoOneShouldSeeThis";
}
None of the items which are marked transient will be serialized.
When deserialized they will be set to their default (class default) values.
Resulting JSON will look like the following:
{"name":"dog","species":"canine"}
For more information on transient you can see the SO
Why does Java have transient fields?
I would override renderJSON to check a the field name against a member array of serialization exclusions.