De/serializing an enumerator type with Jackson - jersey

I have an entity I am serializing into JSON and also deserailizing from JSON:
public class Item {
#JsonProperty private Status status;
....
}
The Status enumeration looks like:
public enum Status {
NEW,
ACTIVE,
PENDING,
ERROR;
}
I want the annotated enumeration type to serialize to, for example, {status: "NEW"} and the same to deserialize into the enumeration type. Is there a separate annotation for this?

You need some additional methods in your enum:
#Override
#JsonValue
public String toString()
{
return super.toString().toUpperCase(Locale.ENGLISH);
}
#JsonCreator
public static Status fromString(final String status)
{
if (status == null)
{
return null;
}
try
{
return valueOf(status.toUpperCase(Locale.ENGLISH));
}
catch (IllegalArgumentException iae)
{
System.err.println("Invalid status");
}
}
These also ensure that your status conversion is case-insensitive (so a status of 'new' would still convert to the correct enum).

Related

How do I add a Type to a graphql-java-annotations project?

The documentation for graphql-java-annotations doesn't do such a great job of telling me how to add a Custom Scalar to my schema: https://github.com/Enigmatis/graphql-java-annotations/tree/v8.0.1#annotations-schema-creator
What I need is to create some 'scalar Date' in the Schema. It is unclear how to do this with the AnnotationsSchemaCreator builder thing.
GraphQLSchema schema = AnnotationsSchemaCreator.newAnnotationsSchema()
.query(Query.class) // to create you query object
.mutation(Mutation.class) // to create your mutation object
.subscription(Subscription.class) // to create your subscription object
.directive(UpperDirective.class) // to create a directive
.additionalType(AdditionalType.class) // to create some additional type and add it to the schema
.typeFunction(CustomType.class) // to add a typefunction
.setAlwaysPrettify(true) // to set the global prettifier of field names (removes get/set/is prefixes from names)
.setRelay(customRelay) // to add a custom relay object
.build();
The docs give me just that. Is a typeFunction what I need here? Do I have to first get the graphql-java "Custom Scalar" stuff set up and put that into the typeFunction?
What's happening right now is that my graphql-java-annotations Types which need the Date type...
public abstract class BasePart {
#GraphQLField
#GraphQLNonNull
#JsonIgnore
public String id;
...
#GraphQLField
public Date createdOn;
...
}
Get into the Schema without the Date scalar defined so the GraphiQL UI is rejecting it with errors like...
Error: Date fields must be an object with field names as keys or a function which returns such an object.
at invariant (http://localhost.blueorigin.com:8080/webjars/graphiql/0.10.1/graphiql.min.js:13:12678)
at defineFieldMap (http://localhost.blueorigin.com:8080/webjars/graphiql/0.10.1/graphiql.min.js:14:16395)
at e.getFields (http://localhost.blueorigin.com:8080/webjars/graphiql/0.10.1/graphiql.min.js:14:22028)
at http://localhost.blueorigin.com:8080/webjars/graphiql/0.10.1/graphiql.min.js:15:22055
at typeMapReducer (http://localhost.blueorigin.com:8080/webjars/graphiql/0.10.1/graphiql.min.js:15:22227)
at http://localhost.blueorigin.com:8080/webjars/graphiql/0.10.1/graphiql.min.js:15:22200
at Array.forEach (<anonymous>)
at http://localhost.blueorigin.com:8080/webjars/graphiql/0.10.1/graphiql.min.js:15:22082
at typeMapReducer (http://localhost.blueorigin.com:8080/webjars/graphiql/0.10.1/graphiql.min.js:15:22227)
at typeMapReducer (http://localhost.blueorigin.com:8080/webjars/graphiql/0.10.1/graphiql.min.js:15:21564)
I'm trying to figure out how to get that information into the, what, AnnotationsSchemaCreator.newAnnotationsSchema() builder?
How do you add a Custom Scalar to a graphql-java-annotations project?
The TypeFunction is the key. You pass the TypeFunction when you are building the Schema with the AnnotationsSchemaCreator. The following code effectively got scalar Date into the service's GraphQL Schema
graphQLSchema = AnnotationsSchemaCreator.newAnnotationsSchema()
.query(QuerySchema.class)
.setAlwaysPrettify(true)
.typeFunction(new MyDateTypeFunction()) // <-- This got scalar Date onto the schema
.build();
The TypeFunction itself realizes the support for the scalar Date.
public class MyDateTypeFunction implements TypeFunction {
#Override
public boolean canBuildType(Class<?> clazz, AnnotatedType annotatedType) {
return clazz == java.util.Date.class;
}
#Override
public GraphQLType buildType(
boolean b,
Class<?> clazz,
AnnotatedType annotatedType,
ProcessingElementsContainer processingElementsContainer) {
return MY_DATE;
}
public static final GraphQLScalarType MY_DATE = GraphQLScalarType
.newScalar()
.name("Date")
.description("Coerce java.util.Date to/from a String representation of the long value of getTime().")
.coercing(
new Coercing() {
#Override
public Object serialize(Object dataFetcherResult) throws CoercingSerializeException {
if (dataFetcherResult instanceof Date) {
final String result = String.format("%d", ((Date) dataFetcherResult).getTime());
return result;
}
final String message =
String.format("Expected type java.util.Date but found %s", typeName(dataFetcherResult));
throw new CoercingSerializeException(message);
}
#Override
public Object parseValue(Object input) throws CoercingParseValueException {
if (input instanceof String) {
try {
return stringToDate((String) input);
} catch (NumberFormatException nfe) {
final String message = String.format("NumberFormatException %s", nfe.getMessage());
throw new CoercingParseValueException(message);
}
}
final String message = String.format("Unable to parseValue %s to a java.util.Date", input);
throw new CoercingParseValueException(message);
}
#Override
public Object parseLiteral(Object input) throws CoercingParseLiteralException {
if (input instanceof StringValue) {
try {
final String inputStringValue = ((StringValue) input).getValue();
return stringToDate(inputStringValue);
} catch (NumberFormatException nfe) {
final String message = String.format("NumberFormatException %s", nfe.getMessage());
throw new CoercingParseLiteralException(message);
}
}
final String message = String.format("Unable to parseLiteral %s to a java.util.Date", input);
throw new CoercingParseLiteralException(message);
}
}
)
.build();
public static Date stringToDate(String input) throws NumberFormatException {
final long inputAsLong = Long.parseLong(input);
return new Date(inputAsLong);
}
public static String typeName(Object input) {
return input == null ? "null" : input.getClass().getName();
}
}
Note that I'm not recommending that you represent java.util.Date as the String value of the long getTime(), java.time.Instant's ISO-8601 is so much more readable, but my service needed this string value and this is how I got it into a graphql-java-annotation's project schema.

enforce enum serialization with a spring jpa projection?

Say I have the following JPA entity, with an enum field ON/OFF mapped to an SQL enum("on", "off").
#Entity
public class Process {
#Id
private Long Id;
#Convert(converter = StatusConverter.class)
private Status status;
// getter/setter omitted
}
public enum Status {
ON("on"),
OFF("off");
private final String status;
Status(String status) {
this.status = status;
}
// JSON (de)serialization
#JsonCreator
public static Status decode(String status) {
return valueOf(status.toUpperCase());
}
#JsonValue
public getStatus() {
return status;
}
// DAO layer conversion
public String toDatabaseColumn() {
return this.name().toLowerCase();
}
}
#Converter
public class StatusConverter implements AttributeConverter<Status, String> {
#Override
public String convertToDatabaseColumn(Status attribute) {
return attribute.toDatabaseColumn();
}
#Override
public Status convertToEntityAttribute(String dbData) {
return Status.decode(dbData);
}
}
// Spring JPA projection
public interface ProcessSummary {
String getStatus();
}
// a minimalist JPA repository
public interface ProcessRepository extends Repository<Process, Long> {
<T> T findById(Long id, Class<T> type;
}
If I use repository.findById(1L, Process.class) in a REST controller, both the DAO layer conversion and the JSON serialization work as expected :
my database record has its status set to on
it is mapped to the Java Status.ON
the entity is serialized as
{
"status" : "on"
}
But if I use repository.findById(1L, ProcessSummary.class) instead, the entity is serialized as
{
"status" : "ON"
}
How can I get the same result when using a projection as target type? Is it possible with a projection, or should I try something else (a DTO class maybe)?
Sorry folks, it was just me and a textbook case of of PEBKAC :)
The getStatus() method in the interface MUST return a Status, not a String.
public interface ProcessSummary {
String getStatus();
}
does what it's asked: converts the enum to a String, hence Status.ON is serialized as "ON", while
public interface ProcessSummary {
Status getStatus();
}
indeed uses the #JsonValue annotated method and serializes Status.ON as "on".

What is the best way to return different types of ResponseEntity in Spring-Boot

I would like to return two different response for a spring boot rest API.
I should not be using <?> wild card as i get the sonar issue "Generic wildcard types should not be used in return types"
My code:
#GetMapping(path = {"/v1/{type}"}, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<?> method(#PathVariable(value = "type") boolean type) {
boolean b = type;// some logic
if (b) {
Success result=new Success();
result.setSuccess("Yes");
return new ResponseEntity<>(result,HttpStatus.OK);
}
else {
Error result=new Error();
result.setError("No");
return new ResponseEntity<>(result,HttpStatus.CONFLICT); //appropriate error code
}
}
Any idea how to handle this situation.
Update:
public interface MyResponse{
public Success getSuccessObj();
public Error getErrorObj();
}
#Service
public class Success implements MyResponse {
public Error getErrorObj(){
return null;
}
public Success getSuccessObj(){
Success s=new Success();
return s;
}
#Service
public class Error implements MyResponse {
public Error getErrorObj(){
Error e=new Error();
return e;
}
public Success getSuccessObj(){
return null;
}
Not claiming to be "the best way", but one approach can be:
Introduce:
package com.my.package;
public interface MyResponseI { //if Error, Success (and others) have more "in common", you can also introduce an (abstract) class (with fields, methods, etc.)!
}
"Implement"/Extend:
public class Success implements com.my.package.MyResponseI { //everything else can stay}
as
public class Error implements com.my.package.MyResponseI { //everything else can stay}
Use as Response Type:
#...
public ResponseEntity<com.my.package.MyResponseI> ...
(on client side distinguish).
..and in "your domain" (error, success, ...), you are free to use any "tweaks" of a object oriented design.
Useful links/entries:
https://stackoverflow.blog/2020/03/02/best-practices-for-rest-api-design/
https://swagger.io/resources/articles/best-practices-in-api-design/
https://www.google.com/search?q=rest+api+design
, but also
https://www.google.com/search?q=object+oriented+design
and https://www.google.com/search?q=domain+driven+design
This should work
I tried the snippet below by myself and it worked for me:
#GetMapping("/testresponse/{id}")
public ResponseEntity<?> testMyResponse(#PathVariable("id") int id)
{
if(id==1)
return ResponseEntity.ok(new Success());
else return new ResponseEntity<>(new Error(), HttpStatus.CONFLICT);
}
public class Success {
private String msg = "Success";
public String getMsg() {
return msg;
}
}
public class Error {
private String msg = "Error";
public String getMsg() {
return msg;
}
}
EDIT: The solution as below doesn't work
You should also define an interface for both Success and Error classes. Let say the interface MyResponse
And then change your method declaration, it would look like this
public ResponseEntity<MyResponse> method(#PathVariable(value = "type") boolean type)
If so, the return statement, could be:
return new ResponseEntity<>(result, HttpStatus.OK);
Or
//for status 200 OK
return ResponseEntity.ok(result);

SpringBoot rest validation does not fail on wrong enum input

I have a SpringBoot rest POST endpoint where in body I POST an enum value. This call does not fail on wrong value input. I would like the rest call to fail instead of returning null for a value which can not be deserialised.
I have tried with the following custom ObjectMapper configuration, but any wrong input i put as enum deserialises to null.
#Bean
#Primary
public ObjectMapper customJsonObjectMapper() {
Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
ObjectMapper objectMapper = builder.build();
objectMapper.configure(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL, false);
SimpleModule module = new SimpleModule();
objectMapper.registerModule(module);
return objectMapper;
}
For example if i have the enum:
public enum CouponOddType {
BACK("back"),
LAY("lay");
private String value;
CouponOddType(String value) {
this.value = value;
}
#Override
#JsonValue
public String toString() {
return String.valueOf(value);
}
#JsonCreator
public static CouponOddType fromValue(String text) {
for (CouponOddType b : CouponOddType.values()) {
if (String.valueOf(b.value).equals(text)) {
return b;
}
}
return null;
}
}
the dto where the request is mapped to:
#ApiModel(description = "Filter used to query coupons. Filter properties are combined with AND operator")
#Validated
#javax.annotation.Generated(value = "io.swagger.codegen.v3.generators.java.SpringCodegen", date = "2020-07-07T13:12:58.487+02:00[Europe/Ljubljana]")
public class CouponQueryFilter {
#JsonProperty("statuses")
#Valid
private List<CouponStatus> statuses = null;
#JsonProperty("oddTypes")
#Valid
private List<CouponOddType> oddTypes = null;
public CouponQueryFilter statuses(List<CouponStatus> statuses) {
this.statuses = statuses;
return this;
}
public CouponQueryFilter addStatusesItem(CouponStatus statusesItem) {
if (this.statuses == null) {
this.statuses = new ArrayList<>();
}
this.statuses.add(statusesItem);
return this;
}
/**
* Get statuses
* #return statuses
**/
#ApiModelProperty(value = "")
#Valid
public List<CouponStatus> getStatuses() {
return statuses;
}
public void setStatuses(List<CouponStatus> statuses) {
this.statuses = statuses;
}
public CouponQueryFilter oddTypes(List<CouponOddType> oddTypes) {
this.oddTypes = oddTypes;
return this;
}
public CouponQueryFilter addOddTypesItem(CouponOddType oddTypesItem) {
if (this.oddTypes == null) {
this.oddTypes = new ArrayList<>();
}
this.oddTypes.add(oddTypesItem);
return this;
}
/**
* Get oddTypes
* #return oddTypes
**/
#ApiModelProperty(value = "")
#Valid
public List<CouponOddType> getOddTypes() {
return oddTypes;
}
public void setOddTypes(List<CouponOddType> oddTypes) {
this.oddTypes = oddTypes;
}
}
and in the POST request i put the enum value in json array:
{
"statuses": [
"wrong value"
],
"oddTypes": [
"wrong value"
]
}
I would like that this type of request results in an HTTP 404 error, instead of deserialising into null.
In this case, Jackson is actually behaving as intended and there is an issue in your deserialization logic. Ultimately, you want bad enum values to throw an error and return that error to the user. This is infact the default behaviour of spring and jackso, and will result in a HTTP 400 BAD REQUEST error. IMO This is the appropriate error to return (not 404) since the user has supplied bad input.
Unless there is a specific reason for you to implement a custom #JsonCreator in your enum class, I would get rid of it. What is happening here is that Jackson is being told to use this method for converting a string into an enum value instead from the defualt method. When a text is passed that is not a valid value of your enum, you are returning null which results into that values deserializing to null.
A quick fix, would be to delete the JsonCreator and allow jackson to use its default behaviour for handling enums. The extra properties methods you have added are unnecessary in most cases
ublic enum CouponOddType {
BACK("back"),
LAY("lay");
private String value;
CouponOddType(String value) {
this.value = value;
}
}
If you need to perserve the creator for some other reason, then you will need to add business logic to determine if any of the enum values in the arrays evaluated to null.
private Response someSpringRestEndpoint(#RequestBody CouponQueryFilter filter){
if (filter.getOddTypes() != null && filter.getOddTypes().contains(null){
throw new CustomException()
}
if (filter.getStatuses() != null && filter.getStatuses().contains(null){
throw new CustomException()
}
//... other business logic
}

Multiple Predicates to group collection to hashmap

I have a list of Objects as below -
List<Transaction>
where the Transaction Object will look like
Transaction {
String Status;
}
Status <A,B,C,D,E,F,G...>
If Status in (A,B,C)->Success
If Status in (D,E,F)->Failure
If Status in (G,H...)->Pending
Individual Predicates for identifying each status transaction are defined.
Expected output would be a hashmap with the Success/Failure/Rejected text as key and collective count of these statuses as value
HashMap<String, Integer> ->
{
"Success": 1,
"Failure":2,
"Pending":2
}
I am unable to proceed how to do this in a single execution. Right now, I get the counts separately. Can anyone please assist with the request?
You may first declare an enum like this to represent the 3 states that you are interested in.
public enum TxStatus {
Success, Failure, Pending;
}
Then write a method in Transaction to transform the String literal value into a real status value that you expect. Here's a one such implementation.
public class Transaction {
private final String status;
private Pattern SUCCESS_PATTERN = Pattern.compile("[ABC]");
private Pattern FAILURE_PATTERN = Pattern.compile("[DEF]");
private Pattern PENDING_PATTERN = Pattern.compile("[GHI]");
public Transaction(String status) {
super();
this.status = status;
}
public String getStatus() {
return status;
}
public TxStatus interpretStatus() {
if (SUCCESS_PATTERN.matcher(status).matches()) {
return TxStatus.Success;
}
if (FAILURE_PATTERN.matcher(status).matches()) {
return TxStatus.Failure;
}
if (PENDING_PATTERN.matcher(status).matches()) {
return TxStatus.Pending;
}
throw new IllegalArgumentException("Invalid status value.");
}
}
Finally your client code should look something like this,
Map<String, Long> txStatusToCountMap = txs.stream()
.collect(Collectors.groupingBy(tx -> tx.interpretStatus().toString(),
Collectors.counting()));

Resources