How to handle varying data by one POJO - java

I'm trying to create a single POJO for the varying JSON data. Is there is a way to implement this by a single class only? and without writing Serializer and Deserializer?
ResultTwo Example:
{ "results": ["24","0","18","34","27"] }
ResultOne Example:
{
"results": [
{
"value": "2|2|5"
},
{
"value": "2|3|4",
"multiplier": 25
},
{
"value": "2|3|5"
},
{
"value": "2|3|4",
"multiplier": 50
},
{
"value": "1|1|4"
},
{
"value": "3|6|6",
"multiplier": 30
}
]
}
What I've tried:
BaseResult
public class BaseResult { }
ResultOne
#Data
#JsonInclude(JsonInclude.Include.NON_NULL)
public class ResultOne extends BaseResult {
// {"value":"2|6|6","multiplier":25}
// or
// {"value":"4|4|4"}
private String value;
private Integer multiplier;
}
ResultTwo
#Data
#JsonInclude(JsonInclude.Include.NON_NULL)
public class ResultTwo extends BaseResult {
private String result;
}
ResultSerializer
public class ResultSerializer extends StdSerializer<BaseResult> {
public ResultSerializer(Class<BaseResult> t) {
super(t);
}
#Override
public void serialize(
BaseResult value, JsonGenerator gen, SerializerProvider arg2)
throws IOException {
if(value instanceof ResultTwo){
ResultTwo result = (ResultTwo)value;
gen.writeString(result.getResult());
}else if(value instanceof ResultOne){
ResultOne resultOne = (ResultOne)value;
gen.writeObject(resultOne);
}else{
throw new ClassCastException("BaseResult value doesn't have any known type: " + value);
}
}
}
ResultDeserializer
public class ResultDeserializer
extends StdDeserializer<BaseResult> {
public ResultDeserializer(Class<?> vc) {
super(vc);
}
#Override
public BaseResult deserialize(
JsonParser jsonparser, DeserializationContext context)
throws IOException {
String content = jsonparser.getText();
JsonNode node = jsonparser.getCodec().readTree(jsonparser);
if(node instanceof ObjectNode){
ObjectMapper objectMapper = new ObjectMapper();
return objectMapper.treeToValue(node, ResultOne.class);
}else{
ResultTwo resultTwo = new ResultTwo();
resultTwo.setResult(content);
return resultTwo;
}
}
}
On parent class I've annotated this:
#JsonSerialize(contentUsing = ResultSerializer.class)
#JsonDeserialize(contentUsing = ResultDeserializer.class)
private List<BaseResult> results = null;

#Kulsin -
In spring controller you can use Map<String, Object> as request body, which can entertain your both the requests-
#RequestMapping(value = "/test", method = RequestMethod.POST, consumes = "application/json", produces = "application/json")
public void testMethod(#RequestBody Map<String, Object> request) {
//access data using request reference.
}

Related

Deserialize multiple json fields into single java property

I want to convert a json into Java class by having custom deserializer.
I'm able to serialize ACC_NUM, NAME and any other fields from json but not sure what can be done to convert MOBILE_NUMBER_1,MOBILE_NUMBER_2 like fields into single JSONArray(See AccountInfo class). There can be many more fields like this and count also is not fixed. Example there can be ADDRESS_1, ADDRESS_2 till ADDRESS_20 and so on and all this fields should go in JSONArray of ADDRESS after deserilization.
I have a Map of Map which holds info like this:
{
"accountInfo": {
"ACC_NUM": "1234567890",
"NAME": "John Cena",
"MOBILE_NUMBER_1": "12376534",
"MOBILE_NUMBER_2": "12376534",
"MOBILE_NUMBER_3": "12376534",
"MOBILE_NUMBER_4": "12376534"
},
"someOther": {
//similer to above
}
}
This info I want to convert to the following class CommonInfo:
public class CommonInfo {
private AccountInfo accountInfo;
//other properties...
}
#JsonIgnoreProperties(ignoreUnknown = true)
public class AccountInfo {
#JsonProperty("ACC_NUM")
private FieldValue<BigInteger> accountNum;
#JsonProperty("NAME")
private FieldValue<String> name;
#JsonProperty("MOBILE_NUMBER")
private FieldValue<JSONArray> mobileNumber;
}
//FieldValue class
public interface FieldValue<T> {
T getInitialValue();
void setInitialValue(T initialValue);
T getValue();
void setValue(T value);
}
#JsonInclude(JsonInclude.Include.ALWAYS)
public class FieldValueImpl<T> implements FieldValue<T> {
protected T initialValue;
protected T value;
//getters, setters, cons..
}
My service code takes json/Map and tries to convert it to CommonInfo class
#Service
public class MyService {
private final ObjectMapper jsonMapper = new ObjectMapper();
#PostConstruct
protected void init() {
SimpleModule simpleModule = new SimpleModule();
simpleModule.addSerializer(FieldValue.class, new FieldValueSerializer());
simpleModule.addDeserializer(FieldValue.class, new FieldValueDeserializer());
jsonMapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);
jsonMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
jsonMapper.registerModule(simpleModule);
}
public CommonInfo setPojoResult(Map<String, LinkedHashMap<String, String>> contentAsMap) {
return jsonMapper.convertValue(contentAsMap, CommonInfo.class);
}
}
Serializer and Deserializer looks like this:
public class FieldValueDeserializer extends JsonDeserializer<FieldValue<?>> implements ContextualDeserializer {
private JavaType valueType;
public JsonDeserializer<?> createContextual(DeserializationContext ctxt, BeanProperty property)
throws JsonMappingException {
var deserializer = new FieldValueDeserializer();
if (property == null) {
deserializer.valueType = ctxt.getContextualType().containedType(0);
} else {
var wrapperType = property.getType();
var valueType = wrapperType.containedType(0);
deserializer.valueType = valueType;
}
return deserializer;
}
#Override
public FieldValue<?> deserialize(JsonParser parser, DeserializationContext context) throws IOException {
FieldValueDeserializer deserializer = new FieldValueDeserializer();
deserializer.getKnownPropertyNames();
FieldValue<?> fieldValueImpl = new FieldValueImpl<>();
if (valueType.toString().contains("java.time.LocalDate")) {
JsonNode node = parser.getCodec().readTree(parser);
FieldValue<LocalDate> f1 = new FieldValueImpl<>();
f1.setValue(DateUtils.convertJulianToLocalDate(node.textValue()));
return f1;
} else {
fieldValueImpl.setValue(context.readValue(parser, valueType));
}
return fieldValueImpl;
}
}
//--
public class FieldValueSerializer extends StdSerializer<FieldValue> {
public FieldValueSerializer() {
this(null);
}
public FieldValueSerializer(Class<FieldValue> vc) {
super(vc);
}
#Override
public void serialize(FieldValue value, JsonGenerator jgen, SerializerProvider provider) throws IOException {
jgen.writeString(String.valueOf(value.getCurValue()));
}
}

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 do I deserialize json into a single object from RestTemplate?

I have to use the exchange() method because I pass HttpHeaders there.
ResponseEntity<WeatherResponse> response Entity = restTemplate.exchange(
weather UrlRequest, Http Method.GET, new HttpEntity<>(headers), WeatherResponse.class);
JSON:
{
"geoloc": {
"city": {
"id": 213,
"name": "Boston"
},
"country": {
"id": 213,
"name": "USA"
},
"temp": {
"value": 19.4
}
}
Object to deserialization:
class WeahterResponse{
String country;
String city;
float temp;
}
How to influence deserialization in this case. There are two objects in JSON, and I need one?
class WeahterResponse{
GeoLocation geoloc;
Map<String,String> temp;
}
class GeoLocation {
Map<String,Map<String,Object> geoData;
}
It will deserialize your data to WeatherResponse.
Now if you want to get city data or country Data you can get that as follows.
suppose json is deserialized into weatherResponse.
Map<String,Map<String,Object> geoData = weatherResponse.getGeoLoc();
if(!CollectionUtils.isEmpty(geoData)){
if(geoData.containsKey("city")){
Map<String,Object> cityData = geoData.get("city");
System.out.println(cityData.get("id");
System.out.println(cityData.get("name");
}
//same for other keys of geoLoc
//to get Temp value
Map<String,String> temp = weatherResponse.getTemp();
System.out.println(temp.get("value");
Thanks for João Dias.
I made custom deserializer. https://www.baeldung.com/jackson-deserialization
In my case:
public class WeatherDeserializer extends JsonDeserializer<WeatherResponse> {
#Override
public WeatherResponse deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
JsonNode nodeTree = jsonParser.getCodec().readTree(jsonParser);
JsonNode geoObjectNode = nodeTree.get("geo_object");
JsonNode factNode = nodeTree.get("fact");
String country = geoObjectNode.get("country").get("name").textValue();
String province = geoObjectNode.get("province").get("name").textValue();
String locality = geoObjectNode.get("locality").get("name").textValue();
GeoObject geoObject = new GeoObject(country, province, locality);
Short temp = factNode.get("temp").shortValue();
Long obsTime = factNode.get("uptime").longValue();
return new WeatherResponse(geoObject,temp,obsTime);
}
}
#AllArgsConstructor
#Getter
#JsonDeserialize(using = WeatherDeserializer.class)
public class WeatherResponse {
private GeoObject geoObject;
private Short temp;
private Long uptime;
}

Serialize, deserialize using jackson

I was trying convert to my object to and from json but the default serializer, deserializer by jackson doesn't work.
How can I make this work? I understand I might need to write a custom serializer, deserializer. How can I do that?
Is ther some annotation by adding which the code would work?
Here is the object:
#JsonDeserialize(keyUsing = mypairDeserializer.class)
#JsonSerialize(keyUsing = mypairSerializer.class)
HashMap<Set < Mypair > , List < Mypair > > obj;
public class ConditionSerializer extends JsonSerializer<Collection<mypair>> {
#Override
public void serialize(final Collection<mypair> conditionSet, final JsonGenerator jsonGenerator, final SerializerProvider serializerProvider) throws IOException, JsonProcessingException {
jsonGenerator.writeStartObject();
jsonGenerator.writeFieldName("Pair");
jsonGenerator.writeStartArray();
for(final Condition condition: conditionSet) {
jsonGenerator.writeString(mypair.toString());
}
jsonGenerator.writeEndArray();
jsonGenerator.writeEndObject();
}
}
public class mypairDeserializer extends KeyDeserializer {
ObjectMapper mapper = new ObjectMapper();
#Override
public Collection<mypair> deserializeKey(final String key, final DeserializationContext ctxt) throws IOException, JsonProcessingException {
// return new mypair(key);
return mapper.readValue(key, Collection.class);
}
}
Hi again from last post,
So, this is an example of what you can do :
Note that since I don't know what is your object Mypair, I did this example with a User class :
public class User {
private int id;
private String name;
public User(int id, String name) {
super();
this.id = id;
this.name = name;
}
// getters & setters
}
The class containing your complex object :
public class YourClass {
#JsonSerialize(using = ComplexObjectSerializer.class)
private Map<Set<User>, List<User>> object;
public YourClass(Map<Set<User>, List<User>> object) {
this.object = object;
}
public Map<Set<User>, List<User>> getObject() {
return object;
}
public void setObject(Map<Set<User>, List<User>> object) {
this.object = object;
}
}
The custom serializer :
public class ComplexObjectSerializer extends StdSerializer<Map<Set<User>, List<User>>> {
public ComplexObjectSerializer() {
this(null);
}
public ComplexObjectSerializer(Class<Map<Set<User>, List<User>>> t) {
super(t);
}
private static final long serialVersionUID = 1L;
#Override
public void serialize(Map<Set<User>, List<User>> complexObject,
JsonGenerator jsonGen, SerializerProvider arg2) throws IOException {
// Suppose you want the following json:
/**
* [ { "set":[], "list":[] } ]
*/
jsonGen.writeStartArray(); // [
for (Entry<Set<User>, List<User>> entry : complexObject.entrySet()) {
jsonGen.writeStartObject(); // {
jsonGen.writeObjectField("set", entry.getKey()); // It will call the default serializer for a Set<User>, ie : [ {"id": 0, "name":"string"} ]
jsonGen.writeObjectField("list", entry.getValue()); // It will call the default serializer for a List<User>, ie the same thing as the Set above
jsonGen.writeEndObject(); // }
}
jsonGen.writeEndArray(); // ]
}
}
Main :
Map<Set<User>, List<User>> complexObject = new HashMap<Set<User>, List<User>>();
// Add some data in the map ...
YourClass yourClass = new YourClass(complexObject);
// Serialize your object
ObjectMapper mapper = new ObjectMapper();
String json = mapper.writeValueAsString(yourClass); // It will call your custom serializer
System.out.println(json);
Output :
{
"object": [
{
"set": [
{
"id": 5,
"name": "userName5"
},
{
"id": 6,
"name": "userName6"
}
],
"list": [
{
"id": 2,
"name": "userName2"
}
]
},
{
"set": [
{
"id": 4,
"name": "userName4"
},
{
"id": 3,
"name": "userName3"
}
],
"list": [
{
"id": 0,
"name": "userName0"
},
{
"id": 1,
"name": "userName1"
}
]
}
]
}

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;
}

Categories