Gson deserialization with changing field types - java

I get a JSON which has the following structure:
{"field1": "string",
"field2": false,
"a": {
"b": {
"listString": []
},
"c": {
"listString": [],
"s": "string"
},
"parent": {
"childA": {
"listString": ["string", "string"]
},
"s": "string"
},
"parent2": {
"listString": ["string", "string"],
"s": "string"
}
},
"field3": ["s", "s"]
}
I'm facing problems with the parent (and parent2) because the format of those fields can change. While the format of the complex objects b and c stays the same. For example, I can get parent (the same holds for parent2) in this way:
{"parent": {
"childA":{
"listString": ["ssssa", "a"]
},
"s": "string"
}}
or
{"parent": {
"listString": ["ssssa", "a"],
"s": "string"
}}
Moreover, childA field (if exists) can have different names, it can be childB or childC
I created java classes for the complex objects:
public class MyPojo{
private String[] field1;
private String field2;
private A a;
private String field3;...}
public class A{
private B b;
private C c;
private Parent parent;
private Parent2 parent2;..}
public class Parent{
private String s;
private ChildA childA;...}...
How can I deserialize something like this with Gson if the parent and parent2 objects have different formates?

This is the parent class:
public class Parent {
Map<String, JsonElement> parent = null;
public Map<String, JsonElement> getParent() {
return parent;
}
public void setParent(Map<String, JsonElement> parent) {
this.parent = parent;
}
}
This is the main class:
public static void main(String[] args) {
// TODO Auto-generated method stub
String input = "{\"parent\": {\"s\": \"string\",\"childA\":{\"listString\": [\"ssssa\", \"a\"]}}}";
Gson gsonInstance = null;
gsonInstance = new GsonBuilder().create();
Parent p = gsonInstance.fromJson(input, Parent.class);
Map<String, JsonElement> parentMap = p.getParent();
Set<String> keyMap = parentMap.keySet();
Iterator<String> iter = keyMap.iterator();
while(iter.hasNext()){
String name = iter.next();
if(name.matches("child(.*)")){
System.out.println(parentMap.get(name));
// do your logic
}
if (keyMap.contains("listString")){
List<String> listString = getListString(parentMap.get("listString"));
System.out.println(listString.toString());
}
}
}
public static List<String> getListString(JsonElement list){
Type listType = new TypeToken<List<String>>() {}.getType();
List<String> listString = new Gson().fromJson(list, listType);
return listString;
}
Hope it helps!

Related

Can Spring Boot read config from a json containing arrays of objects?

I'm trying to reproduce this article so I can load configs from a json file within the app's folder. But there appears to be some issue with the binding to the properties class. When I inspect the nested config I don't see the arrays of objects I'd expect, but an array of LinkedHashMaps.
Any ideas to accomplish this?
Debugging:
DemoBotApplication.java:
package com.myapp.demo;
#SpringBootApplication
#ComponentScan(basePackageClasses = {JsonProperties.class})
public class DemoBotApplication {
#Autowired
private TestService test;
public static void main(String[] args) {
new SpringApplicationBuilder(DemoBotApplication.class).initializers(new JsonPropertyContextInitializer()).run();
}
#PostConstruct
public void init() {
test.test();
}
}
TestService.java:
#Service
public class TestService {
#Autowired
private BotConfig botConfig;
public void test() {
List<com.myapp.demo.config.BotConfig.Device> devices = botConfig.getDevices();
}
}
JsonProperties.java:
package com.myapp.demo.config;
#Configuration
#ConfigurationProperties(prefix = "json")
#Data
public class JsonProperties {
private String serverUrl;
private List<Device> devices;
#Data
public static class Device {
private int id;
private int period;
private float step;
private List<Waypoint> waypoints;
#Data
public static class Waypoint {
private double latitude;
private double longitude;
}
}
}
JsonPropertyContextInitializer.java:
package com.myapp.demo.config;
public class JsonPropertyContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
private final static String JSON_PREFIX = "json";
#Override
#SuppressWarnings("unchecked")
public void initialize(ConfigurableApplicationContext configurableApplicationContext) {
try {
Resource resource = configurableApplicationContext.getResource("file:./config.json");
Map readValue = new ObjectMapper().readValue(resource.getInputStream(), Map.class);
Set<Map.Entry> set = readValue.entrySet();
List<MapPropertySource> propertySources = convertEntrySet(set, Optional.empty());
for (PropertySource propertySource : propertySources) {
configurableApplicationContext.getEnvironment().getPropertySources().addFirst(propertySource);
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private static List<MapPropertySource> convertEntrySet(Set<Map.Entry> entrySet, Optional<String> parentKey) {
return entrySet.stream().map((Map.Entry e) -> convertToPropertySourceList(e, parentKey))
.flatMap(Collection::stream).collect(Collectors.toList());
}
private static List<MapPropertySource> convertToPropertySourceList(Map.Entry entry, Optional<String> parentKey) {
String key = parentKey.map(s -> s + ".").orElse("") + (String) entry.getKey();
Object value = entry.getValue();
return convertToPropertySourceList(key, value);
}
#SuppressWarnings("unchecked")
private static List<MapPropertySource> convertToPropertySourceList(String key, Object value) {
// handles arrays
if (value instanceof ArrayList) {
ArrayList list = (ArrayList) value;
List<MapPropertySource> converted = new ArrayList<MapPropertySource>();
for (int i = 0; i < list.size(); i++) {
LinkedHashMap map = (LinkedHashMap) list.get(i);
Set<Map.Entry> entrySet = map.entrySet();
String arrayKey = String.format("%s[%d]", key, i);
converted.addAll(convertEntrySet(entrySet, Optional.ofNullable(arrayKey)));
}
return converted;
}
// handles nested objects
if (value instanceof LinkedHashMap) {
LinkedHashMap map = (LinkedHashMap) value;
Set<Map.Entry> entrySet = map.entrySet();
return convertEntrySet(entrySet, Optional.ofNullable(key));
}
String finalKey = String.format("%s.%s", JSON_PREFIX, key);
return Collections.singletonList(new MapPropertySource(finalKey, Collections.singletonMap(finalKey, value)));
}
}
config.json:
{
"serverUrl": "localhost:5093",
"devices": [
{
"id": "123456789",
"period": 5,
"step": 0.001,
"speed": 40,
"waypoints": [
{
"latitude": 48.8537,
"longitude": 2.344347
},
{
"latitude": 48.855235,
"longitude": 2.345852
},
{
"latitude": 48.857238,
"longitude": 2.347153
},
{
"latitude": 48.858509,
"longitude": 2.342563
},
{
"latitude": 48.856066,
"longitude": 2.340432
},
{
"latitude": 48.85478,
"longitude": 2.34223
}
]
}
]
}

How to add class name to specified Object

I have the POJO that looks like the following:
public class Person {
public String name;
public int age;
public Map<String, Object> options = new HashMap<>();
}
public class Opt {
public int id;
public String test
}
public class Main {
public static void main(String... args) {
Gson gson = new GsonBuilder().excludeFieldsWithModifiers(Modifier.TRANSIENT, Modifier.STATIC).create();
Opt opt = new Opt();
opt.id = 1;
List<Opt> opts = new ArrayList();
opts.add(opt);
opt.name = "options";
Person person = new Person();
person.name = "John";
person.age = 23;
person.options.put("opts", opts);
String json = gson.toJson(person);
System.out.println(json);
}
}
and I get the JSON looks like the following:
{
"name": "John",
"age": 23,
"options": {
"opts": [{
// how to add "className": "com.example.Opt"
"id": 1,
"name": "options"
}]
}
}
and I need to add to every options data className.
How can I do it using Gson library?

Put method using a mapped class (use setters for specified parameter) - Spring boot API

I'm trying to make a PUT request for an object using only one function for all parameters. Let's say I have this object structure (JSON):
{
"id": 3,
"name": "test",
"dominio": "dom",
"altas": "6",
"bajas": "2",
"default_group": [
{
"idRef": 1,
"name": "Users",
"path": "OU=es"
}
],
"office": [
{
"idRef": 1,
"title": "Intern",
"name": "CN=Office license",
"path": "OU=licenseOffice"
},
{
"idRef": 2,
"title": "Specialist",
"name": "CN=Office License F3",
"path": "OU=LicenseGroupF"
}
]
}
I managed to do this for a GET Request using a Map function with the getters of the class.
To do this, I passed the attribute name in the HTTP request using a GET Request:
Map<String, Function<Compania, Object>> mapCompania = Map.of(
"name", Compania::getName,
"dominio", Compania::getDominio,
"altas", Compania::getAltas,
"bajas", Compania::getBajas,
"default_group", Compania::getDefault_group,
"office", Compania::getOffice
);
Function<Compania, Object> retriever = mapCompania.get(fieldName);
But now, I can't find a way to implement this same thing but in order to use the setter methods. Something like:
PUT localhost/myClass/3/name --> it uses MyClass.setName(input...)
Or:
PUT localhost/myClass/3/office --> it uses MyClass.setOffice(Object office)
Could anyone help me to achieve this? Thank you very much
Assuming that Compania is as follows:
public class Compania {
private Object name;
private Object dominio;
private Object altas;
private Object bajas;
private Object default_group;
private Object office;
public Object getName() {
return name;
}
public void setName(Object name) {
this.name = name;
}
public Object getDominio() {
return dominio;
}
public void setDominio(Object dominio) {
this.dominio = dominio;
}
public Object getAltas() {
return altas;
}
public void setAltas(Object altas) {
this.altas = altas;
}
public Object getBajas() {
return bajas;
}
public void setBajas(Object bajas) {
this.bajas = bajas;
}
public Object getDefault_group() {
return default_group;
}
public void setDefault_group(Object default_group) {
this.default_group = default_group;
}
public Object getOffice() {
return office;
}
public void setOffice(Object office) {
this.office = office;
}
}
The code below should do the trick:
Map<String, BiConsumer<Compania, Object>> mapCompaniaSetters = Map.of(
"name", Compania::setName,
"dominio", Compania::setDominio,
"altas", Compania::setAltas,
"bajas", Compania::setBajas,
"default_group", Compania::setDefault_group,
"office", Compania::setOffice
);
BiConsumer<Compania, Object> setter = mapCompaniaSetters.get(fieldName);
We can test this as follows to check that it actually works:
public static void main(String[] args) {
Map<String, BiConsumer<Compania, Object>> mapCompaniaSetters = Map.of(
"name", Compania::setName,
"dominio", Compania::setDominio,
"altas", Compania::setAltas,
"bajas", Compania::setBajas,
"default_group", Compania::setDefault_group,
"office", Compania::setOffice
);
BiConsumer<Compania, Object> setter = mapCompaniaSetters.get("name");
Compania compania = new Compania();
System.out.println("Empty Compania: " + compania);
setter.accept(compania, "Test");
System.out.println("Compania with Name: " + compania);
}

Jackson deserialize JSON into pojo with map property

Can somebody help me, how I can deserialize the following JSON, which I can not change?
I am using Jackson for serialization.
{
"columns": [
{
"header": "Heading1",
},
{
"header": "Heading2",
}
],
"rows": [
"id": 1,
"Heading1": {
"value": "Value1"
},
"Heading2": {
"value": "Value2"
}
]
}
Columns can have unknown number of headers and their value eg. "Header1" is used in the rows array.
So far I have the following structure:
public class QueryResult {
private ColumnConfig[] columns;
private QueryResultRow[] rows;
}
public class ColumnConfig {
private String header;
}
public class QueryResultRow {
private int id;
private Map<String, CellValue> values;
}
public class CellValue{
private String value;
}
The problem is that the Map is empty when I deserialize into QueryResult;
I read about TypeReference but I do not know how I can specify a TypeReference<HashMap<String,CellValue>> for the property values in QueryResultRow.
Edit:
My ObjectMapper code is the following:
ObjectMapper mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
String queryResultString = loadQuery(queryPath);
QueryResult result = mapper.readValue(queryResultString, QueryResult.class);
The content of queryResultString is the JSON above.
First problem is your JSON is invalid. I assume it should be something like this,
{
"columns": [
{
"header": "Heading1"
},
{
"header": "Heading2"
}
],
"rows": [
{
"id": 1,
"Heading1": {
"value": "Value1"
},
"Heading2": {
"value": "Value2"
}
}
]
}
Then answer is quite straightforward. You need to change your QueryResultRow as follows,
class QueryResultRow {
private int id;
private Map<String, CellValue> values = new HashMap<>();
#JsonAnySetter
public void addValues(String k, CellValue v) {
values.put(k, v);
}
}
Then I think you should good to go.
Here is a complete working example,
public class Main {
public static void main(String[] args) throws IOException {
String s = "{\"columns\":[{\"header\":\"Heading1\"},{\"header\":\"Heading2\"}],\"rows\":[{\"id\":1,\"Heading1\":{\"value\":\"Value1\"},\"Heading2\":{\"value\":\"Value2\"}}]}";
ObjectMapper om = new ObjectMapper();
QueryResult queryResult = om.readValue(s, QueryResult.class);
System.out.println(queryResult);
}
}
#Getter
#Setter
#ToString
class QueryResult {
private ColumnConfig[] columns;
private QueryResultRow[] rows;
}
#Getter
#Setter
#ToString
class ColumnConfig {
private String header;
}
#Getter
#Setter
#ToString
class QueryResultRow {
private int id;
private Map<String, CellValue> values = new HashMap<>();
#JsonAnySetter
public void addValues(String k, CellValue v) {
values.put(k, v);
}
}
#Getter
#Setter
#ToString
class CellValue{
private String value;
}

Recommendation for resetting data in json object using Java

I need a recommendation for this situation.
I have a json object in string format that will have pattern like this:
{
"productCard" : {
"productA" : {
"state" : "Y",
"desc" : "AAA",
"someProp" : 112
},
"productB" : {
"state" : "X",
"desc" : " BBB ",
"listSomeThing" : [
{
"p1" : 1,
"p2" : "2"
},
{
"p2" : "3"
}
]
}
// PRODUCT CAN ADD MORE IN FUTRE
// ALSO CAN HAVE OTHER OBJECT TYPE
}
// THIS CAN HAVE OTHER OBJECT THAT MAY BE NON RELATE INFORMATION WITH PRODUCT CARD
}
and then this will be parsed to an object like this:
class Product {
protected String state
protected String desc
}
class SomeThing {
private int p1
private String p2
}
class ProductA extend Product {
private int someProp
}
class ProductB extend Product {
private List<SomeThing> listSomeThing
}
class ProductCard {
private ProductA prodctA
private ProductB productB
}
class BaseObject {
private ProductCard productCard
}
If I need to reset some field value in each product, and then parse to string format again, should I:
(1) create a new function in Product and then override in some child class for extra method:
class Product {
void reset(){
this.state = "X"
this.desc = ""
}
}
class productB extend Product {
#override
void reset(){
super.reset()
this.listSomeThing = new ArrayList<>()
}
}
and in base object create new function:
class ProductCard {
private ProductA productA
private ProductB productB
void resetAllProduct(){
this.productA.reset()
this.productB.reset()
}
}
class BaseObject {
private ProductCard productCard
void resetAllProductCard(){
this.productCard.resetAllProduct()
}
}
then call BaseObject.resetAllProductCard() where business needs to reset?
(2) create new function in business class? Or some util class:
void reset(ProdctCard productCard){
ProductA productA = productCard.getProductA();
productA.setState("X")
productA.setDesc("")
ProductB productB = productCard.getProdctB();
productB.setState("X")
productB.setDesc("")
productB.setListSomeThing(new ArrayList<>())
}
(3) another approach?
I would use Jackson Project for that job:
public String reset(String json) throws IOException {
ObjectMapper mapper = new ObjectMapper();
JsonNode jsonNode = mapper.readTree(json);
JsonNode productCardNode = jsonNode.get("productCard");
productCardNode.forEach(node -> ((ObjectNode) node).put("state", "X").put("desc", ""));
ObjectNode productBNode = (ObjectNode) productCardNode.get("productB");
productBNode.putArray("listSomeThing");
return jsonNode.toPrettyString();
}
Then:
String jsonReseted = reset(json);
System.out.println(jsonReseted);
Output:
{
"productCard" : {
"productA" : {
"state" : "X",
"desc" : "",
"someProp" : 112
},
"productB" : {
"state" : "X",
"desc" : "",
"listSomeThing" : [ ]
}
}
}

Categories