Parse list of beans with snakeyaml - java

Is it possible to parse with snakeyaml the following content and obtain a List<Radio> (where Radio is the appropriate java bean) ?
-
id: chaine416
name: 'France Inter'
type: music
-
id: chaine417
name: 'France Culture'
type: music
-
id: chaine418
name: 'Couleur 3'
type: music
new Yaml().load(...); returns a List<HashMap>, but I'd like to get a List<Radio> instead.

The only way I know is to use a top object to handle the collection.
Yaml file :
---
stations:
-
id: chaine416
name: "France Inter"
type: music
-
id: chaine417
name: "France Culture"
type: music
-
id: chaine418
name: "Couleur 3"
type: music
I just added "---" , new document and an attribute stations.
Then :
package snakeyaml;
import java.util.ArrayList;
public class Radios {
ArrayList<RadioStation> stations = new ArrayList<RadioStation>();
public ArrayList<RadioStation> getStations() {
return stations;
}
public void setStations(ArrayList<RadioStation> stations) {
this.stations = stations;
}
}
The class RadioStation :
package snakeyaml;
public class RadioStation {
String id;
String name;
String type;
public RadioStation(){
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
#Override
public String toString() {
return "RadioStation{" +
"id='" + id + '\'' +
", name='" + name + '\'' +
", type='" + type + '\'' +
'}';
}
}
And to read the YAML file :
package snakeyaml;
import org.yaml.snakeyaml.Yaml;
import org.yaml.snakeyaml.constructor.Constructor;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
public class Test {
public static void main(String[] args) {
Yaml yaml = new Yaml(new Constructor(Radios.class));
try {
Radios result = (Radios) yaml.load(new FileInputStream("/home/ofe/dev/projets/projets_non_byo/TachesInfoengine/src/snakeyaml/data.yaml"));
for (RadioStation radioStation : result.getStations()) {
System.out.println("radioStation = " + radioStation);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
}

Actually, there is a method loadAll in Yaml that returns Iterable but the yaml file should have a separator "---" between list items.
The file should look like:
id: chaine416
name: 'Fr
ance Inter'
type: music
---
id: chaine417
name: 'France Culture'
type: music
---
id: chaine418
name: 'Couleur 3'
type: music
(The Radio DTO is omitted) And the code should be something like this:
public class Test {
public static void main(String[] args) {
Yaml yaml = new Yaml(new Constructor(Radio.class));
InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream("yourFile.yaml");
List<Radio> radios = StreamSupport.stream(yaml.loadAll(inputStream).spliterator(), false)
.filter(Radio.class::isInstance)
.map(Radio.class::cast)
.collect(Collectors.toList());
}
}

Related

How to convert JSON field name to Java bean class property with Jackson

I have access to a RESTful API which returns JSON Strings, such as the following:
{
"Container1": {
"active": true
},
"Container2": {
"active": false
},
}
The problem is that the RESTful API is a bit maldesigned. The field name contains the data already. With the Jackson library it is not possible to deserialize the field name to a property name of the corresponding Java bean class. I assume, this isn't intended by the JSON specification neither. The above JSON string needs to be deserialized to an instance of the following class:
public class Container {
private Boolean active;
private String name;
}
I end up with UnrecognizedPropertyException for the field Container1.
I thought to configure to ignore unknown properties and to provide a JsonDeserializer for that property like this:
#JsonIgnoreProperties(ignoreUnknown = true)
public class Container {
private Boolean active;
private String name;
#JsonDeserialize(using = FieldNameToPropertyDeserializer.class)
public void setName(String name) {
this.name = name;
}
}
and the FieldNameToPropertyDeserializer:
public class FieldNameToPropertyDeserializer extends StdDeserializer<String> {
public FieldNameToPropertyDeserializer() {
super(String.class);
}
#Override
public String deserialize(JsonParser parser, DeserializationContext context) throws IOException, JsonProcessingException {
return parser.getCurrentName();
}
}
The invocation of the deserialization is achieved as follows:
String jsonString = response.readEntity(String.class);
ObjectMapper objectMapper = new ObjectMapper();
ObjectReader readerFor = objectMapper.readerFor(Container.class);
MappingIterator<Container> mappingIterator = readerFor.readValues(jsonString);
while (mappingIterator.hasNext()) {
Container container = (Container) mappingIterator.next();
containers.add(container);
}
But I only receive empty objects (properties set to null) because the parsing of the properties is skipped since I set #JsonIgnoreProperties(ignoreUnknown = true).
Is this possible at all? Or should I implement something like a post-processing afterwards?
How about this. Create a class ContainerActive like this
public class ContainerActive {
private boolean active;
// constructors, setters, getters
}
And you could just do
Map<String, ContainerActive> map = mapper.readValue(jsonString, new TypeReference<Map<String, ContainerActive>>() {});
With this you will have "Container1", "Container2" as the keys and ContainerActive Object as values which has active field.
Just a quick solution, if the object is such that, that all of it object is a container object you can receive the JSON inside and JSONObject you may use below code
import java.io.IOException;
import org.json.JSONException;
import org.json.JSONObject;
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
public class TestSO {
public static void main(String[] args) throws JsonParseException, JsonMappingException, JSONException, IOException {
String jsonString = "{\r\n" +
" \"Container1\": {\r\n" +
" \"active\": true\r\n" +
" },\r\n" +
" \"Container2\": {\r\n" +
" \"active\": false\r\n" +
" },\r\n" +
"}";
JSONObject jsonObject = new JSONObject(jsonString);
ObjectMapper mapper = new ObjectMapper();
for (String key : jsonObject.keySet()) {
Container container = mapper.readValue(jsonObject.get(key).toString(), Container.class);
System.out.println(container);
}
}
static class Container{
private String name;
private Boolean active;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Boolean getActive() {
return active;
}
public void setActive(Boolean active) {
this.active = active;
}
#Override
public String toString() {
return "Container [name=" + name + ", active=" + active + "]";
}
}
}

How to map key and values from two files?

I have searched around but couldn't find any similar question so I am posting a new one.
Currently I have basically two files. One file is the definition of the key, another file is the value that maps to the key (CSV format, but I'd like to not restrict it CSV file).
File 1:
id:TEXT,name:TEXT,city:TEXT,state:TEXT,zip:Integer
What it means is that the file has 5 fields, it defines the id of type TEXT, name of type TEXT, zip of type Integer, etc.
File 2 (each record is separated by a new line, there will be thousands of lines record):
11212, karen, new york, NY, 10000
21312, jim, boston, MA, 10000
12312,,seattle,,10000 // name and state is not available in this record
So the file 2 will have the value that maps to the key in file 1, notice if the value is null or empty, it will just be ignored in the result.
What would be an element way to convert these files into a java object as below:
#Data
#AllArgsConstructor
public class RecordCollection {
// Key is to map to the `id`, whereas the rest of the values map to Record
Map<String, Record> records;
}
#Data
#AllArgsConstructor
public class Record {
String name;
String city;
String state;
Integer zip;
}
To start I have:
String keyValues = "id:TEXT,name:TEXT,city:TEXT,state:TEXT,zip:Integer";
Now I have the inputStream parsed for file 2 and here is where I am at:
BufferedReader file2InputStreamBuffered = new BufferedReader("file 2");
Now, how to map the value to my Java objects in an elegant way? (With 3rd party tools or any common libs)
You need to build CsvSchema for your format file and after that use it for reading CSV file. Code could look like below:
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.MappingIterator;
import com.fasterxml.jackson.dataformat.csv.CsvMapper;
import com.fasterxml.jackson.dataformat.csv.CsvSchema;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.util.HashMap;
import java.util.Map;
public class CsvApp {
public static void main(String[] args) throws Exception {
File formatFile = new File("./resource/test.format").getAbsoluteFile();
File csvFile = new File("./resource/test.csv").getAbsoluteFile();
CsvSchema schema = createSchemaForFormat(formatFile);
CsvMapper csvMapper = new CsvMapper();
MappingIterator<Record> rows = csvMapper.readerFor(Record.class).with(schema).readValues(csvFile);
RecordCollection recordCollection = new RecordCollection(100);
while (rows.hasNext()) {
recordCollection.add(rows.next());
}
recordCollection.getRecords().forEach((k, v) -> {
System.out.println(k + " => " + v);
});
}
private static CsvSchema createSchemaForFormat(File formatFile) throws IOException {
String content = String.join("", Files.readAllLines(formatFile.toPath()));
String[] columns = content.split(",");
CsvSchema.Builder builder = CsvSchema.builder();
for (String column : columns) {
String[] columnData = column.split(":");
String name = columnData[0];
String type = columnData[1];
builder.addColumn(name, "Integer".equalsIgnoreCase(type) ? CsvSchema.ColumnType.NUMBER : CsvSchema.ColumnType.STRING);
}
return builder.build();
}
}
class RecordCollection {
private final Map<String, Record> records;
RecordCollection(int expectedSize) {
this.records = new HashMap<>(expectedSize);
}
public void add(Record record) {
this.records.put(record.getId(), record);
}
public Map<String, Record> getRecords() {
return records;
}
}
class Record {
private final String id;
private final String name;
private final String city;
private final String state;
private final Integer zip;
#JsonCreator
public Record(
#JsonProperty("id") String id,
#JsonProperty("name") String name,
#JsonProperty("city") String city,
#JsonProperty("state") String state,
#JsonProperty("zip") Integer zip) {
this.id = id;
this.name = name;
this.city = city;
this.state = state;
this.zip = zip;
}
public String getId() {
return id;
}
public String getName() {
return name;
}
public String getCity() {
return city;
}
public String getState() {
return state;
}
public Integer getZip() {
return zip;
}
#Override
public String toString() {
return "Record{" +
"name='" + name + '\'' +
", city='" + city + '\'' +
", state='" + state + '\'' +
", zip=" + zip +
'}';
}
}
Above code prints:
12312 => Record{name=' ', city='seattle ', state=' ', zip=10000}
21312 => Record{name='jim ', city='boston ', state='MA', zip=10000}
11212 => Record{name='karen', city='new york', state='NY', zip=10000}
In above case Record class is tied with configuration file which defines columns. If format of CSV is not dynamic you can build schema in Java class without reading it from file. If it is dynamic, instead of Record class you could store Map<String, Object> type which could handle dynamic columns.
For more information take a look od Jackson CSV documentation.

GSON: Using fromJson to convert a list of dictionaries

I am currently trying to extract the name 'Best I Ever Had' from the last.fm API shown below using GSON but having difficulty with it constantly returning a null value.
******************************EDIT*****************************
Here is the JSON, with tracks being a list of dictionaries, one for each song name:
{
toptracks: {
track: [
{
name: "Best I Ever Had"
}
]
}
}
Using http://www.jsonschema2pojo.org/ I have created the following classes:
TrackName.java
package com.webservice1;
import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;
public class TrackName {
#SerializedName("toptracks")
#Expose
private Toptracks toptracks;
public Toptracks getToptracks() {
return toptracks;
}
public void setToptracks(Toptracks toptracks) {
this.toptracks = toptracks;
}
public TrackName withToptracks(Toptracks toptracks) {
this.toptracks = toptracks;
return this;
}
}
Toptracks.java
package com.webservice1;
import java.util.List;
import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;
public class Toptracks {
#SerializedName("track")
#Expose
private List<Track> track = null;
public List<Track> getTrack() {
return track;
}
public void setTrack(List<Track> track) {
this.track = track;
}
public Toptracks withTrack(List<Track> track) {
this.track = track;
return this;
}
}
and finally Track.java
package com.webservice1;
import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;
public class Track {
#SerializedName("name")
#Expose
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Track withName(String name) {
this.name = name;
return this;
}
}
And here is my code using fromJson method where reply contains the whole JSON and is constantly 'Track Name com.webservice1.Toptracks#1b9e1916'
String reply;
reply = reader.readLine();
Gson gson = new Gson();
TrackName response = gson.fromJson(reply, TrackName.class);
System.out.println("Track Name " + response.getToptracks());
Any help would be appreciated!!
You can do this using Jackson and I'm sure there are settings to ignore missing fields etc...
Here is the maven dependency for the library
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.2</version>
</dependency>
String json = "{\n"
+ " \"toptracks\" : {\n"
+ " \"track\": [{\n"
+ " \"name\": \"Best I Ever Had\",\n"
+ " \"playcount\": \"3551414\",\n"
+ " \"listeners\": \"1058277\",\n"
+ " \"mbid\": \"00bde944-7562-446f-ad0f-3d4bdc86b69f\",\n"
+ " \"url\": \"https://www.last.fm/music/Drake/_/Best+I+Ever+Had\",\n"
+ " \"streamable\": \"0\",\n"
+ " \"artist\": {\n"
+ " \"name\": \"Drake\",\n"
+ " \"mbid\": \"b49b81cc-d5b7-4bdd-aadb-385df8de69a6\",\n"
+ " \"url\": \"https://www.last.fm/music/Drake\"\n"
+ " }\n"
+ " }]\n"
+ "}\n"
+ "}";
ObjectMapper mapper = new ObjectMapper();
TopTracks track = mapper.readValue(json, TopTracks.class);
List<Object> trackList = (List<Object>)track.toptracks.get("track");
Map<String, Object> trackMap = (Map<String, Object>) trackList.get(0);
System.out.println(trackMap.get("name"));
public class TopTracks {
public Map<String, Object> toptracks = new HashMap<>();
}
Output:
--- exec-maven-plugin:1.2.1:exec (default-cli) # MVN ---
Best I Ever Had
------------------------------------------------------------------------
BUILD SUCCESS

validate json payload in spring boot rest to throw error if unknown property or empty property has been sent as part of json payload

JSON Request:
{
"notificationType" : "ISSUER_OTP1ee2asasa",
"content" : "hi fff this is fff template content for SBI email good and mobile dfdfdfd and remaining balance is 333 and your name is hahaha.",
"medium" : "EMAIL",
"asa":"ddddd",
"":""
}
POJO:
package com.innoviti.notification.model;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
#Document(collection = "NotificationTemplate")
#JsonIgnoreProperties(ignoreUnknown=false)
public class NotificationTemplate {
#JsonCreator
public NotificationTemplate(#JsonProperty(value="notificationType",required=true)String notificationType,
#JsonProperty(value="content",required=true)String content, #JsonProperty(value="medium",required=true)String medium) {
super();
this.notificationType = notificationType;
this.content = content;
this.medium = medium;
}
#Override
public String toString() {
return "NotificationTemplate [id=" + id + ", templateId=" + templateId + ", notificationType="
+ notificationType + ", content=" + content + ", medium=" + medium + "]";
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
#Id
private String id;
private String templateId;
public String getTemplateId() {
return templateId;
}
public void setTemplateId(String templateId) {
this.templateId = templateId;
}
private String notificationType;
private String content;
private String medium;
public String getMedium() {
return medium;
}
public void setMedium(String medium) {
this.medium = medium;
}
public String getNotificationType() {
return notificationType;
}
public void setNotificationType(String notificationType) {
this.notificationType = notificationType;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
}
Controller where payload is posted.
#PostMapping(value = "/config", consumes = MediaType.APPLICATION_JSON_VALUE,
produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<NotificationTemplate> configureTemplate(
#Valid #RequestBody NotificationTemplate notificationTemplate) {
NotificationTemplate notificationTemplatePersisted = null;
logger.info(
"Printing payload of template on server side" + ">>>" + notificationTemplate.toString());
try {
validatePayLoad(notificationTemplate);
notificationTemplatePersisted =
notificationTemplateService.createNotificationTemplate(notificationTemplate);
} catch (Exception de) {
logger.info(String.format("Error in saving template", de.getMessage()));
throw new RequestNotCompletedException(de.getLocalizedMessage());
}
return new ResponseEntity<NotificationTemplate>(notificationTemplatePersisted,
HttpStatus.CREATED);
}
Question:How do I validate that an uknown property has been sent as part of payload.In Existing implementation,#RequestBody maps the json without any issue.I want to throw error or validate payload if incoming json is not confirming exactly to POJO.For e.g in payload example i gave,I want to be able to throw error saying that asa is not recognized property
The Jackson property that controls this behaviour is FAIL_ON_UNKNOWN_PROPERTIES. This needs to be true in your case, to get the behaviour you describe.
It seems that since spring boot 1.2 this is set to false by default.
To set it to true add this line to your application.properties file:
spring.jackson.deserialization.fail-on-unknown-properties=true
And then you will get a JsonMappingException when there are extraneous properties in a JSON payload
One can add this class int their project and it would throw an exception if json is mismatched to the pojo class properties.
#Configuration
public class Config implements InitializingBean {
#Autowired
private RequestMappingHandlerAdapter converter;
#Override
public void afterPropertiesSet() throws Exception {
configureJacksonToFailOnUnknownProperties();
}
private void configureJacksonToFailOnUnknownProperties() {
MappingJackson2HttpMessageConverter httpMessageConverter = converter.getMessageConverters().stream()
.filter(mc -> mc.getClass()
.equals(MappingJackson2HttpMessageConverter.class))
.map(mc -> (MappingJackson2HttpMessageConverter) mc)
.findFirst()
.get();
httpMessageConverter.getObjectMapper().enable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
}
}

Ignore absent property when mapping json response

I have a form that should return list of customers.
This form should behave differently in two case:
User starts the research using only "surname"
User starts the research using surname AND name
In the first case the json response has less fields than the response in the second case so I have to ignore all these fields.
I've tried using #JsonInclude(JsonInclude.Include.NON_ABSENT), #JsonInclude(JsonInclude.Include.NON_EMPTY) and #JsonInclude(JsonInclude.Include.NON_NULL) but with each and everyone of these the error returned is always the same:
java.lang.Exception: Could not write content: (was java.lang.NullPointerException) (through reference chain: it.gruppoitas.itasacquire.pojo.Cliente["DATA_NASCITA"]); nested exception is com.fasterxml.jackson.databind.JsonMappingException: (was java.lang.NullPointerException) (through reference chain: it.gruppoitas.itasacquire.pojo.Cliente["DATA_NASCITA"])
This is the pojo Cliente:
package it.gruppoitas.itasacquire.pojo;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
#JsonInclude(JsonInclude.Include.NON_ABSENT)
public class Cliente {
#JsonProperty("TIPO_PERSONA")
private String tipoPersona;
#JsonProperty("PRO_CLIE")
private String proClie;
#JsonProperty("CODICE_FISCALE")
private String codiceFiscale;
#JsonProperty("DATA_NASCITA")
private String dataNascita;
#JsonProperty("SESSO")
private String sesso;
#JsonProperty("NOME")
private String nome;
#JsonProperty("COGNOME")
private String cognome;
public String getTipoPersona() {
return tipoPersona;
}
public void setTipoPersona(String tipoPersona) {
this.tipoPersona = tipoPersona;
}
public String getProClie() {
return proClie;
}
public void setProClie(String proClie) {
this.proClie = proClie;
}
public String getCodiceFiscale() {
return codiceFiscale;
}
public void setCodiceFiscale(String codiceFiscale) {
this.codiceFiscale = codiceFiscale;
}
public String getDataNascita() {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.S");
Date data = null;
try {
data = sdf.parse(dataNascita);
dataNascita = new SimpleDateFormat("dd/MM/yyyy").format(data);
} catch (ParseException e) {
System.err.println(e);
}
return dataNascita;
}
public void setDataNascita(String dataNascita) {
this.dataNascita = dataNascita;
}
public String getSesso() {
return sesso;
}
public void setSesso(String sesso) {
this.sesso = sesso;
}
public String getNome() {
return nome;
}
public void setNome(String nome) {
this.nome = nome;
}
public String getCognome() {
return cognome;
}
public void setCognome(String cognome) {
this.cognome = cognome;
}
#Override
public String toString() {
return "Cliente [tipoPersona=" + tipoPersona + ", proClie=" + proClie + ", codiceFiscale=" + codiceFiscale + ", dataNascita="
+ dataNascita + ", sesso=" + sesso + ", nome=" + nome + ", cognome=" + cognome + "]";
}}
Any idea?
EDIT: this is an example of the json response structure in case 1
{
"TIPO_PERSONA" : "G",
"PRO_CLIE" : "123456789",
"CODICE_FISCALE" : "123456789",
"PARTITA_IVA" : "123456789",
"SESSO" : "S",
"COGNOME" : "CUSTOMER SRL"
}
And this is an example of the json response in case 2:
{
"TIPO_PERSONA" : "F",
"PRO_CLIE" : "123456789",
"CODICE_FISCALE" : "123456789",
"DATA_NASCITA" : "1969-09-07 00:00:00.0",
"SESSO" : "F",
"NOME" : "Foo",
"COGNOME" : "Fie"
}
As you can see there are less fields in case 1 and STS goes in full-panic mode...
You need to configure your object mapper not to fail on empty beans.
Here is a sample code since you didn't provide the creation of the ObjectMapper code yourself:
private ObjectMapper jacksonMapper = new ObjectMapper();
jacksonMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
jacksonMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
You can also use:
jacksonMapper.configure(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES,false);

Categories

Resources