Dynamically create an object based on a field - java

I receive from a network API a JSON like this one:
{
...
"foobar":
{
"type": "...",
"keyTypeA": "value"
}
}
With foobar having different object types depending on its type field.
So, foobar can be:
{
"type": "typeA",
"keyA1": "...",
"keyA2": "...",
...
}
or
{
"type": "typeB",
"keyB1": "...",
"keyB2": "...",
...
}
etc.
How can I parse these JSON models into my POJO classes defined here:
public class FoobarTypeBase {
#SerializedName("type")
public String type;
}
public class FoobarTypeA extends FoobarTypeBase {
#SerializedName("keyA1")
public SomeObject keyA1;
#SerializedName("keyA2")
public SomeObject keyA2;
}
public class FoobarTypeB extends FoobarTypeBase {
#SerializedName("keyB1")
public SomeObject keyB1;
#SerializedName("keyB2")
public SomeObject keyB2;
}
I guess I have to deal with TypeAdapterFactory and TypeAdapter but I don't known how to do it efficiently.

I usually use a combination of Retrofit + Gson and do it this way:
RuntimeTypeAdapterFactory<FoobarTypeBase> itemFactory = RuntimeTypeAdapterFactory
.of(FoobarTypeBase.class, "type") // The field that defines the type
.registerSubtype(FoobarTypeA.class, "foobar")
.registerSubtype(FoobarTypeB.class) // if the flag equals the class name, you can skip the second parameter.
Gson gson = new GsonBuilder()
.registerTypeAdapterFactory(itemFactory)
.create();
Then I initialize Retrofit like this:
Retrofit.Builder builder = new Retrofit.Builder();
builder.baseUrl(BASE_URL);
builder.addConverterFactory(GsonConverterFactory.create(gson));
Retrofit retrofit = builder.build();

Related

Parse a nested dynamic module using Gson

I have a JSON that looks more or less like this:
{
"modules": {
"someExistingModule": {
"name": "pug",
...
},
"randomExistingModule": {
"type": "cat",
...
},
"myNewModule": { // <----- I care about this module. Note that this is NOT an array
"modules": {
"img1": {
"type": "image",
"url": "https://some/random/image,
"altText": "Some image description
},
"img2": {
"type": "image",
"url": "https://some/random/image,
"altText": "Some image description
},
"img3": {
"type": "image",
"url": "https://some/random/image,
"altText": "Some image description
},
"txt1": { // <------ Updated JSON
"type": "text",
"content": "Hello world 1"
},
"txt2": {
"type": "text",
"content": "Hello world 2"
},
...
}
}
Inside myModule there can be N number of imgN objects and txtN. I need to parse this dynamically.
My current Response class looks like this:
public class MyModuleResponse extends SomeResponseClass
{
#Override
public void parse(InputStream data)
{
T responseBody = readJsonStream(data, MyModuleResponseBody.class());
MyModuleDataParser.parse(responseBody);
}
MyModuleDataParser.java
...
public static MyModuleDataParser parse(#Nullable MyModuleResponseBody body)
{
parseSomeExistingModule();
parseRandomExistingModule();
parseMyNewModule(); // <--- This is the new module I'm trying to parse. Currently, this method is empty.
}
MyModuleResponseBody.java
public class MyModuleResponseBody
{
public Modules modules;
public static class Modules
{
SomeExistingModule someExistingModule;
RandomExistingModule randomExistingModule;
MyNewModule myNewModule; // <----- My new module
}
public static class SomeExistingModule
{
String name;
...
}
public static class RandomExistingModule
{
String type;
...
}
public static class MyNewModule
{
public ??? modules; // <--- Trying to define the Type here. Something like List<MyImageModule>. But, it won't work
}
MyImageModule.java
public class MyImageModule extends Module // <---- Update: This class now extends a generic Module class
{
private String url;
private String altText;
}
MyTextModule.java <---- New Module
public class MyTextModule extends Module // New class
{
private String content;
}
Module.java
public class Module // <----- New parent class
{
protected String type;
}
How do I create a list of MyImageModule from myNewModule? I believe I need to use some kind of TypeAdapter from Gson library. But, I'm not familiar how to do this inside an existing response.
Use Map<String, MyImageModule>, in fact, a hashmap to solve the issue of non-list modules object in the json.
public static class MyNewModule {
public Map<String, MyImageModule> modules; // initialize as a hashmap
}

Using jackson deserialising a property which can be List of object or the object

My lib is calling an API which can return either of the following JSON structure -
{
"key_is_same" : {
"inner" : "val"
}
}
-or-
{
"key_is_same" : [{
"inner" : "val1"
},
{
"inner" : "val2"
}
]
}
Is there any annotation in jakson which can handle this and deserializ it into respective type
Looks like you are looking for the ACCEPT_SINGLE_VALUE_AS_ARRAY deserialization feature.
Feature that determines whether it is acceptable to coerce non-array (in JSON) values to work with Java collection (arrays, java.util.Collection) types. If enabled, collection deserializers will try to handle non-array values as if they had "implicit" surrounding JSON array. This feature is meant to be used for compatibility/interoperability reasons, to work with packages (such as XML-to-JSON converters) that leave out JSON array in cases where there is just a single element in array.
Feature is disabled by default.
It could be enabled either in ObjectMapper:
ObjectMapper mapper = new ObjectMapper();
mapper.enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY);
Or via the #JsonFormat annotation:
#JsonFormat(with = Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY)
private List<Foo> oneOrMany;
For illustration purposes, consider the following JSON documents:
{
"oneOrMany": [
{
"value": "one"
},
{
"value": "two"
}
]
}
{
"oneOrMany": {
"value": "one"
}
}
It could be the deserialized to the following classes:
#Data
public class Foo {
private List<Bar> oneOrMany;
}
#Data
public class Bar {
private String value;
}
Just ensure the feature is enabled in your ObjectMapper or your field is annotated with #JsonFormat(with = Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY).
And in case you are looking for the equivalent feature for serialization, refer to WRITE_SINGLE_ELEM_ARRAYS_UNWRAPPED.
I would recommend using Object as your data type for the property which is dynamic. So Here is my sample.
import java.util.Arrays;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
public class MainObject {
private Object key_is_same;
public Object getKey_is_same() {
return key_is_same;
}
public void setKey_is_same(Object key) {
this.key_is_same = key;
}
public static class KeyObject {
private String inner;
public String getInner() {
return inner;
}
public void setInner(String inner) {
this.inner = inner;
}
}
public static void main(String...s) throws JsonProcessingException {
MainObject main = new MainObject();
KeyObject k = new KeyObject();
k.setInner("val1");
main.setKey_is_same(k);
ObjectMapper om = new ObjectMapper();
System.out.println(om.writeValueAsString(main));
main.setKey_is_same(Arrays.asList(k, k));
System.out.println(om.writeValueAsString(main));
public static void main(String...s) throws IOException {
MainObject main = new MainObject();
KeyObject k = new KeyObject();
k.setInner("val1");
main.setKey_is_same(k);
ObjectMapper om = new ObjectMapper();
System.out.println(om.writeValueAsString(main));
main.setKey_is_same(Arrays.asList(k, k));
System.out.println(om.writeValueAsString(main));
// Deserialize
MainObject mainWithObject = om.readValue("{\"key_is_same\":{\"inner\":\"val1\"}}", MainObject.class);
MainObject mainWithList = om.readValue("{\"key_is_same\":[{\"inner\":\"val1\"},{\"inner\":\"val1\"}]}", MainObject.class);
if(mainWithList.getKey_is_same() instanceof java.util.List) {
((java.util.List) mainWithList.getKey_is_same()).forEach(System.out::println);
}
}
}
}
Output
{"key_is_same":{"inner":"val1"}}
{"key_is_same":[{"inner":"val1"},{"inner":"val1"}]}

How to have a single GSON custom serializer to apply to all subclasses?

I'm using GSON to apply a universal serializer to all subclasses of an abstract Base class. However, GSON will not call my serializer when given actual subclasses of the Base class unless explicitly told to use Base.class as a cast. Here's a simple instance of what I'm talking about.
public interface Base<T>{
String getName();
public List<Object> getChildren();
}
public class Derived1 implements Base<Integer>{
private Integer x = 5;
String getName(){
return "Name: " + x;
}
List<Object> getChildren(){
return Lists.newArrayList(new Derived2(), "Some string");
}
}
public class Derived2 implements Base<Double>{
private Double x = 6.3;
String getName(){
return "Name: " + x;
}
List<Object> getChildren(){
return new List<>();
}
}
I'm creating a serializer as follows:
JsonSerializer customAdapter = new JsonSerializer<Base>(){
#Override
JsonElement serialize(Base base, Type sourceType, JsonSerializationContext context){
JsonObject jsonObject = new JsonObject();
jsonObject.addProperty("name", base.getName());
JsonArray jsonArray = new JsonArray();
for (Object child : base.getChildren()){
jsonArray.add(context.serialize(child));
}
if (jsonArray.size() != 0){
jsonObject.add("children", jsonArray);
}
}
};
Gson customSerializer = new GsonBuilder()
.registerTypeAdapter(Base.class, customAdapter)
.create();
However, applying my custom serializer to a List of subclasses does not have the desired effect.
customSerializer.toJson(Lists.newArrayList(new Derived1(), new Derived2()));
This applies the default GSON serialization to my subclasses. Is there any easy way to get my custom serializer to use my custom adapter on all subclasses of the parent class? I suspect that one solution is to use reflection to iterate over all subclasses of Base and register the custom adapter, but I'd like to avoid something like that if possible.
Note: I don't care about deserialization right now.
Maybe you should not use JsonSerializer. Namely, this is possible if you use TypeAdapter doing the same magic by registering TypeAdapterFactory that tells Gson how to serialize any class.
See below TypeAdapterFactory and TypeAdapter in it:
public class CustomAdapterFactory implements TypeAdapterFactory {
#SuppressWarnings("unchecked")
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
// If the class that type token represents is a subclass of Base
// then return your special adapter
if(Base.class.isAssignableFrom(typeToken.getRawType())) {
return (TypeAdapter<T>) customTypeAdapter;
}
return null;
}
private TypeAdapter<Base<?>> customTypeAdapter = new TypeAdapter<Base<?>>() {
#Override
public void write(JsonWriter out, Base<?> value) throws IOException {
out.beginObject();
out.value(value.getName());
out.endObject();
}
#Override
public Base<?> read(JsonReader in) throws IOException {
// Deserializing to subclasses not interesting yet.
// Actually it is impossible if the JSON does not contain
// information about the subclass to which to deserialize
return null;
}
};
}
If you do something like this:
#Slf4j
public class SubClassTest {
#Test
public void testIt() {
Gson gson = new GsonBuilder()
.setPrettyPrinting()
.registerTypeAdapterFactory(new CustomAdapterFactory())
.create();
log.info("\n{}", gson.toJson(new Derived1()));
log.info("\n{}", gson.toJson(new Derived2()));
}
}
the output will be like this:
2018-10-12 23:13:17.037 INFO
org.example.gson.subclass.SubClassTest:19 - { "name": "Name: 5" }
2018-10-12 23:13:17.043 INFO
org.example.gson.subclass.SubClassTest:20 - { "name": "Name: 6.3"
}
If it is not exactly what you want just fix the write(..) method in the customTypeAdapter.

Java jackson how to unwrap a raw value during serialization?

Using the Jackson json library in Java, is it possible to do the following:
public class MyObject {
#JsonRawValue
#JsonUnwrapped
private String rawJson;
}
public class DTO {
public MyObject json;
}
Example of rawJson:
{
"key": "value"
}
When DTO is serialized and rendered as json, I want the JSON to simply look like:
{
"dto": {
"key":"value"
}
}
Instead it's always:
{
"dto":{
"rawJson":{
"key":"value"
}
}
}
Basically it seems that when one of your properties is a JSON value stored in a string with #JsonRawValue, #JsonUnwrapped doesn't work.
Ideally I'm looking for a serialization solution at the level of the MyObject class rather than DTO, or else I'd have to apply the solution to not only DTO but every other DTO where MyObject is referenced.
Update:
The below answer solved this problem in one location, but still is an issue in the following scenario;
public class DTO2 {
#JsonUnwrapped
public MyObjectAbstract json2;
}
Where MyObject is an extension of MyObjectAbstract, and when DTO2 MyObjectAbstract is an instance of MyObject I'd like DTO2 to simply be serialized as:
{
"key": "value"
}
The issue is that whenever DTO2.MyObjectAbstract is an instance of MyObject it's serialized like:
{
"json": {
"key": "value"
}
}
You can use JsonRawValue and JsonValueannotations on a getter method instead. Here is an example:
public class JacksonUnwrapped {
static class MyObject {
private String rawJson;
MyObject(final String rawJson) {
this.rawJson = rawJson;
}
#JsonRawValue
#JsonValue
String getRawJson() {
return rawJson;
}
}
static class DTO {
public MyObject json;
DTO(final MyObject json) {
this.json = json;
}
}
public static void main(String[] args) throws JsonProcessingException {
final ObjectMapper objectMapper = new ObjectMapper();
final DTO dto = new DTO(new MyObject(
"{\"key\": \"value\"}"));
System.out.println(objectMapper
.writerWithDefaultPrettyPrinter()
.writeValueAsString(dto));
}
}
Output:
{
"json" : {"key": "value"}
}

Can not deserialize JSON with Jackson lib

I have a JSON:
{
"firstField": "Something One",
"secondField": "Something Two",
"thirdField": [
{
"thirdField_one": "Something Four",
"thirdField_two": "Something Five"
},
{
"thirdField_one": "Something Six",
"thirdField_two": "Something Seven"
}
],
"fifthField": [
{
"fifthField_one": "Something… ",
"fifthField_two": "Something...",
"fifthField_three": 12345
},
{
"fifthField_one": "Something",
"fifthField_two": "Something",
"fifthField_three": 12345
}
]
}
I have my classes:
public static class MyClass {
#JsonProperty
private String firstField, secondField;
#JsonProperty
private ThirdField thirdField;
#JsonProperty
private FifthField fifthField;
public static class ThirdField {
private List<ThirdFieldItem> thirdField;
}
public static class ThirdFieldItem {
private String thirdField_one, thirdField_two;
}
public static class FifthField {
private List<FifthFieldItem> fifthField;
}
public static class FifthFieldItem {
private String fifthField_one, fifthField_two;
private int fifthField_three;
}
}
I'm deserializing them with Jackson library:
public void testJackson() throws IOException {
JsonFactory factory = new JsonFactory();
ObjectMapper mapper = new ObjectMapper(factory);
File from = new File("text.txt"); // JSON I mentioned above
mapper.readValue(from, MyClass.class);
}
but I'm getting the Exception:
org.codehaus.jackson.map.JsonMappingException: Can not deserialize
instance of Main$MyClass$ThirdField out of START_ARRAY token
You defined your thirdField and fifthField properties as arrays in your JSON. They need to be arrays or collections on your Java bean as well:
public static class MyClass {
#JsonProperty
private String firstField, secondField;
#JsonProperty
private Collection<ThirdField> thirdField;
#JsonProperty
private Collection<FifthField> fifthField;
/// ...
}
As you are going through and converting an existing JSON object into beans, keep in mind that JSON data is very much like a map. If you envision how you would map the data from a map into your object it really helps. Your ThirdField and FifthField objects need to map the definitions in your JSON. This is what your JSON says a ThirdField is:
{
"thirdField_one": "Something Four",
"thirdField_two": "Something Five"
}
Literally converting that to a Java bean gives you:
public class ThirdField implements Serializable {
private String thirdField_one;
private String thirdField_two;
// ...
}
You can add in your annotations etc, etc to get a full fledged bean. Do the same thing for your FifthField object.
Note: I'm the EclipseLink JAXB (MOXy) lead and a member of the JAXB (JSR-222) expert group.
Since it is not always possible to change your domain model (as answered by #Perception), below is how you could map your original object model to the desired JSON using MOXy.
Java Model
In this use case you can leverage the #XmlPath(".") extension. This tells MOXy to bring the contents of the target object into the sources node.
#XmlAccessorType(XmlAccessType.FIELD)
public static class MyClass {
private String firstField, secondField;
#XmlPath(".")
private ThirdField thirdField;
#XmlPath(".")
private FifthField fifthField;
#XmlAccessorType(XmlAccessType.FIELD)
public static class ThirdField {
private List<ThirdFieldItem> thirdField;
}
#XmlAccessorType(XmlAccessType.FIELD)
public static class ThirdFieldItem {
private String thirdField_one, thirdField_two;
}
#XmlAccessorType(XmlAccessType.FIELD)
public static class FifthField {
private List<FifthFieldItem> fifthField;
}
#XmlAccessorType(XmlAccessType.FIELD)
public static class FifthFieldItem {
private String fifthField_one, fifthField_two;
private int fifthField_three;
}
}
Conversion Code
The demo code below shows how to enable MOXy's JSON binding.
public static void main(String[] args) throws Exception {
Map<String, Object> properties = new HashMap<String, Object>(2);
properties.put(JAXBContextProperties.MEDIA_TYPE, "application/json");
properties.put(JAXBContextProperties.JSON_INCLUDE_ROOT, false);
JAXBContext jc = JAXBContext.newInstance(new Class[] {MyClass.class}, properties);
Unmarshaller unmarshaller = jc.createUnmarshaller();
StreamSource json = new StreamSource("src/forum13600952/input.json");
MyClass myClass = unmarshaller.unmarshal(json, MyClass.class).getValue();
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(myClass, System.out);
}
input.json/Output
Belos is the input from your question slightly reformatted to match the output produced by MOXy.
{
"firstField" : "Something One",
"secondField" : "Something Two",
"thirdField" : [ {
"thirdField_one" : "Something Four",
"thirdField_two" : "Something Five"
}, {
"thirdField_one" : "Something Six",
"thirdField_two" : "Something Seven"
} ],
"fifthField" : [ {
"fifthField_one" : "Something...",
"fifthField_two" : "Something...",
"fifthField_three" : 12345
}, {
"fifthField_one" : "Something",
"fifthField_two" : "Something",
"fifthField_three" : 12345
} ]
}
For More Information
http://blog.bdoughan.com/2011/08/json-binding-with-eclipselink-moxy.html
http://blog.bdoughan.com/2010/07/xpath-based-mapping.html
http://blog.bdoughan.com/2011/05/specifying-eclipselink-moxy-as-your.html

Categories