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;
}
}
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 want to use Jackson to dynamically deserialize objects into the appropriate java classes, but I'm having trouble configuring Jackson the right way.
I have the following simplified model (getters/setters omitted for brevity):
class GeneralObject {
public String objType;
public String commonProp;
public GeneralObject nestedObject;
// map for additional properties, so that I can re-serialize the full object later
public Map<String, JsonNode> additionalFields = new HashMap<>();
#JsonAnyGetter
public Map<String, JsonNode> getAdditionalFields() {
return additionalFields;
}
#JsonAnySetter
public void addAdditionalField(String fieldName, JsonNode value) {
this.additionalFields.put(fieldName, value);
}
}
class SpecialObject extends GeneralObject {
public String specialProp;
}
In reality, there are different kinds of "special Objects", and I want to be
able to add more in the future, when the need comes.
The jsons look like this (I get them from external sources, I cannot change
the format in which they are sent):
{
"objType": "someType1",
"commonProp": "example1..."
}
{
"objType": "SPECIAL",
"commonProp": "example2...",
"specialProp": "more example"
}
{
"objType": "someOtherType",
"commonProp": "example3...",
"nestedObject": {
"objType": "SPECIAL",
"commonProp": "example2...",
"specialProp": "more example"
}
}
I am currently parsing them like this:
ObjectMapper mapper = new ObjectMapper();
String objString = "{\"objType\": \"SPECIAL\", \"commonProp\": \"...\", \"specialProp\": \"more example\"}";
GeneralObject genObj = mapper.readValue(objString, GeneralObject.class);
if (genObj.objType.equals("SPECIAL")) {
genObj = mapper.readValue(objString, SpecialObject.class);
}
// Some business-logic: If SPECIAL, then this cast is required to work:
System.out.println(((SpecialObject) genObj).specialProp);
This works for the top-level object, but not for the nested objects. If, for
example, the nested Object is a SPECIAL object, it will still be deserialized as
a common object.
What I want do do is to tell Jackson: "No matter the nesting-level, if objType=SPECIAL, use SpecialObject, else use GeneralObject". I looked into Polymorphic Deserialization and tried using #JsonSubTypes, but could not set up this logic correctly. How can I make sure that SPECIAL objects are deserialized into the appropriate class, even if they are nested?
I first took inspiration from this Gist and tried to solved it with a custom TypeIdResolver. Unfortunately, this had the problem of not deserializing the objType properly (see first version of this answer).
I then got inspiration from this answer and switched to a custom Deserializer:
class CustomDeserializer extends StdDeserializer<GeneralObject> {
private static final String SPECIAL = "\"SPECIAL\"";
protected CustomDeserializer() {
super(GeneralObject.class);
}
#Override
public GeneralObject deserialize(JsonParser p, DeserializationContext ctx) throws IOException {
TreeNode node = p.readValueAsTree();
// Select appropriate class based on "resourceType"
TreeNode objTypeNode = node.get("objType");
if (null == objTypeNode) {
throw new JsonParseException(p, "field \"objType\" is missing!");
}
if (!objTypeNode.isValueNode()) {
throw new JsonParseException(p, "field \"objType\" must be a String.");
}
String objType = objTypeNode.toString();
Class<? extends GeneralObject> clazz;
if (objType.equals(SPECIAL)) {
clazz = SpecialObject.class;
} else {
clazz = RecursionStopper.class;
}
return p.getCodec().treeToValue(node, clazz);
}
}
It checks the contents of .objType and emits the appropriate (sub)-class that should be used for deserialization. The deserializer needs to be registered on the GeneralObject, for example by using the following annotation:
#JsonDeserialize(using = CustomDeserializer.class)
class GeneralObject {
...
}
To stop an infinite recursion loop from occurring, all sub-classes must be annotated not to use this custom deserializer, and we need to introduce a helper class that stops the recursion for the GeneralObject:
#JsonDeserialize(using = JsonDeserializer.None.class)
class SpecialObject extends GeneralObject {
public String specialProp;
}
#JsonDeserialize(using = JsonDeserializer.None.class)
class RecursionStopper extends GeneralObject {
// this class intentionally empty
}
The deserialization works as intended, also for nested objects:
ObjectMapper mapper = new ObjectMapper();
String objString = "{\n" +
" \"objType\": \"someObjType\",\n" +
" \"commonProp\": \"example3...\",\n" +
" \"nestedObject\": {\n" +
" \"objType\": \"SPECIAL\",\n" +
" \"commonProp\": \"example2...\",\n" +
" \"specialProp\": \"more example\"\n" +
" }\n" +
"}";
GeneralObject genObj = mapper.readValue(objString, GeneralObject.class);
System.out.println(((SpecialObject) genObj.nestedObject).specialProp);
My json string looks like the following:
{
"text": ["foo",1,"bar","2",3],
"text1": "value1",
"ComplexObject": {
.....
}
}
I have a pojo defined like this:
class MyPojo {
List<String> text;
String text1;
ComplexObject complexObject;
}
I use google gson and am able to get my java object populated properly. The problem here is that the field text is an array of mixed types (string and int). So all the entries there are converted into String and i am not able to figure out which entries in the array is a string vs int. I cant use parseInt since the entries in the original array may have "2" as well as 3.
Is there a way for me to get the right instance type of the fields in my array after converting into java object.
SOLUTION
So i implemented the solution using gson the round about way using the JsonDeserializer. And then i tried using jackson. Guess what jackson supports serializing/deserializing the mixed array type by preserving the data types.
ObjectMapper mapper = new ObjectMapper();
MyPojo gmEntry = mapper.readValue(json, new TypeReference<MyPojo >(){});
And i can basically fetch the List<Object> and do an instanceof to check for the datatype.
Shame on you gson!!
By having a custom class and adding a type adapter u can manipulate the string (json.toString() returns with the '"' quotes, so you can see if its a string or not.
Output: (the classes seem correct)
class test.Main$StringPojo pojo{object=foo}
class test.Main$IntPojo pojo{object=1}
class test.Main$StringPojo pojo{object=bar}
class test.Main$StringPojo pojo{object=2}
class test.Main$IntPojo pojo{object=3}
public static void main(final String[] args){
String str = "{\n" +
" \"text\": [\"foo\",1,\"bar\",\"2\",3],\n" +
" \"text1\": \"value1\" }";
GsonBuilder builder = new GsonBuilder();
builder.registerTypeAdapter(pojo.class, new JsonDeserializer<pojo>() {
#Override
public pojo deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
try {
return new IntPojo(Integer.parseInt(json.toString()));
} catch (Exception e) {
return new StringPojo(json.getAsString());
}
}
});
MyPojo myPojo = builder.create().fromJson(str, MyPojo.class);
for (pojo pojo : myPojo.text) {
System.out.println(pojo.getClass() + " " + pojo.object);
}
}
public static abstract class pojo{
protected Object object;
public pojo() {
}
#Override
public String toString() {
return "pojo{" +
"object=" + object +
'}';
}
}
public static class StringPojo extends pojo{
public StringPojo(String str) {
object = str;
}
}
public static class IntPojo extends pojo{
public IntPojo(int intt) {
this.object = intt;
}
}
public static class MyPojo {
List<pojo> text;
String text1;
}
As you wrote - you defined: List<String> text; but that list also contains integers.
Java is strongly typed, please consider to either declare the List as List<Object> (less preferable) or creating a JSON list that contains only a single type of variable (more preferable).
You can create an abstract class ItemType (for use as array item type) and inherits from it two wrapper classes: one for int type and another for string type.
abstract class ItemType {
protected Object value;
}
class IntType extends ItemType {
IntType(Integer value){
this.value = value;
}
}
class StringType extends ItemType {
IntType(String value){
this.value = value;
}
}
Try this List<ItemType> text;
The above situation can be achived by using TypeAdapter of Gson API.
Please follow : https://sites.google.com/site/gson/gson-user-guide#TOC-Serializing-and-Deserializing-Collection-with-Objects-of-Arbitrary-Types
Not sure if this is what you need, but this is the code I use for parsing JSON.
static public void newsParser(String urlString, String targetObject) throws FileNotFoundException, IOException
{
URL url = new URL(urlString);
JSONParser parser=new JSONParser();
BufferedReader br = new BufferedReader(new InputStreamReader(url.openStream()));
Object obj;
try
{
obj = parser.parse(br);
//JSONObject jsonObject = (JSONObject) obj;
JSONArray jsonArray = (JSONArray) obj;
Iterator<?> i = jsonArray.iterator();
while (i.hasNext())
{
slide = (JSONObject) i.next();
newsInfo = (String)slide.get(targetObject);
System.out.println(newsInfo);
newsTitles.add(newsInfo);
}
}
catch (ParseException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
}
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 :)
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;
}
}
}
}