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;
}
}
Related
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.
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'm using Retrofit with the default Gson parser for JSON processing. Oftentimes, I have a series of 4~5 related but slightly different objects, which are all subtypes of a common base (let's call it "BaseType"). I know we can deserialize the different JSONs to their respective child models by checking the "type" field. The most commonly prescribed way is to extend a JsonDeserializer and register it as a type adapter in the Gson instance:
class BaseTypeDeserializer implements JsonDeserializer<BaseType> {
private static final String TYPE_FIELD = "type";
#Override
public BaseType deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
if (json.isJsonObject() && json.getAsJsonObject().has(TYPE_FIELD)) {
JsonObject jsonObject = json.getAsJsonObject();
final String type = jsonObject.get(TYPE_FIELD).getAsString();
if ("type_a".equals(type)) {
return context.deserialize(json, AType.class);
} else if ("type_b".equals(type)) {
return context.deserialize(json, BType.class);
} ...
// If you need to deserialize as BaseType,
// deserialize without the current context
// or you will infinite loop
return new Gson().fromJson(json, typeOfT);
} else {
// Return a blank object on error
return new BaseType();
}
}
}
However, in my experience this is really slow, and seemingly because we have to load up the entire JSON document into a JsonElement and then traverse it to find the type field. I also don't like it that this deserializer has to be run on every one of our REST calls, even though the data isn't always necessarily being mapped to a BaseType (or its children).
This foursquare blog post mentioned using TypeAdapters as an alternative but it didn't really go further with an example.
Anybody here know how to use TypeAdapterFactory to deserialize based on a 'type' field without having to read up the entire json stream into a JsonElement object tree?
The custom deserializer should only be run when you have a BaseType or a sub-classes in the deserialization data, not every request. You register it based on the type, and it is only called when gson need to serialize that type.
Do you deserialize BaseType as well as the sub-classes? If so, this line is going to kill your performance --
return new Gson().fromJson(json, typeOfT);
creation of new Gson objects is not cheap. You are creating one each time you deserialize a base class object. Moving this call to a constructor of BaseTypeDeserializer and stashing it in a member variable will improve performance (assuming you do deserialize the base class).
The issue with creating a TypeAdapter or TypeAdapterFactory for selecting type based on the field is that you need to know the type before you start consuming the stream. If the type field is part of the object, you cannot know the type at that point. The post you linked to mentions as much --
Deserializers written using TypeAdapters may be less flexible than
those written with JsonDeserializers. Imagine you want a type field to
determine what an object field deserializes to. With the streaming
API, you need to guarantee that type comes down in the response before
object.
If you can get the type before the object in the JSON stream, you can do it, otherwise your TypeAdapter implementation is probably going to mirror your current implementation, except that the first thing you do is convert to Json tree yourself so you can find the type field. That is not going to save you much over your current implementation.
If your subclasses are similar and you don't have any field conflicts between them (fields with the same name but different types), you can use a data transfer object that has all the fields. Use gson to deserialize that, and then use it create your objects.
public class MyDTO {
String type;
// Fields from BaseType
String fromBase;
// Fields from TypeA
String fromA;
// Fields from TypeB
// ...
}
public class BaseType {
String type;
String fromBase;
public BaseType(MyDTO dto) {
type = dto.type;
fromBase = dto.fromBase;
}
}
public class TypeA extends BaseType {
String fromA;
public TypeA(MyDTO dto) {
super(dto);
fromA = dto.fromA;
}
}
you can then create a TypeAdapterFactory that handles the conversion from DTO to your object --
public class BaseTypeAdapterFactory implements TypeAdapterFactory {
public <T> TypeAdapter<T> create(Gson gson, final TypeToken<T> type) {
if(BaseType.class.isAssignableFrom(type.getRawType())) {
TypeAdapter<T> delegate = gson.getDelegateAdapter(this, type);
return newItemAdapter((TypeAdapter<BaseType>) delegate,
gson.getAdapter(new TypeToken<MyDTO>(){}));
} else {
return null;
}
}
private TypeAdapter newItemAdapter(
final TypeAdapter<BaseType> delagateAdapter,
final TypeAdapter<MyDTO> dtoAdapter) {
return new TypeAdapter<BaseType>() {
#Override
public void write(JsonWriter out, BaseType value) throws IOException {
delagateAdapter.write(out, value);
}
#Override
public BaseType read(JsonReader in) throws IOException {
MyDTO dto = dtoAdapter.read(in);
if("base".equals(dto.type)) {
return new BaseType(dto);
} else if ("type_a".equals(dto.type)) {
return new TypeA(dto);
} else {
return null;
}
}
};
}
}
and use like this --
Gson gson = new GsonBuilder()
.registerTypeAdapterFactory(new BaseTypeAdapterFactory())
.create();
BaseType base = gson.fromJson(baseString, BaseType.class);
i have the following problem.
I have to parse a json request into an object that contains a generic type field.
EDIT
i have made some tests using a regular class type (so i make it work before i replace it with generic). Now parsing for a single element works great.
The issue is when i need to parse out a list object out of that class.
So i have to inform jackson somehow that my T is of type list instead of just AlbumModel.
Here is what i have tried.
#Override
public ListResponseModel<AlbumModel> parse(String responseBody) throws Exception {
JavaType type = mapper.getTypeFactory().constructParametricType(ResponseModel.class,
AlbumModel.class);
return mapper.readValue(responseBody,
mapper.getTypeFactory().constructParametricType(ResponseModel.class, type));
}
But the code above doesn't work. what is the solution for something like this?
my generic type in the ListResponseModel is defined like: List<T> data
succeeded like:
public class BaseResponseModel<T> {
#JsonProperty("data")
private T data;
#JsonProperty("paginations")
private PaginationModel pagination;
}
so far i have the following code but it always parses into a Hash.
public class ResponseParser extends BaseJacksonMapperResponseParser<ResponseModel<AlbumModel>> {
public static final String TAG = ResponseParser.class.getSimpleName();
#Override
public ResponseModel<AlbumModel> parse(String responseBody) throws Exception {
return mapper.readValue(responseBody,
mapper.getTypeFactory().constructParametricType(ResponseModel.class, AlbumModel.class));
}
}
public abstract class BaseJacksonMapperResponseParser<T> implements HttpResponseParser<T> {
public static final String TAG = BaseJacksonMapperResponseParser.class.getSimpleName();
public static ObjectMapper mapper = new ObjectMapper();
static {
mapper.disable(Feature.FAIL_ON_UNKNOWN_PROPERTIES);
mapper.enable(Feature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT);
mapper.configure(SerializationConfig.Feature.WRAP_ROOT_VALUE, true);
}
}
I agree with eugen's answer but just wanted to expand on it a bit. The first step is to refactor your parse method so it takes a second argument. Instead of allocating the type reference in your method, you require the caller to pass in a TypeReference instance.
public BaseResponseModel<T> parse(String responseBody, TypeReference<T> ref) throws Exception {
return mapper.readValue(responseBody, ref);
}
Unfortunately your snippet does not show the code which calls parse - so I'll make something up:
BaseResponseParser<Collection<Person>> parser = new BaseResponseParser<Collection<Person>>();
BaseResponseModel<Collection<Person>> result = parser.parse(jsonText, new TypeReference<Collection<Person>>(){});
Notice that when the TypeReference instance is compiled in this case, it a type reference to the real concrete class that we expect.
You could do the same thing passing in a Class at runtime, however TypeReference is a bit more powerful because it even works when type T is a generic collection. There is some magic in the TypeReference implementation that allows it to hold onto type information that would normally be erased.
[update]
Updated to use Collection<Person>. Note - as far as I know as List<Whatever> should work also, but I double checked a project where I was using jackson to deserialize collections. Base class Collection definitely worked so I stayed with that.
Your type T will be "erased" at runtime, so Jackson does not know what is the real type of T and deserializes it to a Map. You need a second parameter to your parse method that will be Class<T> clazz or TypeReference<T> or java.lang.reflect.Type.
EDIT
Small explanation on the magic of TypeReference. When you do new XX() {} you are creating a anonymous class, so if it is a class with typevariables (parameterized if you prefer), new X<List<Y>>() {}, you will be able to retrieve List<Y> as a java Type at runtime. It is very similar as if you had done :
abstract class MyGenericClass<T> {}
class MySpecializedClass extends MyGenericClass<List<Y>> {}
Since you're using Jackson you probably need to create a custom JsonDeserializer or JsonSerializer depending on whether you're handing the response or request. I've done this with Dates because on my response I want a standard view. I'm not 100% positive it will work with a generic field though. Here is an example of what I'm doing:
public class DateSerializer extends JsonSerializer<Date> {
private SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZZ");
#Override
public void serialize(Date value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException {
String dateString = dateFormat.format(value);
jgen.writeString(dateString);
}
}
Then I just add it to my class like so:
#JsonSerialize(using = DateSerializer.class)
public Date getModifiedDate() {
return modifiedDate;
}