Parse a nested dynamic module using Gson - java

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
}

Related

GSON serialize a list of polymorphic Composite classes

I have a set of classes with following structure
public abstract class Rule { .... }
public class RuleType1 extends Rule { ... }
public class RuleType2 extends Rule { ... }
public class RuleType3 extends Rule { ... }
public class RuleType4 extends Rule { ... }
public class RuleSet extends Rule {
...
private final Collection<Rule> rules;
}
I want to parse following json, which contains multiple nested Rule child classes. Since RuleSet contains Collection of Rule, it can have multiple RuleSet as well.
I have checked these answers already. I don't want to implement custom (de)serializer because that will make child classes less flexible. (Whoever will change classes will need to change serializer classes as well)
Deserializing an abstract class in Gson
Gson serialize a list of polymorphic objects
Here is the same json, I want to serialize/deserialize.
{
"ruleType": "ruleSet",
"rules": [
{
"ruleType": "ruleSet",
"rules": [
{
"ruleType": "RuleType1"
},
{
"ruleType": "ruleSet",
"rules": [
{
"ruleType": "RuleType2"
},
{
"ruleType": "RuleType3"
}
]
}
]
},
{
"ruleType": "ruleSet",
"rules": [
{
"ruleType": "RuleType1"
},
{
"ruleType": "ruleSet",
"rules": [
{
"ruleType": "RuleType3"
},
{
"ruleType": "RuleType4"
}
]
}
]
}
]
}
Edit after comments:
This is my class structure where RuleSet has Collection<IRule> rules
And this is my class
public class SampleClass {
public IRule parameters;
public double defaultConfidence;
public String ruleType;
}
static RuntimeTypeAdapterFactory<IRule> adaptor = RuntimeTypeAdapterFactory.of(IRule.class)
.registerSubtype(RuleSet.class)
.registerSubtype(Rule.class);
static RuntimeTypeAdapterFactory<Rule> ruleAdaptor = RuntimeTypeAdapterFactory.of(Rule.class)
.registerSubtype(DocumentTypesRule.class)
.registerSubtype(DataAttributesRule.class)
.registerSubtype(DictionariesRule.class)
.registerSubtype(FileFormatsRule.class)
.registerSubtype(FileLengthRule.class)
.registerSubtype(FileNamePatternsRule.class)
.registerSubtype(FixedRule.class)
.registerSubtype(ImageTypesRule.class)
.registerSubtype(KeywordsRule.class)
.registerSubtype(RegexesRule.class)
.registerSubtype(SimilaritiesRule.class)
.registerSubtype(TopicsRule.class);
private static final Gson GSON = new GsonBuilder()
.registerTypeAdapterFactory(adaptor)
.registerTypeAdapterFactory(ruleAdaptor)
.create();
and facing following error while calling GSON.fromJson(response, SampleClass.class)
java.lang.RuntimeException: Unable to invoke no-args constructor for interface <classPath>.IRule. Registering an InstanceCreator with Gson for this type may fix this problem.

Jackson: Deserializing a polymorphic list

I would like to implement a custom deserializer for our REST API that is not only used by Java application. Therefore I don't want to have Jackson putting type information into the serialized JSON.
I'm currently struggling with deserializing CollectionExpand since it contains a list data of specific ResourceModel.
public class EntityModel<R extends ResourceModel> implements Serializable {
private R data;
private List<ResourceLink> links;
private List<CollectionExpand> expands;
}
public class CollectionExpand {
private String name;
// Resource Model is an interface
private Collection<ResourceModel> data;
}
ResourceModel is an interface an each CollectionExpand contains a collection of one type of ResourceModel per name.
For example a json output could look like this.
{
"data": {},
"links": [],
"expand": [
{
"name": "photos",
"data": [
{
"id": 12,
"name": "hello.jpg"
},
{
"id": 12,
"name": "hello.jpg"
}
]
},
{
"name": "persons",
"data": [
{
"id": 783378,
"name": "Peter",
"age": 12
},
{
"id": 273872,
"name": "Maria",
"age": 77
}
]
}
]
}
As you can see each name contains the same type of resource model. photos contains PhotoResourceModel and person contains PersonResourceModel.
I started to implement my custom Jackson Deserializer
public class CollectionExpandDeserializer extends StdDeserializer<CollectionExpand> {
public CollectionExpandDeserializer() {
super(CollectionExpand.class);
}
#Override
public CollectionExpand deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
CollectionExpand collectionExpand = new CollectionExpand();
if (Objects.equals(p.nextFieldName(), "name")) {
collectionExpand.setName(p.nextTextValue());
}
if (Objects.equals(p.nextFieldName(), "data")) {
// depending on the field name I would like to delegate the deserialization to a specific type.
if (name.equals("photos") {
// how to do this?
collectionExpand.setData(/* deserialize to a list of PhotoResource */);
}
}
return collectionExpand;
}
I'm current stuck on how can I delegate telling Jackson to deserialize this as a PhotoResource list.
In general is this the right approach or is there another way to do it (without putting any Jackson meta data into the JSON while serialization)?
I have ended up implementing my custom deserializer as below
#Override
public CollectionExpand deserialize(JsonParser p, DeserializationContext ctx) throws IOException {
JsonNode node = ctx.readTree(p);
CollectionExpand collectionExpand = new CollectionExpand();
collectionExpand.setName(node.get("name").asText());
ArrayNode data = node.withArray("data");
Iterator<JsonNode> iterator = data.iterator();
while (iterator.hasNext()) {
Class<? extends ResourceModel> aClass = resolveClass(collectionExpand.getName());
if (aClass != null) {
JsonNode jsonNode = iterator.next();
collectionExpand.getData().add(p.getCodec().treeToValue(jsonNode, aClass));
}
}
return collectionExpand;
}
private Class<? extends ResourceModel> resolveClass(String name) {
if ("contents".equals(name)) {
return ContentsGetResourceModel.class;
} else if ("tags".equals(name)) {
return TagsGetResourceModel.class;
} else {
return null;
}
}
I took my a while to understand how to deserialize a JsonNode/TreeNode into a specific type. In the end I learned that this can be basically done by using the parsers codec.
PhotoResource photoResource = p.getCodec().treeToValue(jsonNode, PhotoResource.class);

Spring boot RequestBody with object fields that could be needed

How can I make my RequestBody accept a model with object fields that may or may not be included?
This is the Restcontroller endpoint I’m calling
#PatchMapping(value = "/projects/update/{projectNumber}")
public ResponseEntity<Object> updateProject(#PathVariable int projectNumber, #RequestBody RequestReviewUpdate rru)
{
return reviewUpdateService.updateReview(projectNumber, rru);
}
Here is the model #RequestBody is accepting
public class RequestReviewUpdate
{
private UpdateProject project;
private List<UpdateHlsd> reviews;
private List<UpdateProjectLeadership> stakeHolders;
public UpdateProject getProject()
{
return project;
}
public List<UpdateHlsd> getReviews()
{
return reviews;
}
public List<UpdateProjectLeadership> getStakeHolders()
{
return stakeHolders;
}
}
I want to be able to send a JSON could contain all object field, or some.
My JSON body could look something like this...
{
"project": {
"type": "HLSD"
},
"reviews": [
{
"id": 570,
"requestedBy": "Name here",
"notes":"test"
}
],
"stakeHolders": [
{
"id": 1088,
"projectResource": "sid"
}
]
}
Or this...ect
{
"reviews": [
{
"id": 570,
"requestedBy": "name",
"notes":"test"
}
}
Or any other possible combinations.
Just make your RequestReviewUpdate optional, like:
#PatchMapping(value = "/projects/update/{projectNumber}")
public ResponseEntity<Object> updateProject(#PathVariable int projectNumber, #RequestBody(required=false) RequestReviewUpdate rru) {
return reviewUpdateService.updateReview(projectNumber, rru);
}
This will make your parameter optional, either entirely or partially. I.e. any member of the object you receive as an argument, might be present or absent.

Customize JSON view with Jackson for Dynatable

I am working on a Spring Boot application. In the html view, I make a ajax call to a RestController, which returns a list of custom entities:
#Controller
public class MyController {
#ResponseBody
#JsonView(View.MyView.class)
public List<CustomEntity> getEntities() {
...
}
}
This is working fine, I am getting, as expected, following structure:
{
"id": "1",
"name": "Test1"
},
{
"id": "2",
"name": "Test2"
}
In the view, I want to use it with Dynatable. And here comes my problem. I need following structure:
{
"records": [
{
"id": "1",
"name": "Test1"
},
{
"id": "2",
"name": "Test2"
}
],
"queryRecordCount": 2,
"totalRecordCount": 2
}
Is there a way to generate the JSOn view using jackson (or any other framework) based on a template, so I can use the data with Dynatable, and if so, how?
Thanks in advance,
Stephan
You could create a wrapper that does this for you...
class DyntableResponse<T> {
private List<T> records;
public List<T> getRecords() { return records; }
public void setRecords(final List<T> records) { this.records = records; }
public int getQueryRecordCount() { return records.size(); }
public int getTotalRecordCount() { return records.size(); }
}
And then return it from your RestController...
#Controller
public class MyController {
#ResponseBody
#JsonView(View.MyView.class)
public DyntableResponse<CustomEntity> getEntities() {
final DyntableResponse<CustomEntity> resp = new DyntableResponse<>();
resp.setRecords(...); // Your finder here.
return resp;
}
}
This is untried, but should be close.

Serialize JSON file with GSON in java

I want to convert this JSON into objects in java:
{
"mapping": [
{
"boardPosition": {
"row": 1,
"col": 1
},
"nodeId": 3242324
},
{
"boardPosition": {
"row": 1,
"col": 2
},
"nodeId": 432423
},
{
"boardPosition": {
"row": 1,
"col": 3
},
"nodeId": 424324132
}
]
}
this is how I created my java classes
class MapeoWumpus {
public mapp mapping;
}
class mapp{
public boardP boardPosition;
public String nodeId;
}
class boardP{
public int row;
public int col;
}
and then when I try to convert my file like this
MapeoWumpus mapa=new MapeoWumpus();
mapa=gson.fromJson(filetext, MapeoWumpus.class);
I get a null object
What can I do?
EDIT: This is my entire code:
package parserjson;
import java.io.FileNotFoundException;
import java.util.*;
import com.google.gson.*;
public class Main {
/**
* #param args
*/
public static void main(String[] args) throws FileNotFoundException {
String filetext;
ParserJson parser=new ParserJson();
Gson gson=new Gson();
MapeoWumpus mapa=new MapeoWumpus();
filetext=parser.leerArchivo("b1.json");
mapa=gson.fromJson(filetext, MapeoWumpus.class);
}
}
"leerArchivo" is just a method to get the json file, as you can see my json file is in a string variable
You should define instance variable mapp as array. Because your JSON data seems to contain mapping array.
class MapeoWumpus {
public mapp[] mapping;
}
Creating new MapeoWumpus in the below code is unnecessary
MapeoWumpus mapa=new MapeoWumpus();
mapa=gson.fromJson(filetext, MapeoWumpus.class);
Just change it as follows
MapeoWumpus mapa=gson.fromJson(filetext, MapeoWumpus.class);

Categories