My task is deserialize json object to enum. See example at the end of the page.
But I can't define which enum create in method JsonEnumDeserializer.deserialize (Drive or some other).
I want just annotate my enum #JsonDeserialize(using = JsonEnumDeserializer.class) and it should deserialize all annotated enum by way I described.
1) For this I tried extend org.codehaus.jackson.map.deser.std.EnumDeserializer but it fails at runtime requiring no arg constructor or fails at compile time requiring override just parent constructor EnumDeserializer(EnumResolver<?> res).
I tried dig into EnumDeserializer how it retrieve Enum Class but it did not give me any result.
2) Other solution add field "type" to json, but it's bad solution.
In method JsonEnumDeserializer.deserialize I would like retrieve Class that annotated by #JsonDeserialize (it will solve problem). Could somebody help me?
{
"make":"Mazda",
"model":"6",
"drive":{
"id":"FWD",
"name":"Front wheel drive"
}
}
public class Car {
private String make;
private String model;
private Drive drive;
/* setters and getters */
}
#JsonDeserialize(using = JsonEnumDeserializer.class)
public enum Drive {
FWD, RWD, AWD
}
public class JsonEnumDeserializer extends JsonDeserializer<Enum> {
#Override
public Enum deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
String enumName = jp.readValueAsTree().findValue("id").asText();
return ??? /* Knowing enumName how can I figure out which enum Class return? Drive enum or other enum? I want write universal deserializer for all enum */
}
}
One solution I found is change Jackson to Gson. Gson can say instance of which Class is expected to be returned. Argument Type type.
public class GsonEnumDeserializer implements JsonDeserializer<Enum> {
#Override
public Enum deserialize(JsonElement jsonElement, Type type, JsonDeserializationContext jsonDeserializationContext) throws JsonParseException {
try {
Class enumClass = Class.forName(type.getTypeName());
String enumName = jsonElement.getAsJsonObject().get("id").getAsString();
return (Enum) enumClass.getMethod("valueOf", String.class).invoke(null, enumName);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
/* usage */
Gson gson = new GsonBuilder()
.registerTypeHierarchyAdapter(Enum.class, new GsonEnumDeserializer())
.create();
Car car = gson.fromJson(carJson, Car.class);
You don't need a custom JsonDeserializer at all in this case.
The drive field in Car is an enum, not an Object, so your json should be:
{
"make":"Mazda",
"model":"6",
"drive":"FWD"
}
Either your json text or you're Car class is wrong.
Related
I'm looking for a way to deserialize a subclass using a deserializer registered using the #JsonDeserialize annotation on the abstract super class. If there are better options I'm happy to adapt these options – but I'm not aware of any solution to this problem at the moment.
The core problem is: There is an abstract super class A:
#JsonSerialize(using = SerializerForA.class)
#JsonDeserialize(using = DeserializerForA.class)
public abstract class A {
private String value;
protected A(String value) {
this.value = value;
}
...
}
(The annotations are my attempt to do custom deserialization – maybe it's the wrong approach).
There are some derived classes, and A doesn't know any of the derived classes. Think about A is part of a framework and the derived classes are client code using the framework. Here are two derived classes:
public class B extends A {
public B(String value) {
super(value);
}
...
}
and
public class C extends A {
public C(String value) {
super(value);
}
...
}
These derived classes are used in a "container" class, e.g.:
public class MyClass {
private B b;
private C c;
...
}
And the corresponding JSON looks like this:
{
"b": "value_of_b",
"c": "value_of_c"
}
Writing a serializer is relatively simple:
public class SerializerForA extends JsonSerializer<A> {
#Override
public void serialize(A obj, JsonGenerator gen, SerializerProvider serializers) throws IOException {
gen.writeString(obj.getValue());
}
}
The deserializer would look like this:
public class DeserializerForA extends JsonDeserializer<A> {
#Override
public A deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JacksonException {
A result = ???
return result;
}
}
But how does the deserializer know, which type the resulting object has? Is it possible to get the class name from one of the parameters (e.g. DeserializationContext)?
There are some ways the code can be changed, if it helps. For example, a setter can be used for the value field, instead of the constructor, but I would prefer a constructor, or some factory method (public static A getInstance(String value) { ... }).
Edit (1) The deserializer should be called without any specific code automatically by the ObjectMapper, like:
ObjectMapper mapper = new ObjectMapper();
MyClass myClass = mapper.readValue(json, MyClass.class);
That means, Jackson knows the type of the container class. It also knows the type of the properties a and b.
The reason to use a custom deserializer is that I need to have control over the instance creation process (basically, I want to reuse the same instance of each object for the same value – similar to an enum).
Edit (2) Changing the JSON structure is not an option. But I don't think it should be necessary. If I didn't need to have control over instance creation, the whole implementation would just work out of the box. Additional type information in the JSON should not be necessary.
Edit (3) The purpose of all of this is to implement a framework that can be used by application to create typesafe objects that are stored as JSON. Normally, I would use a Java enum for this purpose, but it is possible, that clients need to read JSON documents that are created by a new version of the framework (with new values), but the client didn't update the framework version yet.
Example:
There is a class called Currency:
public class Currency extends A {
public static final Currency EUR = new Currency("EUR");
}
It is used like this:
public class Transaction {
private Currency currency;
private double amount;
}
The JSON would look like this:
{
"currency": "EUR",
"amount": 24.34
}
Now a new currency is added:
public class Currency extends A {
public static final Currency EUR = new Currency("EUR");
public static final Currency USD = new Currency("USD");
}
Clients with the new framework can produce the following JSON:
{
"currency": "USD",
"amount": 48.93,
}
One client didn't update to the new framework version. This client should be able to read the JSON without crashing.
To sum up, the ObjectMapper is provided with an instance of MyClass containing one B and one C.
Jackson will call the JsonDeserializer<A> both for B and C providing the string "value_of_b" / "value_of_c" (because by reflection, it will know that B and C are instances of A and that's the only deserializer available in the context).
Considering that in the Jackson deserializer you are in a static context (you don't have any concrete instance of A in there, you're just deserializing some string text with information that allows you to create a new instance of MyClass that looks like the serialized instance that they provided you with), then I think the only option you have is to create a factory method somewhere in your code as you guessed (I'd create it directly in the A class):
public static A getInstance(String value) {
...
}
and then inside the deserializer, simply instantiate it from that independently on whether the serialized instance was a B or a C (cause at the end of the day, you only know A so you can't handle anything else):
public final class ADeserializer extends JsonDeserializer<A> {
#Override
public A deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
String value = jsonParser.getText();
return A.getInstance(value);
}
}
So basically each implementation will provide you with the String value that you need to create an A, and of course you will have to create a concrete basic implementation of A on your side in order to instantiate it (because you don't know what the other implementations are, and because you need it to be concrete to create an instance).
You have to include some information during the serialization in the json. There are two ways to achieve that.
First is to enable default typing. This will add class names to your json. It will look like this:
{
"a": [
"A",
{
"value": "a"
}
],
"b": [
"B",
{
"value": "b"
}
]
}
You can enable it on ObjectMapper by calling activateDefaultTyping(ptv, DefaultTyping.OBJECT_AND_NON_CONCRETE)
Second one is to add per-class annotations. You can achieve that by adding those annotations to your abstract class.
#JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.PROPERTY,
property = "type")
#JsonSubTypes({
#Type(value = A.class, name = "a"),
#Type(value = B.class, name = "b")
})
Then the serializer will produce json like this:
{
"a": {
"type": "a",
"value": "value_of_a"
}
"b": {
"type": "b",
"value": "value_of_b"
}
}
A simple solution – that even doesn't need a lot of magic – is to use a factory method and #JsonCreator:
The base class is already known, and also the serializer:
#JsonSerialize(using = SerializerForA.class)
public class A {
protected String value;
public String getValue() {
return value;
}
}
public class SerializerForA extends JsonSerializer<A> {
#Override
public void serialize(A a, JsonGenerator gen, SerializerProvider serializers)
throws IOException {
gen.writeString(a.getValue());
}
}
The inherited classes need to implement a factory method each:
public class B extends A {
#JsonCreator
public static B create(String value) {
B b = new B();
b.value = value;
return b;
}
}
and
public class C extends A {
#JsonCreator
public static C create(String value) {
C c = new C();
c.value = value;
return c;
}
}
Now the following JSON is parsed successfully:
{
"b":"This is B",
"c":"This is C"
}
The obvious downside is, that inherited classes have to implement the factory method. I'd like to avoid that.
Suppose I have a simple class:
public class MyClass {
private final String id;
private final String additionalJson;
}
I want to serialize this class to JSON so that the raw value in additionalJson is written directly into the JSON without writing the field's name.
Additionally, the id field should be transformed so that its name is written as identifier.
Example
The values of an instance are:
id = 1234
additionalJson = "someKey": "someValue"
The serialized JSON I want should be:
{
"identifier": "1234",
"someKey": "someValue"
}
Changing the name of id is trivial to do using Mixins:
public class MyClassMixin {
#JsonProperty("identifier")
private final String id;
}
But I don't have a good way to include the raw JSON without adding the field's name.
My current solution is to write a custom JsonSerializer for MyClass which writes the fields value using writeRaw():
public class MyClassSerializer extends JsonSerializer<MyClass> {
#Override
public void serialize(MyClass myClass, JsonGenerator json, SerializerProvider serializerProvider) throws IOException {
try {
json.writeStartObject();
for (Field field : myClass.getClass().getDeclaredFields()) {
if (Objects.equals(field.getName(), "additionalJson")) {
json.writeRaw(", " + (String)field.get(angebotsTemplate));
} else {
json.writeObjectField(field.getName(),
}
}
json.writeEndObject();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
This correctly includes the additionalJson value (after making the MyClass fields public) but doesn't make use of the Mixin. While I could add code to the serializer to transform the id name I would rather still do this in a Mixin as MyClass may have many fields, each with their own transformations I don't want to define in the Serializer.
Is there a way to achieve what I want, preferably relying on Mixins?
I have a POJO with a field or property, containing collection of objects, something like this:
public class Box {
public List<Items> items;
}
By default, value of items is null, and I do not want to initialize it with empty list.
Now, if I try to serialize it with Jackson, I get NullPointerException. Is there a simple way to make Jackson not break on such value and serialize it as an empty collection: [ ]?
Note. This class is just a simplified example. In reality, there are a hundred of classes and a number of fields with different names in each of them, which are occasionally set to null sometimes somewhere in the code, breaking serialization in runtime.
If you do not want to change the contract of your POJO class, think about the possibility to define custom Jackson serializer / deserializer which extend JsonSerializer<T> and JsonDeserializer<T> respectively. E.g.:
public class CountryDeserializer extends JsonDeserializer<CountryCode> {
#Override
public CountryCode deserialize(final JsonParser jp, final DeserializationContext ctxt) throws IOException {
return CountryCode.getByCode(jp.getText());
}
}
and then
#JsonDeserialize(using=CountryDeserializer.class)
private CountryCode country;
You can check whether your field is null and act accordingly, in both directions (serialization / deserialization).
Have you considered making this class a JavaBean?
In that case, you would be able to give a default value in the getter:
public class Box {
private List<Items> items;
public List<Items> getItems() {
if(null == items) {
return Collections.emptyList();
}
return this.items;
}
//Setter here
}
This approach would prevent a lot of trouble related to Jackson's assumptions.
Update: Based on clarification... You could implement a custom serializer for the list type (and/or any other desired customization). Please note that :
public class ListSerializer extends JsonSerializer<List> {
#Override
public void serialize(List value, JsonGenerator jgen, SerializerProvider provider) throws IOException,
JsonProcessingException {
if (null == value) {
provider.defaultSerializeValue(new ArrayList<Object>(), jgen);
} else {
provider.defaultSerializeValue(value, jgen);
}
}
}
//Then your code could set the serializer on the object mapper as follows:
objectMapper.addSerializer(List.class, new ListSerializer());
Repeat for all such customization.
Code was inspired by this article: http://www.baeldung.com/jackson-custom-serialization
I'm serializing some existing objects with Jackson 2.22, leveragin the MixIn feature to decouple the real object from the Jackson annotations configuration.
Actually my mixin is an interface that declares the same methods of the target class and annotates them, here's an example.
Target class:
public class Product {
// ...
public String getName();
public String getDescription();
public String getPrice();
public String getFinalPrice();
public String getDiscount();
// ...
}
and the mixin:
public interface ProductApi {
#JsonProperty
public String getName();
#JsonProperty("price")
public String getFinalPrice();
}
My JSON should have some more informations, computed from several methods or fields of the target class.
Is this even possible in Jackson?
I tried turning the mixin in a class and adding a new method there, but that didn't work.
public class ProductApi {
#JsonProperty
public String getName();
#JsonProperty("price")
public String getFinalPrice();
#JsonProperty("images")
public List<String> getImages() { /* ... */ }
}
I guess this is because the mixin only provides annotations for the target class, but is the latter that is read for serialization.
Of course, if I change the object to be serialized with a new subclass that contains the new method I need, that works, but the objects come from our services layers, and this would mean I have to rewrite all those methods.
I'm using Jackson with Jersey, so don't want to change Jackson with another library.
Here's how I did it.
The solution is to specify a custom JsonSerializer implementation to the field getter.
First of all, I changed the mixin interface to a class that extends the entity (target) class, so that it can access the target class data.
public class ProductApi extends Product {
#JsonProperty
#Override
public String getName() {
return super.getName();
};
// ...
}
Next, I implemented the JsonSerializer that would create the derived property I want:
public static class ImagesSerializer extends JsonSerializer<String> {
#Override
public void serialize(String value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException {
Product p = (Product) jgen.getCurrentValue();
int num = p.getNumberOfImages();
List<String> imgs = new ArrayList<String>(num);
for(int i = 0; i < num; i++) {
String src = "/include/images/showImage.jsp?"+"id="+p.getId()+"&number="+i;
imgs.add(src);
}
provider.defaultSerializeValue(imgs, jgen);
}
}
This is a really simple implementation, more safety checks should be done.
What this does is, basically, retrieve the whole entity instance from the JSON generator, build up a custom object and then ask Jackson to serialize it.
I implemented it inside my ProductApi as a static class, but just for simplicity.
Finally, the serializer needs to be bound to the JsonProperty annotated field:
public class ProductApi extends Product {
#JsonProperty
#Override
public String getName() {
return super.getName();
};
// ...
#JsonSerialize(using=ImagesSerializer.class)
#JsonProperty("images")
#Override
public String getImage() { // in my entity this returns an image number, whereas in my JSON I want a list of URLs
return "";
}
// ...
}
As a side note, it seems that the returned value of the getImage() method is not used.
Why don't you just make some fields, which should be serialized and use Gson for it?
I want to create a custom serializer in GSON that insert a key/value pair in an object, not a new object. For example, supose this:
class Car {
#Expose
String model;
#Expose
Manufacturer manufacturer;
}
class Manufacturer {
#Expose
String name;
#Expose
String from;
}
I want to get then a JSON like this:
"car":{
"model":"beatle",
"manufacturer":"volkswagen",
"country":"Germany"
}
But no matter how I code the serializer, it insists to create a manufacturer object inside "car"
"manufacturer":{
"name":"volkswagen",
"country":"Germany"
}
How can I fix that, to get only key/value pairs?
PS: I cannot make significative changes in classes, because they are mapping the DB. It is just a example to simulate my problem.
Use custom serializer should help.
private class ManufacturerSerializer implements JsonSerializer<Manufacturer> {
public JsonElement serialize(Manufacturer src, Type typeOfSrc, JsonSerializationContext context) {
return new JsonPrimitive(src.getName()); // or src.name public.
}
}
Refer this: https://sites.google.com/site/gson/gson-user-guide/#TOC-Custom-Serialization-and-Deserialization
You should define you own serializer and deserializer for the Car class which will avoid just serializing composited classes in the trivial way but will render just what you need.
That's not hard at all, for example you will define
class CarSerializer implements JsonSerializer<Car> {
JsonElement serialize(Car src, Type typeOfSrc, JsonSerializationContext context) {
JsonObject o = new JsonObject();
o.addProperty("manufacturer", src.manufacturer.name);
...
}
}
Remember that you need to register the serializer as explained in documentation.
I was not possible to make what I want with JsonSerializer. I could only make it works OK with the new API of GSON, since 2.1 (TypeAdapter class).
public class ManufacturerSerializer extends TypeAdapter<Manufacturer> {
#Override
public void write(JsonWriter out, Manufacturer m) throws IOException {
out.value(m.getName());
out.name("country");
out.value(m.getFrom());
}
/* I only need Serialization, dont use this */
#Override
public Manufacturer read(JsonReader in) throws IOException {
return null;
}
}