I'm trying to write a custom deserializer for Jackson and I want to make it generic (generic in the sense of working on any type, not as in "generics").
However I cannot seem to figure out how to get a handle to the type of the field being deserialized.
Eg, I'm looking to do something like the following:
#Override
public MyObject deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
Class c = <get type of current field>
// do something with that type
return new SubclassOfC(somedata based on c);
}
It's specifically the get type of current field part that I have struggled with.
Edit: It is the type of the java field I am interested in.
You don't -- deserializers are registered by type, so you need to construct deserializer to know what type it is expected to deserialize.
If you do want to registered a generic deserializer, you can however make things more dynamic by implementing ContextualDeserializer. Its createContextual() method is called with BeanProperty argument, and you can check things like name of the property (which may be null, in case of root values which are not referenced by a property) and type (which is the declared type).
This method can then return a new instance (do NOT modify original deserializer, since it is shared by all properties), configured with all extra information you need.
I have solved my particular problem by adding an implementation of Deserializers to the ObjectMapper. Eg
Deserializers d = new Deserializers.Base() {
#Override
public JsonDeserializer<?> findEnumDeserializer(Class<?> type, DeserializationConfig config, BeanDescription beanDesc, BeanProperty property)
throws JsonMappingException {
if (property.getType().getContentType() != null)
return new EnumDeserializer(property.getType().getContentType().getRawClass());
return new EnumDeserializer(property.getType().getRawClass());
}
};
mapper.setDeserializerProvider(mapper.getDeserializerProvider().withAdditionalDeserializers(d));
This will return my custom EnumDeserializer instantiated for each separate Enum type.
I solved it like this.
Get current field java type...
#Override
public Enum deserialize(JsonParser jsonparser, DeserializationContext context) throws IOException, JsonProcessingException {
System.out.println("EnumDeserializer ....");
Field field = findField(jsonparser.getCurrentName(), jsonparser.getCurrentValue().getClass());
Class<?> javaType = field.getType();
return null;
}
public Field findField(String name, Class<?> c) {
for (; c != null; c = c.getSuperclass()) {
for (Field field : c.getDeclaredFields()) {
if (Modifier.isStatic(field.getModifiers())) {
continue;
}
if (field.getName().equals(name)) {
return field;
}
}
}
return null;
}
Roughly speaking, and without exception catching and error checking...
JsonToken tok = jp.nextValue();
Field field = findField(jp.getCurrentName());
Class<?> fc = field.getType();
if(fc == int.class) {
field.setInt(this, jp.getIntValue());
} // handle all the primitive types and String in the same way, then...
} ... else if(tok == JsonToken.START_ARRAY) {
if(fc.isArray()) {
// Load into an array
} else if(Collection.class.isAssignableFrom(fc)) {
// Load into a collection
} else {
// throw
}
} else if(tok == JsonToken.START_OBJECT) {
// Recursively create that object from the JSON stream
}
... and loop until tok is END_OBJECT. To find a of the current class by name:
Field findField(String name) {
for(Class<?> c = getClass(); c != null; c = c.getSuperclass()) {
for(Field field : c.getDeclaredFields()) {
if(field.getName().equals(name)) {
return field;
}
}
}
}
Related
I have a class with the following attributes,
public AnalyticsEventProperty(String eventID, String key, Object value, EventPropertyValueType valueType) {
this.eventID = eventID;
this.key = key;
this.value = value;
this.type = valueType();
}
This object is created and passed to an array of event properties, when I do the Json Conversion I get the output below:
{"eventID":"afc970ef-80cf-4d6e-86e6-e8f3a56f26f5","name":"app_start","propertyArrayList":[{"eventID":"afc970ef-80cf-4d6e-86e6-e8f3a56f26f5","key":"session_id","value":"69200430-95a0-4e14-9a36-67942917573d"}
I am getting 'key and 'value' used, I can see why, but how do I use the key and values as key and values i.e. "session_id":"69200430-95a0-4e14-9a36-67942917573d", bearing in mind that these key and values may have different property names depending on what is passed in the constructor.
When i create the String i am simply calling
String text_to_send = new Gson().toJson(events);
Where events is the ArrayList.
You can solve this by writing a custom TypeAdapterFactory which obtains the default adapter for your class (that is the reflection based one) and uses it to create an in-memory JSON representation in the form of a JsonObject. That JsonObject can then be modified to have the structure you expect; afterwards it has to be written to the JsonWriter:
class RewritingEventPropertyAdapterFactory implements TypeAdapterFactory {
public static final RewritingEventPropertyAdapterFactory INSTANCE = new RewritingEventPropertyAdapterFactory();
private RewritingEventPropertyAdapterFactory() {}
#Override
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
// Only consider AnalyticsEventProperty or subtypes
if (!AnalyticsEventProperty.class.isAssignableFrom(type.getRawType())) {
return null;
}
TypeAdapter<T> delegate = gson.getDelegateAdapter(this, type);
TypeAdapter<JsonObject> jsonObjectAdapter = gson.getAdapter(JsonObject.class);
return new TypeAdapter<T>() {
#Override
public T read(JsonReader in) throws IOException {
throw new UnsupportedOperationException("Deserialization is not supported");
}
#Override
public void write(JsonWriter out, T value) throws IOException {
if (value == null) {
out.nullValue();
return;
}
JsonObject jsonObject = delegate.toJsonTree(value).getAsJsonObject();
// Remove "key" and "value"
String eventKey = jsonObject.remove("key").getAsString();
JsonElement eventValue = jsonObject.remove("value");
// Add back an entry in the form of `"key": "value"`
jsonObject.add(eventKey, eventValue);
// Write the transformed JsonObject
jsonObjectAdapter.write(out, jsonObject);
}
};
}
}
You then have to register the factory with a GsonBuilder.
An alternative would be to perform the complete serialization of the class manually by directly writing the properties to the JsonWriter. This will most likely be a bit more performant, but is also more error-prone.
I am trying to write a TypeAdapterFactory for the JavaFX Types that I am using (different properties and observables). For the ObjectProperty i could write a JsonSerializer/Deserializer making use of a cast to ParamterizedType and then getting the ActualTypeArguments.
This is the code I use to achieve this:
public class ObjectPropertySerializer implements JsonSerializer<ObjectProperty>, JsonDeserializer<ObjectProperty> {
#Override
public ObjectProperty deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
Type objectType = ((ParameterizedType)typeOfT).getActualTypeArguments()[0];
return new SimpleObjectProperty(new GsonBuilder().create().fromJson(json, objectType));
}
#Override
public JsonElement serialize(ObjectProperty src, Type typeOfSrc, JsonSerializationContext context) {
Type objectType = ((ParameterizedType)typeOfSrc).getActualTypeArguments()[0];
return new GsonBuilder().create().toJsonTree(src.getValue(), objectType);
}
}
However I also need to use a TypeAdapterFactory because of nested classes/object and direct serialisation, that do not work when registering the serialisers directly to a GsonBuilder using .registerTypeAdapter().
This is the code of my factory so far (the other properties already work):
public class JFXGsonAdapterFactory implements TypeAdapterFactory {
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
if ( BooleanProperty.class.isAssignableFrom(type.getRawType()) ) {
return (TypeAdapter<T>) new BooleanPropertyAdapter();
}
else if ( DoubleProperty.class.isAssignableFrom(type.getRawType()) ) {
return (TypeAdapter<T>) new DoublePropertyAdapter();
}
else if (IntegerProperty.class.isAssignableFrom(type.getRawType()) ) {
return (TypeAdapter<T>) new IntegerPropertyAdapter();
}
else if (ObjectProperty.class.isAssignableFrom(type.getRawType()) ) {
return (TypeAdapter<T>) new ObjectPropertyAdapter(gson);
}
else if ( StringProperty.class.isAssignableFrom(type.getRawType()) ) {
return (TypeAdapter<T>) new StringPropertyAdapter();
}
else {
return null;
}
}
}
I can't return an implementation of the serializer/deserializer interfaces here, but need to use an adapter class that extends TypeAdapter.
Here is my TypeAdapter implementation for ObjectProperty:
public class ObjectPropertyAdapter<T> extends TypeAdapter<ObjectProperty> {
private Gson gson;
TypeAdapter<JsonElement> elementAdapter;
public ObjectPropertyAdapter( Gson gson ) {
this.gson = gson;
elementAdapter = gson.getAdapter(JsonElement.class);
}
#Override
public void write(JsonWriter out, ObjectProperty value) throws IOException {
if ( value == null ) {
out.nullValue();
} else {
JsonElement jsonElement = gson.toJsonTree(value.get());
elementAdapter.write(out, jsonElement);
}
}
#Override
public ObjectProperty read(JsonReader in) throws IOException {
if (in.peek() == JsonToken.NULL) {
in.nextNull();
return null;
} else {
// Read
return null; // tmp
}
}
}
However I am not sure how to get the type of the generic for ObjectProperty in the TypeAdapter and I do not know how to get it from the TypeToken either (since I could pass it to the adapter from the factory).
Summary of the question:
1) How do I cast an implementation of JsonSerializer/Deserializer to TypeAdapter
or
2) How can I get the generic type for e.g. ObjectProperty from the TypeToken?
or
3) Do I need to do something completely different to achieve this?
1) You can't. JsonSerializer and JsonDeserializer are the old converter way. New applications should use TypeAdapter, which has a superset of funcionality.
2) Assuming you mean JavaFX's ObjectProperty<T> and you want to extract T's class.
You can access the Type inside TypeToken easily with TypeToken#getType().
I will give the example with List<String> which is the same idea but easily testable.
TypeToken<List<String>> typeToken = new TypeToken<List<String>>(){};
System.out.println("typeToken.getRawType() = " + typeToken.getRawType());
Type type = typeToken.getType();
System.out.println("typeToken.getType() = " + type);
if (type instanceof ParameterizedType) {
Type[] typeArguments = ((ParameterizedType) type).getActualTypeArguments();
System.out.println("typeArguments = " + Arrays.toString(typeArguments));
}
Output result:
typeToken.getRawType() = interface java.util.List
typeToken.getType() = java.util.List<java.lang.String>
typeArguments = [class java.lang.String]
3) I think you are nearly there. ;-) Ah, with the TypeAdapter you don't think in terms of JsonObject or JsonElement (DOM style) but you build the object or the JSON representation on the fly (stream style).
Also, in your question you say
However I also need to use a TypeAdapterFactory because of nested
classes/object and direct serialisation, that do not work when
registering the serialisers directly to a GsonBuilder using
.registerTypeAdapter().
Maybe you need to use GsonBuilder#registerTypeHierarchyAdapter() and look for those nested classes. It will depend on your problem details.
I am trying to create an #JsonDeserializer that will work across classes. I am using JAX-RS and the incoming json string will have fields in snake case. I want to override the json deserialization so that my java objects do not have snake-case fields. Since the creation of the java object is happening within JAX-RS, I am using the #JsonDeserializer annotation on all my request classes. My current implementation has a generic base class, but I need to extend it for all the concrete classes so that I can pass in the actual class I want to create. Is there any way to do this more generically?
For example, I have multiple request objects like this:
#JsonDeserialize(using = MyRequestDeserializer.class)
public class MyRequest {
....
}
I have created a generic deserializer like so:
public class GenericRequestDeserializer extends JsonDeserializer<Object> {
private static ObjectMapper objectMapper = new ObjectMapper();
#Override
public Object deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException {
return null;
}
protected Object deserializeIt(JsonParser jsonParser, Class cls) {
try {
JsonNode node = jsonParser.getCodec().readTree(jsonParser);
Iterator<String> fieldNames = node.fieldNames();
Object object = cls.newInstance();
while(fieldNames.hasNext()) {
String fieldName = fieldNames.next();
JsonNode value = node.get(fieldName);
String newFieldName = convertFieldName(fieldName);
//TODO: currently failing if I do not find a field, should the exception be swallowed?
Class returnType = object.getClass().getMethod("get" + newFieldName).getReturnType();
Method setMethod = object.getClass().getMethod("set" + newFieldName, returnType);
Object valueToSet = null;
if(value.isTextual()) {
valueToSet = value.asText();
} else if(value.isContainerNode()) {
valueToSet = objectMapper.readValue(value.toString(), returnType);
} else if (value.isInt()) {
valueToSet = value.asInt();
}
setMethod.invoke(object, valueToSet);
}
return object;
} catch (Exception e) {
throw new TokenizationException(GatewayConstants.STATUS_SYSTEM_ERROR,
"Error in deserializeIt for " + cls.getSimpleName() + " caused by " + e.getMessage(), e);
}
}
private String convertFieldName(String fieldName) {
StringBuilder newFieldName = new StringBuilder();
int length = fieldName.length();
boolean capitalize = true; //first character should be capitalized
for(int i = 0; i < length; i++) {
char current = fieldName.charAt(i);
if(current == '_') {
//remove the underscore and capitalize the next character in line
capitalize = true;
} else if(capitalize) {
newFieldName.append(Character.toUpperCase(current));
capitalize = false;
} else {
newFieldName.append(current);
}
}
return newFieldName.toString();
}
}
But I still need to create a new class per Request in order to pass in the proper class to create:
public class MyRequestDeserializer extends GenericRequestDeserializer {
#Override
public Object deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException {
return deserializeIt(jsonParser, MyRequest.class);
}
}
Is there any way to get rid of all the MyRequestDeserializer classes? In other words, can the GenericRequestDeserializer figure out what class it is actually deserializing?
So I found a much better option for changing all my objects to snake case. Instead of using Serializers and Deserializers on each class, I was able to inject an ObjectMapper into the JsonProvider in Spring. ObjectMapper already supports a property that will do the camel-case to snake-case automagically. I just needed to overwrite the getSingletons method in my class that extends Application like so:
public class MyApp extends Application {
....
#Override
public Set<Object> getSingletons() {
final Set<Object> objects = new LinkedHashSet<Object>();
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setPropertyNamingStrategy(
PropertyNamingStrategy.CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES);
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
objects.add(new JacksonJsonProvider(objectMapper));
return objects;
}
}
We need to process some broken JSON from a legacy server here that wrongly encodes null values as literal "null" strings in its output.
I already found that I probably want to override https://github.com/FasterXML/jackson-core/blob/master/src/main/java/com/fasterxml/jackson/core/base/ParserMinimalBase.java#L368 to "fix" this, but this seems to be so deep inside Jackson that I'd rather do it differently. Are there alternatives, for example by using the ObjectMapper to add a custom deserializer for the String.class or am I lost?
Ok, it worked by overriding the standard String deserializer. Unfortunately I had to copy the complete implementation over because org/codehaus/jackson/map/deser/std/StringDeserializer.java is final and cannot be extended.
public class FixesModule extends SimpleModule {
public FixesModule() {
super();
addDeserializer(String.class, new CustomStringDeserializer());
}
}
and
public class CustomStringDeserializer extends StdScalarDeserializer<String> {
private static final String NULL_STRING = "null";
public CustomStringDeserializer() {
super(String.class);
}
#Override
public String deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
JsonToken curr = jp.getCurrentToken();
// Usually should just get string value:
if (curr == JsonToken.VALUE_STRING) {
// BEGIN NULL_STRING fix
if (NULL_STRING.equals(jp.getText())) {
return null;
}
// END NULL_STRING fix
return jp.getText();
}
// [JACKSON-330]: need to gracefully handle byte[] data, as base64
if (curr == JsonToken.VALUE_EMBEDDED_OBJECT) {
Object ob = jp.getEmbeddedObject();
if (ob == null) {
return null;
}
if (ob instanceof byte[]) {
return Base64Variants.getDefaultVariant().encode((byte[]) ob, false);
}
// otherwise, try conversion using toString()...
return ob.toString();
}
// Can deserialize any scalar value, but not markers
if (curr.isScalarValue()) {
return jp.getText();
}
throw ctxt.mappingException(_valueClass, curr);
}
// 1.6: since we can never have type info ("natural type"; String, Boolean,
// Integer, Double):
// (is it an error to even call this version?)
#Override
public String deserializeWithType(JsonParser jp, DeserializationContext ctxt, TypeDeserializer typeDeserializer)
throws IOException, JsonProcessingException {
return deserialize(jp, ctxt);
}
}
Can I write a generic method to trim all strings within an complex object (object containing other objects)? Should java reflection api be used to achieve this?Thanks.
I have provided a sample below. However in reality there could be multiple objects within objects. Each object might contain a collection of String or collection of other objects which may contain String. Is there a way to trim the Strings - ones directly with the objects and ones within collection.
public class School{
private List<Course> courses;
private List<Student> students;
// Getters and Setters
}
public class Course{
private String name;
private String xxx;
private String yyy;
private List<String> zzzList;
}
public class Student{
private Map<String,String> xxx;
private List<Course> courseList;
}
Yes, reflection is the way. Basically, you need to:
get the class of the top level object (with [object].getClass())
get all the fields of the object (with clazz.getFields() - beware, it works only with public fields)
check if the field is String (either get field.getType() and check it's a string, or do a field.get(the object) and a instanceof String)
if it's the case, replace the string in the object with the trimmed one, using field.set([your object],[trimmed string])
if the field is an object but not a string, call your method recursively
That will do the trick.
---- just seen your update
Trimming strings in collection will be more tricky, since the strings are not exposed as public fields of the collection (List for example).
You will need something more clever, that will check if an object is an instance of List, or Map, or etc... (or a derived class!).
Main problem is also that java generics are done with erasing type at compile type. So you cannot know that your field is List[String] or List[Integer] or whatever. Every List[?] becomes List.
Still you can try to do it like that:
if field type is List
iterate through the list values
if a value is instanceof String, you have to remove it from the list and insert in place the trimmed version
if a value is an object, there you go again recursively with your method.
Not very interesting in real life samples, but more on a library side maybe.
Long way to go though!
Yes, you can do that with reflection, quite easily. Just check if the field is instanceof String.
The exact way to do it depends on your object structure.
/*********************************************************************************************
* Trim first level children of string type in this object
* #param obj which all string properties to be trimmed
*********************************************************************************************/
public static void trimAll(final Object obj)
throws LocalException
{
if (obj==null) return;
final Class c = obj.getClass();
final Method[] methods = c.getMethods();
final Class[] SETTER_ARGS = new Class[]{String.class};
final Object[] SETTER_VAL = new Object[1];
final String SET = "set";
final String GET = "get";
final String SPACE = "\u0020";
final String TAB = "\t";
for (final Method m:methods)
{
try
{
final String name=m.getName();
if (
name.length()>GET.length()
&& name.indexOf(GET)==0
&& m.getReturnType().equals(String.class)
&& m.getParameterTypes().length==0)
{
final String v = (String)m.invoke(obj);
if (v!=null && (v.contains(SPACE) || v.contains(TAB)) )
{
final Method setter=c.getMethod(SET+name.substring(3),SETTER_ARGS);
if (setter!=null)
{
SETTER_VAL[0]=v.trim();
setter.invoke(obj,SETTER_VAL);
}
}
}
}
catch (final Throwable e)
{
throw new LocalException(LocalException.EC_GENERAL_EXCEPTION,e);
}
}
}
We can also use Jackson to serialize and then deserialize the object. While deserializing we can use custom deserializer to trim all the String values.
Create a deserializer like this:
public class TrimStringToNullDeserializer extends JsonDeserializer<String> {
#Override
public String deserialize(JsonParser jsonParser, DeserializationContext deserializationContext)
throws IOException {
String value = jsonParser.getValueAsString();
if (isNull(value)) {
return null;
}
value = value.trim();
if (value.length() == 0) {
value = null;
}
return value;
}
And then we can use Jackson to trim all values:
public class TrimStringToNullConfiguration {
private ObjectMapper objectMapper;
public Client trimToNull(Client inputClient) throws JsonProcessingException {
return getObjectMapper().readValue(getObjectMapper().writeValueAsString(inputClient), Client.class);
}
private ObjectMapper getObjectMapper() {
if (isNull(objectMapper)) {
objectMapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.addDeserializer(String.class, new TrimStringToNullDeserializer());
objectMapper.registerModule(module);
}
return objectMapper;
}
I have placed a working example over here.
private <T> T toTrim(T t) {
Field[] fields = t.getClass().getFields();
for (Field field : fields) {
try {
if (field.get(t) instanceof String) {
Object o = field.get(t);
String s = (String) o;
field.set(t, s.trim().toUpperCase());
}
} catch (IllegalAccessException e) {
log.info("Error converting field "+ field.getName() );
}
}
return t;
}
if (yourObject instanceof String){
yourObject = yourObject.trim();
}
Hope it helps :)