Jackson Deserializing From CSV to Enum - java

I'm trying to deserialize from a football results csv to an enum using Jackson's dataformat library. This is the csv file (the sixth column is the one I'm interested in):
Egypt,Uruguay,GROUP,2,0,HOME
This is my enum class:
#JsonIgnoreProperties(ignoreUnknown = true)
public enum MatchOutcome {
HOME(3, 0),
DRAW(1, 1),
AWAY(0, 3),
HOME_ET(1, 1),
AWAY_ET(1, 1),
HOME_PENS(1, 1),
AWAY_PENS(1, 1);
private final Integer homePoints;
private final Integer awayPoints;
MatchOutcome(Integer homePoints, Integer awayPoints) {
this.homePoints = homePoints;
this.awayPoints = awayPoints;
}
public Integer homePoints() {
return this.homePoints;
}
public Integer awayPoints() {
return this.awayPoints;
}
}
And this is the main method:
public static void main(String[] args) throws Exception {
CsvSchema csvSchema = CsvSchema.builder()
.addColumn("HOME")
.addColumn("AWAY")
.addColumn("STAGE")
.addColumn("HOME_FULL_TIME")
.addColumn("AWAY_FULL_TIME")
.addColumn("MATCH_OUTCOME")
.build();
CsvMapper csvMapper = new CsvMapper();
File csvFile = new File("src/Resources/fixtureResult.csv");
MappingIterator<MatchOutcome> matchOutcomeIterator = csvMapper.readerFor(MatchOutcome.class).with(csvSchema)
.readValues(csvFile);
while (matchOutcomeIterator.hasNextValue()) {
MatchOutcome matchOutcome = matchOutcomeIterator.nextValue();
System.out.println(matchOutcomeIterator.toString());
}
}
I'm getting the following error:
Exception in thread "main" com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize instance of `com.football.Calculator.MatchOutcome` out of START_OBJECT token
Are my annotations wrong? Or do I need a toString method on my Enum?

I created a wrapper class and mapped to that in the end,
#JsonIgnoreProperties(ignoreUnknown = true)
public class MatchOutcomeWrapper {
public MatchOutcome matchOutcome;
public MatchOutcomeWrapper(#JsonProperty("MATCH_OUTCOME") MatchOutcome matchOutcome) {
this.matchOutcome = matchOutcome;
}
public MatchOutcome getMatchOutcome() {
return matchOutcome;
}
public String toString() {
return new ToStringBuilder(this, ToStringStyle.JSON_STYLE)
.append("matchOutcome", matchOutcome)
.toString();
}
}

Related

ModelMapper attribute fields with correlation names, in camel case

Error converting entity with source attribute name content in source fiels with camel case part
Example: in source model i heve a String field edgeId, and in target model i heve a field Long id, the match is true. This generate a exception, java.lang.NumberFormatException, how to ignore this match.
This is occurre with another attribute names
package br.com.combinado;
import org.modelmapper.ModelMapper;
public class TestModelMapper {
public static void main(String[] args) {
Target target = new Target();
target.setTesteBatataFrita("batataFrinta");
ModelMapper mapper = new ModelMapper();
Source source = mapper.map(target, Source.class);
System.out.println(source);
}
private static class Source {
private Long frita;
public Long getFrita() {
return frita;
}
public void setFrita(Long frita) {
this.frita = frita;
}
}
private static class Target {
private String testeBatataFrita;
public String getTesteBatataFrita() {
return testeBatataFrita;
}
public void setTesteBatataFrita(String testeBatataFrita) {
this.testeBatataFrita = testeBatataFrita;
}
}
}
I solved by adding a add a SourceNameTokenizer
mapper.getConfiguration().setSourceNameTokenizer(new NameTokenizer() {
public String[] tokenize(String name, NameableType nameableType) {
return new String[] { name };
}
});
Or
#Bean
public ModelMapper modelMapper() {
final ModelMapper mapper = new ModelMapper();
mapper.getConfiguration().setDestinationNameTokenizer((name, nameableType) -> new String[] { name });
mapper.getConfiguration().setSourceNameTokenizer((name, nameableType) -> new String[] { name });
return mapper;
}
Old question but in latest versions I found a way to solve it with
mapper.getConfiguration().setMatchingStrategy(MatchingStrategies.STRICT);

Determine appropriate class based on property existence when reading JSON

Assuming I have two types of responses from a service - positive and negative. They have JSON format.
When I use Jackson API to create the corresponding java objects I have to write the code like this:
import com.fasterxml.jackson.databind.ObjectMapper;
private ObjectMapper objectMapper = new ObjectMapper();
String json = ... // JSON received from a service
ServiceResponse response = objectMapper.readValue(json, ServiceResponse.class);
// Analyze the response
if (response instanceof PositiveServiceResponse) {
...
}
...
The problem is that both types of responses have nothing in common. There is no "type" property.
Otherwise I could use Vehicle-Car-Truck example from https://www.baeldung.com/jackson-inheritance
Sample positive response:
{
"city": "c",
"street": "s"
}
Negative response:
{
"errorMsg": "error",
"errorCode": "572"
}
The NegativeServiceResponse contains errorCode property. Is it possible to "tell" the Jackson API to create and instance of NegativeServiceResponse class if JSON contains the property, and an instance of PositiveServiceResponse otherwise?
Of course I can parse the JSON using low-level mechanisms (JsonNode etc.), but is there a more elegant solution?
It is possible to do. You need to create a Container class for both positive and negative response. See below, let me know if it works.
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
class Solution {
public static void main(String[] args) throws IOException {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.enableDefaultTyping();
Positive positive = new Positive(1);
Negative negative = new Negative(-1);
Response response = new Response(positive, negative);
String json = objectMapper.writeValueAsString(response);
Response output = objectMapper.readValue(json, Response.class);
System.out.println(output.negative);
System.out.println(output.positive);
//practically, when you have only either positive or negative
Response response2 = new Response();
response2.setPositive(positive);
String json2 = objectMapper.writeValueAsString(response2);
//or String json2 = "{\"positive\":{\"code\":1}};
System.out.println(json2);
Response output2 = objectMapper.readValue(json2, Response.class);
System.out.println(output2.positive);
System.out.println(output2.negative);
}
}
Output:
Negative{otherCode=-1}
Positive{code=1}
{"positive":{"code":1},"negative":null}
Positive{code=1}
null
Response classes:
class Response {
Positive positive;
Negative negative;
public Response() {
}
public Response(Positive positive, Negative negative) {
this.positive = positive;
this.negative = negative;
}
public boolean isPositive() {
return positive != null;
}
public void setPositive(Positive positive) {
this.positive = positive;
}
public void setNegative(Negative negative) {
this.negative = negative;
}
public Positive getPositive() {
return positive;
}
public Negative getNegative() {
return negative;
}
}
class Positive {
private int code;
public Positive() {
}
public Positive(int code) {
this.code = code;
}
public int getCode() {
return code;
}
#Override
public String toString() {
return "Positive{" +
"code=" + code +
'}';
}
}
class Negative {
private int otherCode;
public Negative() {
}
public Negative(int otherCode) {
this.otherCode = otherCode;
}
public int getOtherCode() {
return otherCode;
}
#Override
public String toString() {
return "Negative{" +
"otherCode=" + otherCode +
'}';
}
}
Assuming you have class hierarhy like following
class ServiceResponse {}
class PositiveServiceResponse extends ServiceResponse { public String city; public String street; }
class NegativeServiceResponse extends ServiceResponse { public String errorMsg; public String errorCode; }
Then you create deserializer:
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
class ServiceResponseDeserializer extends StdDeserializer<ServiceResponse> {
protected ServiceResponseDeserializer() {
super(ServiceResponse.class);
}
#Override
public ServiceResponse deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
ObjectMapper mapper = (ObjectMapper) p.getCodec();
ObjectNode root = mapper.readTree(p);
if (root.get("errorMsg") != null) { // found negative token
NegativeServiceResponse result = new NegativeServiceResponse();
result.errorCode = root.get("errorCode").asText();
result.errorMsg = root.get("errorMsg").asText();
return result;
// OR (shorter but less efficient due to object-back-to-string conversion)
return mapper.readValue(root.toString(), NegativeServiceResponse.class);
} else {
return mapper.readValue(root.toString(), PositiveServiceResponse.class);
}
}
}
and use like following
#Test
public void testJackson() throws IOException {
SimpleModule module = new SimpleModule("ServiceResponseModule")
.addDeserializer(ServiceResponse.class, new ServiceResponseDeserializer());
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(module);
ServiceResponse positive = mapper.readValue(
"{ \"city\": \"c\", \"street\": \"s\" }", ServiceResponse.class);
assertTrue(positive instanceof PositiveServiceResponse);
ServiceResponse negative = mapper.readValue(
"{ \"errorMsg\": \"error\", \"errorCode\": \"572\" }", ServiceResponse.class);
assertTrue(negative instanceof NegativeServiceResponse);
}

Get api param from json file java with arrays

I have a json a file that doesnt contain only my api i am new to json and trying to get my api parameters from the file
"operators": {
"tez" : {
"api": "www.my-tour.com/search/getResult",
"parameters": [
{
"country": "Canada",
"queryParameters": {
"priceMin": ["0"],
"priceMax":["150000"],
"currency":["5561"],
"nightsMin":[1,2,3,4,5,6,7,8,9,10,11,12,13],
"nightsMax":[1,2,3,4,5,6,7,8,9,10,11,12,13]
In my app operator is just simple the company that owns the api so i have many operators so "tez" is the name of the company and below is its api and param
#Override
public JsonObject fetchData(String url) {
Calendar calendar = Calendar.getInstance();
calendar.setTime(new Date());
calendar.add(Calendar.DAY_OF_MONTH, 20);
for (int i = 1; i <= 180; i++) {
String formattedDate = dateFormat.format(calendar);
url += "&after=" + formattedDate + "&before=" + formattedDate;
// how can i get the api iteratively to get all api param
JsonObject json = new JsonObject().getJsonObject("tez");
// TODO call eternal API here
JSONParser parser = new JSONParser();
JsonObject a = null;
try {
FileReader fileReader = new FileReader("\\home\\user\\MyProjects\\MicroserviceBoilerPlate\\src\\config\\local_file.json");
a = (JsonObject) parser.parse(fileReader);
} catch (Exception e1) {
e1.printStackTrace();
}
This above is what i am came up with but its not correct im not able to access the json file and how can i iterate through the parameters so i can add them to the api
www.my-tour.com/search/getResult?priceMin=0&priceMax=150000&currency=+value &nightsMin= + value &nightsMax=+values etc
Note: This is a vertx app and i am using JsonObject and other Json specific api's
You could just make an object of the JSON properties in the file
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import java.util.List;
#JsonIgnoreProperties(ignoreUnknown = true)
public class TezModel {
public Operators operators;
public String getApi() {
return operators.tez.api;
}
public List<String> getPriceMin() {
return operators.tez.parameters.get(0).queryParameters.priceMin;
}
public List<String> getPriceMax() {
return operators.tez.parameters.get(0).queryParameters.priceMax;
}
public List<String> getCurrency() {
return operators.tez.parameters.get(0).queryParameters.currency;
}
public List<Integer> getNightsMin() {
return operators.tez.parameters.get(0).queryParameters.nightsMin;
}
public List<Integer> getNightsMax() {
return operators.tez.parameters.get(0).queryParameters.nightsMax;
}
#JsonIgnoreProperties(ignoreUnknown = true)
public static class Operators {
public Tez tez;
}
#JsonIgnoreProperties(ignoreUnknown = true)
public static class Tez {
public String api;
public List<Parameters> parameters;
}
#JsonIgnoreProperties(ignoreUnknown = true)
public static class Parameters {
public String country;
public QueryParameters queryParameters;
}
#JsonIgnoreProperties(ignoreUnknown = true)
public static class QueryParameters {
public List<String> priceMin;
public List<String> priceMax;
public List<String> currency;
public List<Integer> nightsMin;
public List<Integer> nightsMax;
}
}
And then you could add your parameters to a string using jackson databind
public static void main(String[] args) throws IOException {
ObjectMapper mapper = new ObjectMapper();
TezModel tezModel = mapper.readValue(new File("local_file.json"), TezModel.class);
String api = tezModel.getApi()+ "+priceMin="
+ tezModel.getPriceMin().get(0)
+ "&priceMax=" + tezModel.getPriceMax().get(0)
+ "&currency=+" + tezModel.getCurrency().get(0)
+ "nightsMin=" + tezModel.getNightsMin().get(0)
+ "nightsMax=" + tezModel.getNightsMax().get(0);
System.out.println(api);
}

Jackson - how to get view-dependant CsvSchema?

I am trying to convert my POJO into 2 different CSV representations.
My POJO:
#NoArgsConstructor
#AllArgsConstructor
public static class Example {
#JsonView(View.Public.class)
private String a;
#JsonView(View.Public.class)
private String b;
#JsonView(View.Internal.class)
private String c;
#JsonView(View.Internal.class)
private String d;
public static final class View {
interface Public {}
interface Internal extends Public {}
}
}
Public view exposed fields a and b, and Internal view exposes all fields.
The problem is that if I construct the ObjectWriter with .writerWithSchemaFor(Example.class) all my fields are included but ignored as defined by the view. ObjectWriter will create the schema as defined by the Example.class but if I apply .withView it will only hide the fields, not ignore them.
This means that I must construct the schema manually.
Tests:
#Test
public void testJson() throws JsonProcessingException {
final ObjectMapper mapper = new ObjectMapper();
final Example example = new Example("1", "2", "3", "4");
final String result = mapper.writerWithView(Example.View.Public.class).writeValueAsString(example);
System.out.println(result); // {"a":"1","b":"2"}
}
#Test
public void testCsv() throws JsonProcessingException {
final CsvMapper mapper = new CsvMapper();
final Example example = new Example("1", "2", "3", "4");
final String result = mapper.writerWithSchemaFor(Example.class).withView(Example.View.Public.class).writeValueAsString(example);
System.out.println(result); // 1,2,,
}
#Test
public void testCsvWithCustomSchema() throws JsonProcessingException {
final CsvMapper mapper = new CsvMapper();
CsvSchema schema = CsvSchema.builder()
.addColumn("a")
.addColumn("b")
.build();
final Example example = new Example("1", "2", "3", "4");
final String result = mapper.writer().with(schema).withView(Example.View.Public.class).writeValueAsString(example);
System.out.println(result); // 1,2
}
testCsv test has 4 fields, but 2 are excluded. testCsvWithCustomSchema test has only the fields I want.
Is there a way to get CsvSchema that will match my #JsonView without having to construct it myself?
Here is a solution I did with reflection, I am not really happy with it since it is still "manually" building the schema.
This solution is also bad since it ignores mapper configuration like MapperFeature.DEFAULT_VIEW_INCLUSION.
This seems like doing something that should be already available from the library.
#AllArgsConstructor
public class GenericPojoCsvSchemaBuilder {
public CsvSchema build(final Class<?> type) {
return build(type, null);
}
public CsvSchema build(final Class<?> type, final Class<?> view) {
return build(CsvSchema.builder(), type, view);
}
public CsvSchema build(final CsvSchema.Builder builder, final Class<?> type) {
return build(builder, type, null);
}
public CsvSchema build(final CsvSchema.Builder builder, final Class<?> type, final Class<?> view) {
final JsonPropertyOrder propertyOrder = type.getAnnotation(JsonPropertyOrder.class);
final List<Field> fieldsForView;
// DO NOT use Arrays.asList because it uses an internal fixed length implementation which cannot use .removeAll (throws UnsupportedOperationException)
final List<Field> unorderedFields = Arrays.stream(type.getDeclaredFields()).collect(Collectors.toList());
if (propertyOrder != null && propertyOrder.value().length > 0) {
final List<Field> orderedFields = Arrays.stream(propertyOrder.value()).map(s -> {
try {
return type.getDeclaredField(s);
} catch (final NoSuchFieldException e) {
throw new IllegalArgumentException(e);
}
}).collect(Collectors.toList());
if (propertyOrder.value().length < type.getDeclaredFields().length) {
unorderedFields.removeAll(orderedFields);
orderedFields.addAll(unorderedFields);
}
fieldsForView = getJsonViewFields(orderedFields, view);
} else {
fieldsForView = getJsonViewFields(unorderedFields ,view);
}
final JsonIgnoreFieldFilter ignoreFieldFilter = new JsonIgnoreFieldFilter(type.getDeclaredAnnotation(JsonIgnoreProperties.class));
fieldsForView.forEach(field -> {
if (ignoreFieldFilter.matches(field)) {
builder.addColumn(field.getName());
}
});
return builder.build();
}
private List<Field> getJsonViewFields(final List<Field> fields, final Class<?> view) {
if (view == null) {
return fields;
}
return fields.stream()
.filter(field -> {
final JsonView jsonView = field.getAnnotation(JsonView.class);
return jsonView != null && Arrays.stream(jsonView.value()).anyMatch(candidate -> candidate.isAssignableFrom(view));
})
.collect(Collectors.toList());
}
private class JsonIgnoreFieldFilter implements ReflectionUtils.FieldFilter {
private final List<String> fieldNames;
public JsonIgnoreFieldFilter(final JsonIgnoreProperties jsonIgnoreProperties) {
if (jsonIgnoreProperties != null) {
fieldNames = Arrays.asList(jsonIgnoreProperties.value());
} else {
fieldNames = null;
}
}
#Override
public boolean matches(final Field field) {
if (fieldNames != null && fieldNames.contains(field.getName())) {
return false;
}
final JsonIgnore jsonIgnore = field.getDeclaredAnnotation(JsonIgnore.class);
return jsonIgnore == null || !jsonIgnore.value();
}
}
}

Jersey REST Service Output Format

I need to format the output (xml) of a restful service using Jersey according to following scenario
I have a class with key value pair as follows.
#XmlRootElement(name="columnValues")
public class KeyValueDTO {
private String key;
private String val;
#XmlElement(name="column")
public String getKey() {
return key;
}
#XmlElement(name="value")
public String getVal() {
return val;
}
}
Suppose I have list like this which is returned by rest service:
List<KeyValueDTO> mylist = new ArrayList<KeyValueDTO>();
KeyValueDTO dto1 = new KeyValueDTO();
dto1.key = "Name";
dto1.val = "alex";
KeyValueDTO dto2 = new KeyValueDTO();
dto2.key = "Age";
dto2.val = 23
mylist.add(dto1);
mylist.add(dt02);
And I want to generate the output as follow
<Name>alex</Name>
<Age>20</Age>
But currently it is giving following output
<column>Name</column>
<value>alex</column>
<column>Age</column>
<value>20</column>
Can anyone let me know how to achieve this?
You could try using an XmlAdapter:
public class KeyValueAdapter extends XmlAdapter<String, List<KeyValueDTO>> {
#Override
public List<KeyValueDTO> unmarshal(String v) throws Exception {
// Needs implementation
return null;
}
#Override
public String marshal(List<KeyValueDTO> vs) throws Exception {
StringBuffer buffer = new StringBuffer();
for (KeyValueDTO v: vs) {
buffer.append(String.format("<%s>%s</%1$s>", v.key, v.val));
}
return buffer.toString();
}
}
And then add that adapter to your bean:
#XmlRootElement
public static class Wrapper {
#XmlJavaTypeAdapter(KeyValueAdapter.class)
List<KeyValueDTO> dtos;
}

Categories

Resources