I have a ServerEndpoint that will be recieving different JSON formats. Since we are only allowed one message handler per message type, my one decoder will have to convert the message to the corresponding Java objects.
In my decoder, I am trying to convert the message recieved to either SubClassA or SubClassB (which implemets the same interface) using the ObjectMapper class. The ObjectMapper class has a readValue method that requires the type I am trying to map the json to and will throw an exception when it cannot convert to the specified type.
I am currently decoding it like the following, but it is not very elegant.
#Override
public Message decode(String message) throws DecodeException {
ObjectMapper mapper = new ObjectMapper();
try {
SubclassA obj = mapper.readValue(message, SubclassA .class);
return obj;
} catch (IOException e) {
}
try {
SubclassB obj = mapper.readValue(message, SubclassB .class);
return obj;
} catch (IOException e) {
}
throw new DecodeException(message, "Failed to decode message.");
}
What is the best way to decode the JSON string into the corresponding Java object using ObjectMapper?
Use Jackson, you can declare subtypes for the parent object. Your Json will contain #type with with the name of the subtype, look at this post
Deserialize JSON with Jackson into Polymorphic Types - A Complete Example is giving me a compile error
Jackson is one of the best options to work with JSON in Java.
For polymorphic deserialization, consider the following:
Using the #JsonTypeInfo annotation.
For situations where the #JsonTypeInfo annotation is not suitable, consider using a StdDeserializer. This answer will help you.
Related
I have a controller with #RequestBody
#PostMapping("/")
public String doSomething(#RequestBody ??? foo)
foo can be 2 different objects. Foobject or Barobject.
I don't know what will the object sent.
These objects are totally different without any kind of common fields where I could use
#JsonTypeInfo and #JsonSubTypes
I can workaround this and use ObjectMapper to try to map it to on of the 2 classes:
#RequestBody Object json
...
ObjectMapper objectMapper = new ObjectMapper();
try {
Foobject obj = objectMapper.convertValue(json, Foobject.class);
}
catch (IllegalArgumentException ei){
Barobject obj = objectMapper.convertValue(json, Barobject.class);
}
Is there a proper way to do this? Or better said, alternatives?
I know this goes against REST API development, because this should be another method, each with its own object.
You can choose to use, Object, class like this and then take advantage of the function
mapper.readValue(jsonStr, Fooobject.class);
method throws JsonMappingException
#PostMapping("/")
public String doSomething(#RequestBody Object foo) {...}
boolean isA(String json, Class expected) {
try {
ObjectMapper mapper = new ObjectMapper();
mapper.readValue(json, expected);
return true;
} catch (JsonMappingException e) {
e.printStackTrace();
return false;
}
}
Well you said that "I don't know what will the object sent", so you can only do trial and error.
Else modify your request to involve a "Type" or something (I would do this by Making a parent class with only "type" member of ObjectType type, and child classes extending that parent class.
enum ObjectType {FOO, BAR}
Method 1: Use readValue() function of jackson
Method 2: Do a string search (just to make it faster) something unique non-nullable field about any of the object.
Method 3: Modify the request.
Managed to do it with Jackson.
Create parent class, and both objects extend from parent.
On the parent added:
#JsonTypeInfo(use = JsonTypeInfo.Id.DEDUCTION)
#JsonSubTypes({
#JsonSubTypes.Type(Foobject.class),
#JsonSubTypes.Type(Barobject.class),,
})
This works pretty well, and I guess is a clean solution.
Also, has the advantage to be done on a Spring layer, where other validations defined on fields of the classes will occur.
That wasn't the case with other solutions.
I am not a Java programmer but keen to know how to achieve a feature which would ignore one or more invalid fields while de-serializing. In my case, the byte[] is send by a legacy system and hence no control over data correction (on encoding side or to send default in case of missing). This means, I need to anticipate invalid data/missing data within the byte[].
Following code serialize payloads but throws exception upon receiving invalid (wrong encoding, null etc).
public class LDeserializer<T> implements Deserializer<T> {
private final ObjectMapper objectMapper;
private final Class<T> myType;
public LDeserializer(final ObjectMapper objectMapper, final Class<T> myType) {
this.objectMapper = objectMapper;
this.myType = myType;
}
public LDeserializer(final Class<T> myType) {
this(new ObjectMapper(), myType);
}
#Override
public T deserialize(final String sometext, final byte[] bytes) {
if (bytes == null) {
return null;
}
try {
return objectMapper.readValue(bytes, myType);
} catch (final IOException e) {
throw new SerializationException(e);
}
}
}
Question: In my case, I can safely ignore invalid values while serialization. However, I am not sure how to instruct my serializer to ignore invalid fields (irrespective of its type). So that the serializer would emit object even thou the constructed object is not complete.
Note: ObjectMapper is the type I am using here to support serialization. However, I am free to use any helper types that can be used instead ObjectMapper. The only concern here is how to ignore invalid fields (irrespective of its type).
Please advise
I would like to deserialize a json string to a java object. I wanted to write a generic method.
public class ObjectSerializer<T> {
T t;
private ObjectMapper mapper = new ObjectMapper();
/* public Person deSerial(String json) throws Exception {
TypeReference<Person> typeRef = new TypeReference<Person>() {};
return mapper.readValue(json, typeRef);
} */
public T deSerialize(String jsonInput) throws Exception {
TypeReference<T> typeRef
= new TypeReference<T>() {};
return mapper.readValue(jsonInput, typeRef);
}
}
When I call deSerialize(validPersonJsonString) [validPersonJsonString : valid person JSON String], it is not working, it it throwing me the error:
java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to com.example.Person.
Whereas, when in call the commented deSerial method, it works fine. Please explain the issue.
Thanks.
Jackson doesn't support TypeReference with generic type parameters because of type erasure. Here's the bug about it.
As far as your use case is concerned, you don't need to use ObjectSerializer class at all. You can directly expose ObjectMapper object to your service layer and perform the deserialization.
If you want to shield the json serializing mechanism from your service layer then you can wrap ObjectMapper implementation into another class (like you have done in your code) but then, accept the class type as a method argument rather than a generic type.
I have an interface -
#JsonTypeInfo(use=JsonTypeInfo.Id.NAME, include=JsonTypeInfo.As.PROPERTY, property="type")
interface Base { ... }
I have two derived classes - ClassA and ClassB. I am attempting to serialize and deserialize with Jackson ION to base type as follows -
class TestSerDeSer {
private static ObjectMapper MAPPER = new IonObjectMapper();
static {
MAPPER.registerSubtypes(new NamedType(A.class, "A"));
MAPPER.registerSubtypes(new NamedType(B.class, "B"));
}
public byte[] serialize(Base baseType) {
try {
return MAPPER.writeValueAsBytes(baseType);
} catch (JsonProcessingException e) {
throw new RuntimeException(e.getMessage(), e);
}
}
public Base deserialize(byte[] bytes) {
Base base;
try {
base = MAPPER.readValue(bytes, Base.class);
return base;
} catch (IOException e) {
throw new RuntimeException(e.getMessage(), e);
}
}
}
I am creating an Object of Class A and serializing and desrializing using above functions as
Base baseObj = new ClassA(...);
//serialization works fine
byte[] serializedBytes = serialize(baseObj);
//this line throws exception
Base deserializedBase = deserialize(serializedBytes);
The exception is -
Caused by: com.fasterxml.jackson.databind.exc.InvalidTypeIdException: Missing type id when trying to resolve subtype of [simple type, class mypackage.path.Base]: missing type id property 'type'
I am registering subtypes in ObjectMapper. I also have the annotation for type in base interface. What is it that I am missing here?
Your example work with standard JSON with ObjectMapper however it fails when serialization format is switched to Ion with IonObjectMapper. Tested your example with com.fasterxml.jackson.dataformat:jackson-dataformat-ion:2.9.7, fails with the same exception.
There is an open issue [avro] Support #JsonSubTypes in schema generation and serialization #11 which implies that not all binary dataformats support subtypes. Open pull request [Ion] Better support for Ion type annotations. #109 implies that #JsonTypeInfo doesn't work when using Ion yet.
I am trying to deserialize a json object into a java bean.
The main issue I am facing is that I'd like to treat the field object of the json string as a plain string, even if it contains a potentially correct json object.
The json structure is like this:
{
"type":"user",
"object":{
"id":"1",
...}
}
How can i tell gson to ignore the object value so that it doesn't get deserialized into an object? I'd like it only to be mapped to a plain String field in my bean so that I can dispose a proper deserialization for it, once I got the type from the type field.
Just declare it as of type JsonObject
class ExampleJsonModel {
#SerializedName("type")
public String type;
#SerializedName("object")
public JsonObject object;
}
I don't know if your problem is solved. I ran into a similar question and here it is how I worked it out:
JsonDeserializer allows you to make you own adapter to deserialize that **:
class JavaBeanDeserializer implements JsonDeserializer<JavaBeanObject>() {
public JavaBeanObject fromJson(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
// return JavaBeanObject built using your logic.
}
You've to register JavaBeanDeserializer to Gson object when building it:
Gson gson = new GsonBuilder().registerTypeAdapter(JavaBeanObject.class, new JavaBeanDeserializer()).create();