How to use #JsonConverter with Genson? - java

I'm trying to serialise an object with a Long id to JSON, using Genson.
It works well if I serialise to JSON and back into Java. But I'm deserialising in JavaScript.
JavaScript can't support a full 64-bit unsigned int as a Number (I'm finding the last few bits of my id are being zeroed in JavaScript), so I need to convert the Long id to a String during serialisation.
I don't want to convert all the Longs in the Object, so I'm trying to use a Converter for just the id field.
import com.owlike.genson.annotation.JsonConverter;
import javax.persistence.Id;
import lombok.Getter;
import lombok.Setter;
...
/** the local database ID for this order */
#JsonConverter(LongToStringConverter.class)
#Id
#Setter
#Getter
private Long id;
/** The unique ID provided by the client */
#Setter
#Getter
private Long clientKey;
My converter code looks like this:
public class LongToStringConverter implements Converter<Long> {
/** Default no-arg constructor required by Genson */
public LongToStringConverter() {
}
#Override
public Long deserialize(ObjectReader reader, Context ctx) {
return reader.valueAsLong();
}
#Override
public void serialize(Long obj, ObjectWriter writer, Context ctx) {
if (obj != null) {
writer.writeString(obj.toString());
}
}
}
I'm not doing anything special when invoking serialisation itself:
Genson genson = new GensonBuilder().useIndentation(true).create();
String json = genson.serialize( order );
This doesn't work. Output still looks like this:
{
"clientKey":9923001278,
"id":1040012110000000002
}
What I'm trying to achieve is this:
{
"clientKey":9923001278,
"id":"1040012110000000002" // value is quoted
}
I did also try to pass my Converter into a GensonBuilder but that hits all the Longs in the object, which isn't what I need.
Any advice?

Well, I'm not clear on WHY but it looks like Genson just doesn't get presented with the annotation. It may be down to the use of Hibernate or Lombok.
The solution seems to be to force Genson to consider the annotated field.
I did this by using the GensonBuilder:
Genson genson = new GensonBuilder().useIndentation(true).include("id").create();
String json = genson.serialize( order );
EDIT:
Incorporating Eugen's answer above, this also works because it instructs Genson to look at the private field instead of relying on the getter/setter:
Genson genson2 = new GensonBuilder().useFields(true, VisibilityFilter.PRIVATE).useMethods(true).create();

Related

Not able to convert underscore case to camel case with Jackson

I have a DTO class which has a property like:
#JsonIgnoreProperties(ignoreUnknown = true)
public class WPPostResponse {
#JsonProperty("featuredMedia")
Long featured_media;
public Long getFeatured_media() {
return featured_media;
}
public void setFeatured_media(Long featured_media) {
this.featured_media = featured_media;
}
}
The input JSON has the key featured_media. I convert the JSON string to the object and then sends it to the client response as JSON. I want the final response JSON to have featuredMedia as the key. I am however getting null as the value. If I remove the JsonProperty, it gives the value, but the key is having underscore. How to fix this? Thanks.
Always respect the Java naming conventions in your Java code. Use annotations to deal with Json not respecting them.
In this case, use JsonAlias
Annotation that can be used to define one or more alternative names for a property, accepted during deserialization as alternative to the official name
public class WPPostResponse {
#JsonAlias("featured_media")
Long featuredMedia;
public Long getFeaturedMedia() {
return featuredMedia;
}
public void setFeaturedMedia(Long featuredMedia) {
this.featuredMedia = featuredMedia;
}
}
You can use the JsonProperty on setters and getters to have different namings during serialization and deserialization
#JsonIgnoreProperties(ignoreUnknown = true)
public class WPPostResponse {
Long featuredMedia;
#JsonProperty("featuredMedia") // output will be featuredMedia
public Long getFeatured_media() {
return featuredMedia;
}
#JsonProperty("featured_media") // input should be featured_media
public void setFeatured_media(Long featured_media) {
this.featuredMedia = featured_media;
}
}
And also you set access level to #JsonProperty annotation
#JsonProperty(value = "featured_media", access = JsonProperty.Access.WRITE_ONLY)

Genson Polymorphic / Generic Serialization

I am trying to implement a JSON serialization in Java with Genson 1.3 for polymorphic types, including:
Numbers
Arrays
Enum classes
The SSCCE below demonstrates roughly what I am trying to achieve:
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import com.owlike.genson.Genson;
import com.owlike.genson.GensonBuilder;
/**
* A Short, Self Contained, Compilable, Example for polymorphic serialization
* and deserialization.
*/
public class GensonPolymoprhicRoundTrip {
// our example enum
public static enum RainState {
NO_RAIN,
LIGHT_RAIN,
MODERATE_RAIN,
HEAVY_RAIN,
LIGHT_SNOW,
MODERATE_SNOW,
HEAVY_SNOW;
}
public static class Measurement<T> {
public T value;
public int qualityValue;
public String source;
public Measurement() {
}
public Measurement(T value, int qualityValue, String source) {
this.value = value;
this.qualityValue = qualityValue;
this.source = source;
}
}
public static class DTO {
public List<Measurement<?>> measurements;
public DTO(List<Measurement<?>> measurements) {
this.measurements = measurements;
}
}
public static void main(String... args) {
Genson genson = new GensonBuilder()
.useIndentation(true)
.useRuntimeType(true)
.useClassMetadataWithStaticType(false)
.addAlias("RainState", RainState.class)
.useClassMetadata(true)
.create();
DTO dto = new DTO(
new ArrayList(Arrays.asList(
new Measurement<Double>(15.5, 8500, "TEMP_SENSOR"),
new Measurement<double[]>(new double[] {
2.5,
1.5,
2.0
}, 8500, "WIND_SPEED"),
new Measurement<RainState>(RainState.LIGHT_RAIN, 8500, "RAIN_SENSOR")
)));
String json = genson.serialize(dto);
System.out.println(json);
DTO deserialized = genson.deserialize(json, DTO.class);
}
}
Numbers and Arrays worked well out-of-the-box, but the enum class is providing a bit of a challenge. In this case the serialized JSON form would have to be IMO a JSON object including a:
type member
value member
Looking at the EnumConverter class I see that I would need to provide a custom Converter. However I can't quite grasp how to properly register the Converter so that it would be called during deserialization. How should this serialization be solved using Genson?
Great for providing a complete example!
First problem is that DTO doesn't have a no arg constructor, but Genson supports classes even with constructors that have arguments. You just have to enable it via the builder with 'useConstructorWithArguments(true)'.
However this will not solve the complete problem. For the moment Genson has full polymorphic support only for types that are serialized as a json object. Because Genson will add a property called '#class' to it. There is an open issue for that.
Probably the best solution that should work with most situations would be to define a converter that automatically wraps all the values in json objects, so the converter that handles class metadata will be able to generate it. This can be a "good enough" solution while waiting for it to be officially supported by Genson.
So first define the wrapping converter
public static class LiteralAsObjectConverter<T> implements Converter<T> {
private final Converter<T> concreteConverter;
public LiteralAsObjectConverter(Converter<T> concreteConverter) {
this.concreteConverter = concreteConverter;
}
#Override
public void serialize(T object, ObjectWriter writer, Context ctx) throws Exception {
writer.beginObject().writeName("value");
concreteConverter.serialize(object, writer, ctx);
writer.endObject();
}
#Override
public T deserialize(ObjectReader reader, Context ctx) throws Exception {
reader.beginObject();
T instance = null;
while (reader.hasNext()) {
reader.next();
if (reader.name().equals("value")) instance = concreteConverter.deserialize(reader, ctx);
else throw new IllegalStateException(String.format("Encountered unexpected property named '%s'", reader.name()));
}
reader.endObject();
return instance;
}
}
Then you need to register it with a ChainedFactory which would allow you to delegate to the default converter (this way it works automatically with any other type).
Genson genson = new GensonBuilder()
.useIndentation(true)
.useConstructorWithArguments(true)
.useRuntimeType(true)
.addAlias("RainState", RainState.class)
.useClassMetadata(true)
.withConverterFactory(new ChainedFactory() {
#Override
protected Converter<?> create(Type type, Genson genson, Converter<?> nextConverter) {
if (Wrapper.toAnnotatedElement(nextConverter).isAnnotationPresent(HandleClassMetadata.class)) {
return new LiteralAsObjectConverter(nextConverter);
} else {
return nextConverter;
}
}
}).create();
The downside with this solution is that useClassMetadataWithStaticType needs to be set to true...but well I guess it is acceptable as it's an optim and can be fixed but would imply some changes in Gensons code, the rest still works.
If you are feeling interested by this problem it would be great you attempted to give a shot to that issue and open a PR to provide this feature as part of Genson.

JSON Jackson parse different keys into same field

I have a POJO which has a field:
public class Media {
private Asset asset;
}
Everything works perfectly when parsing a json response into this asset POJO. but however there is a slight difference with the key this asset comes with. It can either be:
#JsonProperty("cover_asset")
or
#JsonProperty("asset")
Is there a way to annotate the POJO to recognize this case and de-serialize into the same field. Its not possible for both of them to appear in the same response.
Well, as only deserialization is your concern, #JsonAlias introduced in 2.9 is perfect for this situation. You can do something like this:
#JsonAlias({"cover_asset", "asset"})
private Asset asset;
#JsonAlias docs:
Annotation that can be used to define one or more alternative names
for a property, accepted during deserialization as alternative to the
official name. Alias information is also exposed during POJO
introspection, but has no effect during serialization where primary
name is always used.
Note: Make sure you update all related dependencies(annotations, core, databind) if you are using them. Updating just annotations without others threw me runtime error.
More succinctly, I would suggest using two separate #JsonSetter annotations for this. Here's a working example. This means that your java class will only have one getter method to use for the property instead of two. You can also make the setter you don't want exposed to clients of Media private and treat one of the json keys in a special manner.
import com.fasterxml.jackson.annotation.JsonGetter;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonSetter;
import com.fasterxml.jackson.databind.ObjectMapper;
#SuppressWarnings("unused")
public class Media {
private Asset asset;
#JsonGetter("asset")
public Asset getAsset() {
return asset;
}
#JsonSetter("asset")
public void setAsset(Asset asset) {
this.asset = asset;
}
#JsonSetter("cover_asset")
private void setMediaAsset(Asset asset) {
if (this.asset == null) {
setAsset(asset);
}
}
private static class Asset {
#JsonProperty("foo")
private String foo;
}
public static void main(String[] args) throws Exception {
String withAsset = "{'asset': {'foo':'bar'}}";
String withCoverAsset = "{'cover_asset': {'foo':'bar'}}";
ObjectMapper mapper = new ObjectMapper();
Media mediaFromAsset = mapper.readValue(withAsset.replace('\'','"'), Media.class);
Media mediaFromCoverAsset = mapper.readValue(withCoverAsset.replace('\'','"'), Media.class);
System.out.println(mediaFromAsset.asset.foo.equals(mediaFromCoverAsset.asset.foo));
}
}
Great answer By Vikas with JsonAlias.
Just adding that you can also benefit from both of the worlds (JsonProperty&Alias) [Since jackson 2.9]:
#JsonProperty("cover_asset")
#JsonAlias({"asset", "cover_asset","amazing_asset"})
private Asset asset;
Reference.
I'd propose to use getters/setters, for both property names, which are referring to the same POJO field.
public class Media {
private Asset asset;
#JsonProperty("cover_asset")
public Asset getCoverAsset() {
return asset;
}
public void setCoverAsset(Asset asset) {
this.asset= asset;
}
#JsonProperty("asset")
public Asset getAsset() {
return asset;
}
public void setAsset(Asset asset) {
this.asset= asset;
}
}
See also my answer to possible duplicate question:
Different names of JSON property during serialization and deserialization

Play Framework: Rendering custom JSON objects

I am using Play Framework 1.2.4 with Java and using JPA to persist my database objects. I have several Model classes to be rendered as JSON. But the problem is I would like to customize these JSON responses and simplify the objects just before rendering as JSON.
For instance, assume that I have an object named ComplexClass and having properties id, name, property1,...,propertyN. In JSON response I would like to render only id and name fields.
What is the most elegant way of doing this? Writing custom binder objects or is there simple JSON mapping such as using a template?
Play Framework 1.2.4 directly depends on the gson library so you could use that to render your JSON strings. All you have to do is use gson's #Expose annotation. So in your example, you would mark the fields you want in your JSON string like this:
public class ComplexClass {
#Expose
public Long id;
#Expose
public String name;
...
}
Then in your controller, you would just do this:
public static void someActionMethod() {
// get an instance of your ComplexClass here
ComplexClass complex = ...
Gson gson = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create()
String json = gson.toJson(complex);
renderJson(json);
}
See documentation here.
If ComplexClass is actually a play.db.jpa.Model and therefore the id field is abstracted away in a parent class and you can't put the #Expose annotation on it, then you could create your own ExclusionStrategy that skips fields that aren't annotated with #Expose and are not called id. So something like this (pseudo-code):
public final class ComplexClassExclusionStrategy implements ExclusionStrategy {
public boolean shouldSkipField(FieldAttributes attributes) {
if (name of field is "id") return false;
if (field is annotated with #Expose) return false;
return true;
}
Then the controller would altered slightly to look like this:
GsonBuilder builder = new GsonBuilder();
ComplexClassExclusionStrategy strategy = new ComplexClassExclusionStrategy();
builder.setExclusionStrategies(strategy);
Gson gson = builder.create();
String json = gson.toJson(complex);
renderJson(json);
Use FlexJSON, it's really easy. It allows you to create JSONSerializers which can include/exclude the fields you want.
Check out this article for some examples of using it with Play! Framework.
Here's a simple example:
public ComplexClass {
public Long id;
public String name;
// And lots of other fields you don't want
public String toJsonString() {
// Include id & name, exclude all others.
JSONSerializer ser = new JSONSerializer().include(
"id",
"name",
).exclude("*");
return ser.serialize(this);
}
}
You can add it to your dependencies.yml like so:
require:
- play
- net.sf.flexjson -> flexjson 2.1
What I usually do is write an interface for models that implements a toJSONString() method so that I can call renderJSON(someModel.toJSONString()) in the controller.
Link to official website
EDIT: Extra example for lists/collections
Ok, when you start serializing list you might get some unexpected results. This is because the order of evaluation is important. The first include() or exclude() takes precedence over the following ones.
Here's an example of serializing the childs of a parent entity (OneToMany relation).
JSONSerializer ser = new JSONSerializer();
// Exclude these standard fields from childs
ser.exclude(
"*.persistent",
"*.class",
"*.entityId"
);
// Include childs and all its other fields
ser.include(
"childs",
"childs.*"
);
// Exclude everything else
ser.exclude("*");
String data = ser.serialize(parent);
The * is a wildcard by the way. This piece of documentation explains it perfectly:
An exclude of *.class will match to any path depth. So if flexjson is serializing the field with path of "foo.bar.class" the * in *.class will match foo.bar.

Serialization third-party classes with Simple XML (org.simpleframework.xml)

I have decided to use Simple XML serialization and was stucked with basic problem. I am trying to serialize java.util.UUID class instance as final field in this small class:
#Root
public class Identity {
#Attribute
private final UUID id;
public Identity(#Attribute UUID id) {
this.id = id;
}
}
Tutorial shows how to serialize third-party objects by registering converters like this:
Registry registry = new Registry();
registry.bind(UUID.class, UUIDConverter.class);
Strategy strategy = new RegistryStrategy(registry);
Serializer serializer = new Persister(strategy);
serializer.write( object, stream );
appropriate converter for UUID is pretty simple:
public class UUIDConverter implements Converter<UUID> {
#Override
public UUID read(InputNode node) throws Exception {
return new UUID.fromString(node.getValue());
}
#Override
public void write(OutputNode node, UUID value) throws Exception {
node.setValue(value.toString());
}
}
But this simple code just didn't work for me, during serialization objects with UUID fields was thrown exception Transform of class java.util.UUID not supported.
I have tried something something similar with custom Matcher (which was not in tutorial) that works for me:
Serializer serializer = new Persister(new MyMatcher());
serializer.write( object, stream );
and Matcher class looks like this:
public static class MyMatcher implements Matcher {
#Override
#SuppressWarnings("unchecked")
public Transform match(Class type) throws Exception {
if (type.equals(UUID.class))
return new UUIDTransform();
return null;
}
}
public class UUIDTransform implements Transform<UUID> {
#Override
public UUID read(String value) throws Exception {
return UUID.fromString(value);
}
#Override
public String write(UUID value) throws Exception {
return value.toString();
}
}
Questions:
Is custom Matcher always recommended practice for streaming third-party classes?
In which case I can use Converter?
Are there any better tutorials/examples for Simple XML out there?
Thank you.
I have to answer by myself again :-)
Advice from Niall Gallagher, project leader of Simple XML, from support-list:
"You could use either a Converter or a Transform. I would say
for a UUID a Transform with a Matcher would be the easiest option."
So, I use Transform<T>/Matcher and satisfied with it. This does not alter the fact that the Converter<T> does not work for me :-)
I think i have the answer to this.
Strategy strategy = new AnnotationStrategy();
Serializer serializer = new Persister(strategy);
should register the converter and solve the problem.
I know this is a bit aold but my chance i came to the same exception.
The actual issue is the use of the #Attribute annotation. If instead of #Attribute
you put #Element the exception does not appear and the converter is used for the serialization.
I guess it will then depend on which annotation you used that you should create a Converter or use the Marker-Transform solution. Although i do not know if this is the intendent behaviour.

Categories