When you have a class to be converted to a json, if it contains a BigDecimal attribute, it will return a json like this:
Response {
BigDecimal price;
}
//json:
{
price: 20.20
}
Note that BigDecimal is a class. Its behavior is like an primitive (integer, float).
I want to produce the same behavior (a class return a single information to json)
Example:
class Response {
Money value
}
Money {
BigDecimal price;
}
//What is returning:
{
value : { price: 20.20 }
}
//What I want:
{
value : 20.20
}
Gson doesn't have such a feature out of the box. You'll need to implement it yourself.
If it's just for the Response type, you can simply implement your own TypeAdapter.
class ResponseTypeAdapter extends TypeAdapter<Response> {
#Override
public void write(JsonWriter out, Response value) throws IOException {
out.beginObject();
out.name("value");
// check for null, if applicable, and use a default value, or don't write anything at all
out.value(value.getValue().getPrice());
out.endObject();
}
#Override
public Response read(JsonReader in) throws IOException {
// implement the deserialization
}
}
Then register it.
Gson gson = new GsonBuilder().registerTypeAdapter(Response.class, new ResponseTypeAdapter()).create();
// test it
String json = gson.toJson(new Response(new Money(new BigDecimal("20.20"))));
This would now serialize to
{"value":20.20}
If you can use Jackson, it comes with a #JsonValue annotation which does this for you. For example,
class Money {
private final BigDecimal price;
public Money(BigDecimal bigDecimal) {
this.price = bigDecimal;
}
#JsonValue // <<< this
public BigDecimal getPrice() {
return price;
}
}
used with
ObjectMapper mapper = new ObjectMapper();
String json = mapper.writeValueAsString(new Response(new Money(new BigDecimal("20.20"))));
will generate
{"value":20.20}
The javadoc states
Marker annotation similar to javax.xml.bind.annotation.XmlValue that
indicates that results of the annotated "getter" method (which means
signature must be that of getters; non-void return type, no args) is
to be used as the single value to serialize for the instance. Usually
value will be of a simple scalar type (String or Number), but it can
be any serializable type (Collection, Map or Bean).
At most one method of a Class can be annotated with this annotation;
if more than one is found, an exception may be thrown. Also, if method
signature is not compatible with Getters, an exception may be thrown
(whether exception is thrown or not is an implementation detail (due
to filtering during introspection, some annotations may be skipped)
and applications should not rely on specific behavior).
Related
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;
}
I am serializing and deserializing following domain object to JSON using Jackson 1.8.3
public class Node {
private String key;
private Object value;
private List<Node> children = new ArrayList<Node>();
/* getters and setters omitted for brevity */
}
Object is then serialized and deserialized using following code
ObjectMapper mapper = new ObjectMapper();
mapper.writeValue(destination, rootNode);
And then later deserialized with
mapper.readValue(destination, Node.class);
The original values of the object are either Strings, Doubles, Longs or Booleans. However, during serialization and deserialization Jackson transforms Long values (such as 4) to Integers.
How can I "force" Jackson to deserialize numeric non-decimal values to Long instead of Integer?
There is a new feature in Jackson 2.6 specifically for this case:
configure the ObjectMapper to use DeserializationFeature.USE_LONG_FOR_INTS
see https://github.com/FasterXML/jackson-databind/issues/504
cowtowncoder pushed a commit that closed this issue on May 19, 2015
Fix #504 and #797
If type is declared as java.lang.Object, Jackson uses 'natural' mapping which uses Integer if value fits in 32 bits. Aside from custom handlers you would have to force inclusion of type information (either by adding #JsonTypeInfo next to field / getter; or by enabling so-called "default typing").
I ended up creating a custom deserializer, since in my application logic there are only four different types for values (Double, Long, Integer and String).
I'm not sure if this is the best possible solution but it works for now.
public class MyDeserializer extends JsonDeserializer<Object> {
#Override
public Object deserialize(JsonParser p, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
try {
Long l = Long.valueOf(p.getText());
return l;
} catch (NumberFormatException nfe) {
// Not a Long
}
try {
Double d = Double.valueOf(p.getText());
return d;
} catch (NumberFormatException nfe) {
// Not a Double
}
if ("TRUE".equalsIgnoreCase(p.getText())
|| "FALSE".equalsIgnoreCase(p.getText())) {
// Looks like a boolean
return Boolean.valueOf(p.getText());
}
return String.valueOf(p.getText());
}
}
I've used something like the below to work around this problem.
#JsonIgnoreProperties(ignoreUnknown = true)
public class Message {
public Long ID;
#JsonCreator
private Message(Map<String,Object> properties) {
try {
this.ID = (Long) properties.get("id");
} catch (ClassCastException e) {
this.ID = ((Integer) properties.get("id")).longValue();
}
}
}
In my case I did not want to use DeserializationFeature.USE_LONG_FOR_INTS for ObjectMapper, because it would affect all the project. I used the next solution: use a custom deserializer:
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.ObjectCodec;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonNode;
import java.io.IOException;
public class LongInsteadOfIntegerDeserializer extends JsonDeserializer<Object> {
#Override
public Object deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
ObjectCodec codec = jsonParser.getCodec();
JsonNode jsonNode = codec.readTree(jsonParser);
if (jsonNode.isInt()) {
return jsonNode.asLong();
}
return codec.treeToValue(jsonNode, Object.class);
}
}
And add it to the field of type Object:
public class SomeTOWithObjectField {
//... other fields
#JsonDeserialize(using = LongInsteadOfIntegerDeserializer.class)
private Object value;
//... other fields
}
And it deserialized integers as longs, but other types like String, boolean, double etc. were deserialized as they should be by default.
If you want to wrap a primitive into specific class, you can do follow (example in Kotlin):
data class Age(
#JsonValue
val value: Int
)
And now, your Int primitives will be parsed into Age class and vice versa - Age class into Int primitive.
In jackson 2 we can use TypeReference to specify the generic type in detail. There is and overloaded method for readValue() which takes the TypeReference as the 2nd parameter:
readValue([File|String|etc], com.fasterxml.jackson.core.type.TypeReference))
If you want to get a list of Long instead of Integer, you can do the following.
ObjectMapper mapper = new ObjectMapper();
TypeReference ref = new TypeReference<List<Integer>>() { };
List<Integer> list = mapper.readValue(<jsonString>, ref);
This works for maps as well:
TypeReference ref = new TypeReference<Map<String,Long>>() { };
Map<String, Long> map = mapper.readValue(<jsonString>, ref);
In your case, you can convert your class to a generic one. i.e Node<T>. When creating nodes, do as Node<String/Integer/etc> And use the type reference to read the value.
I am trying to deserialise a JSON-RPC object with Jackson. The format of JSON-RPC is :
{ "result": "something", "error": null, "id": 1}
In my case the result property is an generic Object.
I have a class for deserilising the response:
public class JsonRpcResponse {
private Object result;
private JsonRpcError error;
private int id;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public JsonRpcError getError() {
return error;
}
public void setError(JsonRpcError error) {
this.error = error;
}
public Object getResult() {
return result;
}
public void setResult(Object result) {
this.result = result;
}
}
I can get the response object with:
JsonRpcResponse jsonResp = mapper.readValue(response, JsonRpcResponse.class);
I want to have a generic method that deserializes this result object by passing to the method the type of the object (or the class if you want) that is going to be deserialized to. This way I can pass any type of object depending of the response I expect.
For example, I have this class with two properties:
public class JsonEventProperties {
private String conditon;
private String usage;
public JsonEventProperties(String condition, String usage) {
this.conditon = condition;
this.usage = usage;
}
public JsonEventProperties() {
throw new UnsupportedOperationException("Not yet implemented");
}
public String getConditon() {
return conditon;
}
public void setConditon(String conditon) {
this.conditon = conditon;
}
public String getUsage() {
return usage;
}
public void setUsage(String usage) {
this.usage = usage;
}
}
The result object inside the response for the above case will be:
"result": {"condition":"test","usage":"optional"}
I tried:
mapper.readValue(result,objectClass)
where result is a JsonNode intance of the result (Which for some reason is a LinkedHashMap) and objectClass the class I want it to deserialize to. But this is not working.
I busted my head all day with different ways of doing this but I probably do not understand who Jackson works.
Can anyone help me with this?
Thank you in advance.
I understand the original question to be asking about polymorphic deserialization of the "result" object.
Jackson now has a built-in mechanism available for this, using the #JsonTypeInfo and #JsonSubTypes annotations. (ObjectMapper has methods available as alternatives to using the annotations.) Further information is available in the official docs at http://wiki.fasterxml.com/JacksonPolymorphicDeserialization. Also, I posted a few use examples of this at http://programmerbruce.blogspot.com/2011/05/deserialize-json-with-jackson-into.html.
However, if you're stuck deserializing JSON that, in the target object, does not have an element that identifies the type by some name, then you're stuck with custom deserialization, where you'll have to determine based on some object content what the type should be. One of the last examples in the same blog posted I linked above demonstrates this approach, using the existence of particular JSON elements in the target object to determine the type.
Check out jsonrpc4j on github:
https://github.com/briandilley/jsonrpc4j
I had the same issue, this was my solution.
I added one more field to the object, so when building the object, i am setting the field value with class name, when deserializing it i am using
mapper.convertvalue(object, Class.forName(field value)
In your case
private Object result;
In the result object add one more field "className", while serializing the class set the value "className" with the name of the class you are treating as result object.
while deserializing the object
JsonRpcResponse jsonResp = mapper.readValue(response, JsonRpcResponse.class);
in jsonResp you will have Object result, String className, here the object is of type linkedhashmap
Now to convert to your object
objectmapper.convertValue(result, Class.forName(className))
The above code will get you the generic object which you want .
Hope this helps