FlexJSON Exclude Properties Upon Deserialization - java

I'm receiving a JSON response from a web service, but for various reasons I don't want to have certain properties deserialized in the final response object. For example I have:
public class Foo {
private String bar;
private int baz;
//getters & setters
}
The JSON response I'm getting back has both properties, but upon deserialization I don't want "bar" to be set. The reason for this is that the property they're sending is a long, but ours is a String, so deserializing throws an IllegalArgumentException.
Another option would be to parse the JSON with something like json-simple, remove the properties I want, convert it back to JSON and pass that into the deserializer, but I'd like to avoid that if possible since the JSON is pretty large.
Is there a way to do this with an ObjectFactory perhaps?

Yes an ObjectFactory could be used to allow a conversion from Long to String. Simply register the ObjectFactory on your path like:
new JSONDeserializer().use("some.path.to.bar", new EnhancedStringObjectFactory() ).deserialize( json, new SomeObject() );
public class EnhancedStringObjectFactory implements ObjectFactory {
public Object instantiate(ObjectBinder context, Object value, Type targetType, Class targetClass) {
if( value instanceof String ) {
return value;
} else if( value instanceof Number ) {
return ((Number)value).toString();
} else {
throw context.cannotConvertValueToTargetType(value, String.class);
}
}
}
You could even register that as the default ObjectFactory for String and it would handle that case for any String coming into the deserializer:
new JSONDeserializer().use( String.class, new EnhancedStringObjectFactory() ).deserialize( json, new SomeObject() );

Related

How to serialize an object as a single value in Gson?

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).

GSON TypeAdapter: DeSerialize Polymorphic Objects Based on "Type" Field

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);

Spring Framework JSON Serialization to Array rather than Object

In my web application that is using Spring, we want use a custom JSON structure. Spring by default takes a POJO like this:
public class Model {
private int id;
private String name;
public Model(){}
public Model(int id, String name){
this.id = id;
this.name = name;
}
}
and turns it into this:
{"id":1, "name":"Bob"}
With our application, we want to turn it into this instead:
[1, "Bob"]
I want to use Spring's default serialization logic that detects the Java type (int, String, Collection, etc.) and maps to the appropriate JSON type, but just change the wrapping object to an array rather than and object with fields.
This is the Serializer I have so far (which will be implemented in the model with #JsonSerialize(using = Serializer.class)), but would prefer not to rewrite all the logic Spring already has implemented.
public class Serializer extends JsonSerializer<Model> {
#Override
public void serialize(Model value, JsonGenerator jgen, SerializerProvider provider)
throws IOException, JsonProcessingException {
jgen.writeStartArray();
jgen.writeString(value.id);
.... other values ...
jgen.writeEndArray();
}
}
How can I hook into the pre-existing Serializer so that this new serializer will work with any POJO as the default one does (not just the Model class, but any similar or child class we need to serialize to an array)? This could have mixed properties and no specific naming convention for the properties.
I want to avoid writing a custom serializer for every different Model class (the ... other values ...) section.
Take a look at Apache BeanUtils library, in particular, pay attention to the BeanUtils.populate() method.
What that method does is to convert any given Object to a Map<String, Object>, based on JavaBeans conventions. In the keys you'd have the attribute names, while in the values you'd have every attribute's value. That method should be enough for standard cases. Read the documentation carefully, to check how to handle special cases.
Model model = ...; // get your model from some place
Map<String, Object> properties = new HashMap<>();
BeanUtils.populate(model, properties);
// Exception handling and special cases left as an excercise
The above recursively fills the properties map, meaning that if your Model has an attribute named otherModel whose type is OtherModel, then the properties map will have another map at the entry that matches the otherModel key, and so on for other nested POJOs.
Once you have the properties map, what you want to serialize as the elements of your array will be in its values. So, something like this should do the job:
public List<Object> toArray(Map<String, Object> properties) {
List<Object> result = new ArrayList<>();
for (Object obj : properties.values()) {
Object elem = null;
if (obj != null) {
Class<?> clz = obj.getClass();
if (Map.class.isAssignableFrom(clz)) {
elem = toArray((Map<String, Object>) obj); // recursion!
} else {
elem = obj;
}
}
result.add(elem); // this adds null values
// move 1 line up if you don't
// want to serialize nulls
}
return result;
}
Then, after invoking the toArray() method, you'd have a List<Object> ready to serialize using the standard Spring mechanisms. I even believe you won't need a specific serializer:
List<Object> array = toArray(properties);
return array; // return this array, i.e. from a Controller
Disclaimer:
Please use this as a guide and not as a final solution. I tried to be as careful as possible, but the code might have errors. I'm pretty sure it needs special handling for arrays and Iterables of POJOs. It's undoubtedly lacking exception handling. It works only for POJOs. It might explode if the supplied object has circular references. It's not tested!
You could use #JsonValue annotation for this.
Example:
public class Model {
private int id;
public Model(){}
public Model(int id){
this.id = id;
}
#JsonValue
public int[] getValue() {
return new int[]{this.id};
}
}

Jackson: how to prevent field serialization (while keeping deserialization)

I'd like to go further on what this question was about, I've been roaming SO for a solid hour now without finding anything.
Basically, what I'm trying to do is having a property properly instanciated through Jackson internal reflection algorithm during deserialization but having this same property not serialized when it comes to serialization.
I know about #JsonIgnore and #JsonIgnoreProperties but apparently I can't seem to use them right : either my property is correctly deserialized when I feed Jackson a proper map of properties but it also appears in the serialized results, either (when using #JsonIgnore) it is not serialized (which is wanted) but also not deserialized (not wanted).
Example :
public class Foo {
/* This is the property I want to be instanciated by Jackson upon deserialization
* but not serialized upon serialization
*/
private final Object bar = null;
public Object getBar() {
return bar;
}
}
To make things worse, as you can see, the property is final (this is why I'm keen on using Jackson reflection ability upon Foo instanciation through deserialization). I've read on potential solution about annotating the setter and the getter differently but I'd like to keep this property final if possible. If not possible, I'd settle for a non-final property.
I would appreciate answers not suggesting custom serializer/deserializer, my code base is currently free of such and if the solution could be of minimal impact, that would be perfect. Again, I'm no Jackson expert so if what I'm asking is not possible I'll obviously accept alternative answers.
I've also read this thread on github but none of the suggested ways of implementation have actually been implemented at the moment.
Thanks
EDIT : to make things clearer
public class Foo {
private final String bar = null;
public String getBar() {
return bar;
}
#Override
public String toString() {
return bar;
}
}
public void testMethod() throws IOException {
String json = "{\"bar\":\"Value\"}";
ObjectMapper mapper = new ObjectMapper();
Foo foo = mapper.readValue(json, Foo.class);
System.out.println(foo); // should have a bar property set to "Value"
System.out.println(mapper.writeValueAsString(foo)); // should return an empty JSON object
}
I am not sure whether it is elegant solution but you can use MixIn feature. You have to create new interface which could look like below:
interface FooMixIn {
#JsonIgnore
String getBar();
}
Assume that your POJO looks like this:
class Foo {
private final String bar = null;
public String getBar() {
return bar;
}
#Override
public String toString() {
return bar;
}
}
Now you have to tell Jackson that you want to ignore this property:
String json = "{\"bar\":\"Value\"}";
System.out.println(json);
ObjectMapper deserializeMapper = new ObjectMapper();
deserializeMapper.addMixInAnnotations(Foo.class, FooMixIn.class);
System.out.println(deserializeMapper.readValue(json, Foo.class));
Above example prints:
{"bar":"Value"}
null
Without deserializeMapper.addMixInAnnotations(Foo.class, FooMixIn.class); line above program prints:
{"bar":"Value"}
Value
EDIT 1
If you want to achieve result like you showed you have to create two ObjectMappers and customize them. See below example:
String json = "{\"bar\":\"Value\"}";
ObjectMapper deserializerMapper = new ObjectMapper();
Foo foo = deserializerMapper.readValue(json, Foo.class);
System.out.println("Foo object: " + foo);
ObjectMapper serializerMapper = new ObjectMapper();
serializerMapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
serializerMapper.addMixInAnnotations(Foo.class, FooMixIn.class);
System.out.println("JSON: " + serializerMapper.writeValueAsString(foo));
For serialization you have to use one instance and for deserialization you have to use another instance.
Starting with Jackson 2.6, a property can be marked as read- or write-only. It's simpler than hacking the annotations on both accessors (for non-final fields) and keeps all the information in one place. It's important to note that a final field is considered writable by default by Jackson.
However, it's not enough for a final field to allow deserialization, because you can't have a setter on that field: it needs to be set via the constructor, either directly or using a builder or another type that can be deserialized by Jackson. When using the constructor with the properties as parameters, you need to specify which parameter corresponds to which property, using #JsonProperty:
public class Foo {
#JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
private final String bar;
public Foo(#JsonProperty("bar") String bar) {
this.bar = bar;
}
public String getBar() {
return prop;
}
}

GSON deserialization : how to know the objects?

I try to use gson library to deserialize a flow of objects sent to me.
In all examples i've seen, when the method fromJson is called, we already know what type of object we expect to have.
In my case, I receive a flow of different objects and i'd like to know the best way to know the classes of objects before deserialize them.
{ A : {...}, B : { B1 : {...}, B2 : {...} }, C : {...} }
In this example, I'd like to have a way to know that 3 objects have been sent to me : A.class, B.class and C.class
Thanks
The documentation contains examples of deserializations using arbitrary classes or in two passes (first general deserialization in a collection, then content deserialization).
This exemple looks exactly like what you need. You could adapt it to use
JsonObject obj = parser.parse(json).getAsJsonObject();
to get a JsonObject instead of an array so that you can iterate on all properties (using entrySet) and deserialize according to the names (a = gson.fromJson(myjsonelement, A.class);) by simply mapping names to classes.
Yeah i too stumbled upon this issue. There is no way gson can figure out actual class of a field value. It simply tries to instantiate class used to define the field. Needless to say it is often not what we want.
so if you had, say
class C {
private A a;
private A c;
}
class B extends A {
}
then at runtime you
C c;
c.a = new B();
c.c = new B();
after deserialisation what you get is
c.a.getClass()==A.class;
c.b.getClass()==A.class;
so you would have to specify the subclass explicitly. Here is a wrapper class that is gson friendly.
public class S<T> {
private String objectClass;
private String rawObjectRepresentation;
// Gson needs no args constructor
public S() {
}
public S(T obj) {
objectClass = obj.getClass().getName();
rawObjectRepresentation = getGson().toJson(obj);
}
#SuppressWarnings("unchecked")
public T extract() throws ClassNotFoundException {
final Class<?> clazz = Class.forName(objectClass);
return (T)getGson().fromJson(rawObjectRepresentation, clazz);
}
private Gson getGson() {
return new GsonBuilder().create();
}
#Override
public String toString() {
return "type:"+objectClass;
}
}
If there is a field on the json object that you can use to identify the subclass you need to use, then you can use Gson on Fire: https://github.com/julman99/gson-fire
It has a feature called Type Selector that does exactly what you need.
Imagine a Base class and two child classes, A and B, then the code would look like this:
GsonFireBuilder builder = new GsonFireBuilder()
.registerTypeSelector(Base.class, new TypeSelector<Base>() {
#Override
public Class<? extends Base> getClassForElement(JsonElement readElement) {
String kind = readElement.getAsJsonObject().get("kind").getAsString();
if(kind.equals("a")){
return A.class; //This will cause Gson to deserialize the json mapping to A
} else if(kind.equals("b")) {
return B.class; //This will cause Gson to deserialize the json mapping to B
} else {
return null; //returning null will trigger Gson's default behavior
}
}
});
Gson gson = builder.createGson();

Categories