I have a problem with CsvToBeanBuilder from opencsv. I have a class:
public class TransferNetworkRawData {
#CsvBindByName
private String jurisdiction;
#CsvBindByName
private String currency;
#CsvBindByName
private String transfernetwork;
#CsvBindByName
private String display;
public TransferNetworkRawData() {
}
// getters and setters
}
And a service (FYI: TransferNetworkDataFetcher is just an interface with one method getTransferNetworkData(). I am creating this class in such manner, because I want the data to be loaded on application startup without having to read the file every time a request for this data comes.
#Component
public class CsvTransferNetworkDataFetcher implements TransferNetworkDataFetcher {
private static final Logger LOGGER = LoggerFactory.getLogger(CsvTransferNetworkDataFetcher.class);
private static final char SEPARATOR = ';';
private List<TransferNetworkRawData> transferNetworkRawData = new ArrayList<>();
public CsvTransferNetworkDataFetcher(ResourceLoader resourceLoader) {
try {
Resource resource = resourceLoader.getResource("classpath:" + "csv/transferNetworks.csv");
CSVReader reader = new CSVReaderBuilder(new FileReader(resource.getFile())).build();
transferNetworkRawData = new CsvToBeanBuilder(reader)
.withType(TransferNetworkRawData.class)
.withSeparator(SEPARATOR)
.build()
.parse();
} catch (IOException exception) {
LOGGER.error("File not found! " + exception.getMessage());
}
}
#Override
public List<TransferNetworkRawData> getTransferNetworkData() {
return transferNetworkRawData;
}
}
The CSV file looks like this (the file has 28 sets of data + header, so 28 objects in general)
jurisdiction;currency;transfernetwork;display
US;AAVE;ETHEREUM;TRUE
US;BAT;ETHEREUM;TRUE
EU;BCAP;ETHEREUM;TRUE
And a test:
#SpringBootTest
class CsvTransferNetworkDataFetcherTest {
#Autowired
private CsvTransferNetworkDataFetcher transferNetworkDataFetcher;
#Test
void shouldReturnTransferNetworkList() {
List<TransferNetworkRawData> transferNetworkDataList = transferNetworkDataFetcher.getTransferNetworkData();
System.out.println(transferNetworkDataList); // just for breakpoint
}
}
Running this test shows some unexpected results, as getTransferNetworkData() is returning a list of 28 TransferNetworkRawData objects, but all the values (jurisdiction, currency, transfernetwork and display) in all those 28 objects are null, so it seems that the file is loaded correctly but the data isn't correctly read from it...
I have no idea what am I missing here...
I'm facing with exactly the same issue in my source code. I just change this annotation #CsvBindByName for #CsvBindByPosition(position = #)
Your new model should be:
public class TransferNetworkRawData {
#CsvBindByPosition(position = 0)
private String jurisdiction;
#CsvBindByPosition(position = 1)
private String currency;
#CsvBindByPosition(position = 2)
private String transfernetwork;
#CsvBindByPosition(position = 3)
private String display;
public TransferNetworkRawData() {
}
// getters and setters
}
Related
I'm doing an API that consumes an external API (https://swapi.dev/), but I'm receving this error when I try to return the JSON converted to Object.
I'm trying to solve this for almost 12 hours without any success x_x
Error while extracting response for type [class [Lcom.starwarsapi.filmsapi.model.FilmModel;] and content type [application/json]; nested exception is org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Cannot deserialize value of type `[Lcom.starwarsapi.filmsapi.model.FilmModel;` from Object value (token `JsonToken.START_OBJECT`);
FilmController:
#RestController
#RequestMapping("/films")
public class FilmController {
#Autowired
private FilmService filmService;
#GetMapping
public List<FilmModel> getAllFilms() {
List<FilmModel> response = filmService.getAllFilms();
return response;
}
FilmModel:
#Data
#JsonInclude(JsonInclude.Include.NON_NULL)
#JsonIgnoreProperties(ignoreUnknown = true)
public class FilmModel {
private FilmResultModel[] results;
}
FilmResultModel
#Data
#JsonInclude(JsonInclude.Include.NON_NULL)
#JsonIgnoreProperties(ignoreUnknown = true)
public class FilmResultModel {
private String title;
#JsonProperty("episode_id")
private Integer episodeId;
#JsonProperty("opening_crawl")
private String description;
private String director;
private String producer;
#JsonProperty("release_date")
private String releaseData;
private String[] characters;
private String[] planets;
private String[] starships;
private String[] vehicles;
private String[] species;
private String created;
private String edited;
private String url;
FilmService:
public interface FilmService {
public List<FilmModel> getAllFilms();
}
FilmServiceImpl:
#Service
public class FilmServiceImpl implements FilmService {
#Value("${external.api.url}")
private String filmBaseUrl;
#Autowired
private RestTemplate restTemplate;
#Override
public List<FilmModel> getAllFilms() {
FilmModel[] result = restTemplate.getForObject(filmBaseUrl, FilmModel[].class);
List<FilmModel> films = Arrays.asList(result);
System.out.println(films);
return films;
}
PS¹: external.api.url = https://swapi.dev/api/films/?format=json
PS²: When I return getAllFilms as String, the program works:
#Override
public String getAllFilms() {
String result = restTemplate.getForObject(filmBaseUrl, String.class);
System.out.println(result);
return result;
}
But I need it to return as an object because later I'll try to create a PUT method to change the description of the movie.
try using FilmModel result = restTemplate.getForObject(filmBaseUrl, FilmModel.class);
FilmModel is just the outer wrapper object and contains the list of films.
I would like to deserialize JSON of this structure:
{
"employee_pricing_type":"COMPUTE_BY_OWN_RATE",
"employee_rate":10,
"customer_pricing_type":"COMPUTE_BY_OWN_RATE",
"customer_rate":200
}
I have such POJO to create price setting from a HTTP request:
public class ObjectPricingSetting {
#JsonProperty("pricing_type") // describes output
private final ObjectPricingType pricingType;
#JsonProperty("own_rate") // describes output
private final BigDecimal ownRate;
public ObjectPricingSetting(final ObjectPricingType pricingType, final BigDecimal ownRate) {
AssertUtils.notNull(pricingType, "pricingType");
this.pricingType = pricingType;
if (ownRate != null) {
AssertUtils.isGtZero(ownRate, "ownRate");
this.ownRate = ownRate;
} else {
this.ownRate = null;
}
}
public ObjectPricingType getPricingType() {
return pricingType;
}
public BigDecimal getOwnRate() {
return ownRate;
}
}
this is DTO:
#JsonInclude(JsonInclude.Include.NON_NULL)
public class ObjectPricingCommand extends BaseDto<ObjectId> {
#JsonProperty(value = "employee_pricing_setting")
private ObjectPricingSetting employeePricingSetting;
#JsonProperty(value = "customer_pricing_setting")
private ObjectPricingSetting customerPricingSetting;
}
I would like to create these two instances of ObjectPricingSetting with #JsonCreator.
Q: How should I anotate #JsonProperty parameter in ObjectPricingSetting constructor to recognize what JSON value should use to create these two instances?
You can use #JsonUnwrapped with a prefix in your parent class:
#JsonInclude(JsonInclude.Include.NON_NULL)
public class ObjectPricingCommand extends BaseDto<ObjectId> {
#JsonUnwrapped(prefix = "employee_")
private ObjectPricingSetting employeePricingSetting;
#JsonUnwrapped(prefix = "customer_")
private ObjectPricingSetting customerPricingSetting;
}
Then you can use the normal #JsonCreator/#JsonProperty in your nested DTO, without the prefix:
public class ObjectPricingSetting {
#JsonCreator
public ObjectPricingSetting(
#JsonProperty("pricing_type") final ObjectPricingType pricingType,
#JsonProperty("rate") final BigDecimal ownRate) {
...
I tried to work with immutable objects in MongoDB and Lombok. I found a solution to my problem but it needs to write additional code from docs but I need to used Bson annotations and create a constructor where describes fields via annotations. But if I user #AllArgsConstructor catch exception: "Cannot find a public constructor for 'User'" because I can't use default constructor with final fields. I think i can customize CodecRegistry correctly and the example will work correctly but I couldn't find solution for it in docs and google and Stackoverflow.
Is there a way to solve this problem?
#Data
#Builder(builderClassName = "Builder")
#Value
#BsonDiscriminator
public class User {
private final ObjectId id;
private final String name;
private final String pass;
private final String login;
private final Role role;
#BsonCreator
public User(#BsonProperty("id") final ObjectId id,
#BsonProperty("name") final String name,
#BsonProperty("pass") final String pass,
#BsonProperty("login") final String login,
#BsonProperty("role") final Role role) {
this.id = id;
this.name = name;
this.pass = pass;
this.login = login;
this.role = role;
}
#AllArgsConstructor
public enum Role {
USER("USER"),
ADMIN("ADMIN"),
GUEST("GUEST");
#Getter
private String value;
}
public static class Builder {
}
}
Example for MongoDB where I create, save and then update users:
public class ExampleMongoDB {
public static void main(String[] args) {
final MongoClient mongoClient = MongoClients.create();
final MongoDatabase database = mongoClient.getDatabase("db");
database.drop();
final CodecRegistry pojoCodecRegistry = fromRegistries(MongoClientSettings.getDefaultCodecRegistry(),
fromProviders(PojoCodecProvider.builder().automatic(true).build()));
final MongoCollection<User> users = database.getCollection("users", User.class).withCodecRegistry(pojoCodecRegistry);
users.insertMany(new ExampleMongoDB().getRandomUsers());
System.out.println("Before updating:");
users.find(new Document("role", "ADMIN")).iterator().forEachRemaining(
System.out::println
);
System.out.println("After updating:");
users.updateMany(eq("role", "ADMIN"), set("role", "GUEST"));
users.find(new Document("role", "GUEST")).iterator().forEachRemaining(
System.out::println
);
}
public List<User> getRandomUsers() {
final ArrayList<User> users = new ArrayList<>();
for (int i = 0; i < 15; i++) {
users.add(
User.builder()
.login("log" + i)
.name("name" + i)
.pass("pass" + i)
.role(
(i % 2 == 0) ? User.Role.ADMIN : User.Role.USER
).build()
);
}
return users;
}
}
This should work (it worked for me):
#Builder(builderClassName = "Builder")
#Value
#AllArgsConstructor(onConstructor = #__(#BsonCreator))
#BsonDiscriminator
public class User {
#BsonId
private final ObjectId _id;
#BsonProperty("name")
private final String name;
#BsonProperty("pass")
private final String pass;
#BsonProperty("login")
private final String login;
#BsonProperty("role")
private final Role role;
}
Then in lombok.config add these (in your module/project directory):
lombok.addLombokGeneratedAnnotation=true
lombok.anyConstructor.addConstructorProperties=true
lombok.copyableAnnotations += org.bson.codecs.pojo.annotations.BsonProperty
lombok.copyableAnnotations += org.bson.codecs.pojo.annotations.BsonId
Also piece of advice, keep _id if you are going to use automatic conversion to POJOs using PojoCodec, which will save a lot of trouble.
I have a Spring mvc application, with a #RestController like such:
#RestController
#RequestMapping("levels")
public class LevelController {
private final GetLevelOneCount getLevelOneCount;
private final GetLevelTwoCount getLevelTwoCount;
private final GetLevelThreeCount getLevelThreeCount;
#Inject
public LevelController(GetLevelOneCount getLevelOneCount,
GetLevelTwoCount getLevelTwoCount,
GetLevelThreeCount getLevelThreeCount) {
this.getLevelOneCount = getLevelOneCount;
this.getLevelTwoCount = getLevelTwoCount;
this.getLevelThreeCount = getLevelThreeCount;
}
#GetMapping("/level1/{id}")
public LevelModel levelOne(#PathVariable String id) throws SQLException {
LevelModel levelOneModel = new LevelModel();
levelOneModel.setLevelQuery(getLevelOneCount.execute(id));
levelOneModel.setLevelDirQuery(getLevelOneCount.executeDir(id));
levelOneModel.setLevelDateQuery(getLevelOneCount.executeDate(id));
return levelOneModel;
}
my LevelModel is a POJO with private variables, now i wonder, if this can get serialized to propper JSON with private variables?
package com.pwc.tag.service.levels;
public class LevelModel {
private Long LevelQuery;
private Long LevelDirQuery;
private Long LevelDateQuery;
public Long getLevelQuery() {
return LevelQuery;
}
public void setLevelQuery(Long levelQuery) {
LevelQuery = levelQuery;
}
public Long getLevelDirQuery() {
return LevelDirQuery;
}
public void setLevelDirQuery(Long levelDirQuery) {
LevelDirQuery = levelDirQuery;
}
public Long getLevelDateQuery() {
return LevelDateQuery;
}
public void setLevelDateQuery(Long levelDateQuery) {
LevelDateQuery = levelDateQuery;
}
}
Yes, your object will be serialized to a proper JSON structure including the private field, because of the getters and setters.
If these fields should not be present in the output object, you can add the #JsonIgnore annotation to exclude them from the JSON structure.
P.S. the common approach is to start names of java properties with a lower case letter.
I have a Java class which has 2 List Object inside it and i am Json serializing the parent class.
#JsonSerialize
public class RequestSalesJson {
#JsonProperty("nonUniqueSalesList")
private List<SalesDataJson> getNonUniqueSalesDataJson;
#JsonProperty("uniqueSalesList")
private List<SalesDataJson> uniqueSalesDataJson;
public List<SalesDataJson> getGetNonUniqueSalesDataJson() {
return getNonUniqueSalesDataJson;
}
public void setGetNonUniqueSalesDataJson(List<SalesDataJson> getNonUniqueSalesDataJson) {
this.getNonUniqueSalesDataJson = getNonUniqueSalesDataJson;
}
public List<SalesDataJson> getUniqueSalesDataJson() {
return uniqueSalesDataJson;
}
public void setUniqueSalesDataJson(List<SalesDataJson> uniqueSalesDataJson) {
this.uniqueSalesDataJson = uniqueSalesDataJson;
}
}
SalesReturnJson.java
#JsonSerialize
public class SalesReturnJson {
#JsonProperty("starttime")
private String startTime;
#JsonProperty("pn")
private String partNumber;
#JsonProperty("so")
private String SalesOrderNumber;
#JsonProperty("wo")
private String workOrderNumber;
#JsonProperty("loc")
//other variables declared..
}
Controller.java :-
#RequestMapping(value = "/addAllSalesData",method = RequestMethod.POST)
public void addAllSalesData(#RequestBody RequestSalesJson requestSalesJsons){
log.info("POST : '/addSalesData'");
try{
System.out.print("In Controller "+requestSalesJsons.getUniqueSalesDataJson());
//salesService.processSalesData(requestSalesJsons);
}
catch(Exception e){
// return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(e.getMessage());
}
}
The value here is coming to be null.
Below is the json i am using :-
{ "uniqueSalesJson": [{"SO":4000955,"Part Number":"000","Locator":255638,"Lot Number":"P01-2059139","Reservation Quantity":2,"Status":"Released to warehouse","COE":"Fabrication","ORG":"P07","Start_Time":"2017-09-19 11:21:36"},{"SO":4000955,"Part Number":"000","Locator":255652,"Lot Number":"P01-2059140","Reservation Quantity":10,"Status":"Released to warehouse","COE":"Fabrication","ORG":"P07","Start_Time":"2017-09-19 11:21:36"}],"nonUniqueSalesJson":[{"SO":4000992,"Part Number":"1276M84G15","Locator":12345,"Lot Number":"P01-2344141","Reservation Quantity":6,"Status":"PACKED","COE":"Fabrication","ORG":"P07","Start_Time":"2017-09-19 11:21:36"},{"SO":4000992,"Part Number":"1276M84G15","Locator":12345,"Lot Number":"P01-2344141","Reservation Quantity":6,"Status":"PICKED","COE":"Fabrication","ORG":"P07","Start_Time":"2017-09-19 11:21:36"}]}
There are some issues in your code that let me doubt that your application compiles. First of all, rename the SalesReturnJson class to SalesDataJson.
Then check your #JsonProperty annotations. The value here must match exactly the property key in the Json String. Refactoring all this stuff will lead you to your root entity class:
#JsonSerialize
public class RequestSalesJson {
#JsonProperty("nonUniqueSalesJson")
private List<SalesDataJson> nonUniqueSalesDataJson;
#JsonProperty("uniqueSalesJson")
private List<SalesDataJson> uniqueSalesDataJson;
...
}
and your SalesDataJson class (missing a lot of attributes which the mapper ignores by configuration):
#JsonSerialize
public class SalesDataJson {
#JsonProperty("Start_Time")
private String startTime;
#JsonProperty("Part Number")
private String partNumber;
#JsonProperty("SO")
private String SalesOrderNumber;
}
This sample works as expected with the com.fasterxml.jackson.databind.ObjectMapper
Hope that helps!