Special Field in JSON Data using Jackson - java

I have a data structure that has a special property. The data structure represents stock pricing data for the entire day. [So basically OLHC] In my current serialization routine and builder I currently support an "all" property. Which is a method in the builder that sets all of the OHLC prices to one single price.
Is there a way to set this up without having to have the property name of all?
On Serialization it should make the comparison and use all
On deserialization it should redirect to the all method in the builder.
Is this possible in Jackson?
Example:
The data structure looks like:
EODPrice
- Open
- Low
- Close
- High
Lets say we have the json: "{all: 5.00}" It would deserialize to Open: 5.00, low: 5.00, close: 5.00, and high: 5.00. If we were to serialize I would want to change the behavior so that if we have Open == Low == Close == High then we'd create one property called all. If that condition was not true, then we wouldn't serialize the "all" property at all.

The serialization part is definitely possible by using the annotation #JsonFilter which you can read about in the Jackson documentation.
Deserialization is standard Jackson by the use of #JsonCreator.
Filters can be applied to classes, methods and fields and you can write your own custom filter that handles the open, low, close, high problem.
Check this tutorial for an excellent introduction.
For a code sample, take a look at this. First, declare your EODPrice with the #JsonFilter annotation.
#JsonIgnoreProperties(ignoreUnknown = true) // required to skip the "all" attribute in the JSON
#JsonFilter("allFilter") // Specify the filter
public class EODPrice {
private final BigDecimal close;
private final BigDecimal high;
private final BigDecimal low;
private final BigDecimal open;
// Builder method, does not include "all"
#JsonCreator
public EODPrice(
#JsonProperty("open") final BigDecimal open,
#JsonProperty("low") final BigDecimal low,
#JsonProperty("close") final BigDecimal close,
#JsonProperty("high") final BigDecimal high) {
this.open = open;
this.low = low;
this.close = close;
this.high = high;
}
// This is not part of the JSON but puts the business logic in the POJO
#JsonIgnore
public boolean allFieldsEqual() {
return open.equals(low) && open.equals(close) && open.equals(high);
}
public BigDecimal getAll() {
if (allFieldsEqual()) {
return open;
}
return BigDecimal.ZERO;
}
public BigDecimal getClose() {
return close;
}
public BigDecimal getHigh() {
return high;
}
public BigDecimal getLow() {
return low;
}
public BigDecimal getOpen() {
return open;
}
}
The filter can look something like this:
private PropertyFilter allFilter = new SimpleBeanPropertyFilter() {
#Override
public void serializeAsField(
Object pojo,
JsonGenerator jgen,
SerializerProvider provider,
PropertyWriter writer) throws Exception {
// If it is not the "all" property, go on with normal serialization
if (!writer.getName().equals("all")) {
writer.serializeAsField(pojo, jgen, provider);
return;
}
// Else, check the special all-rule
final EODPrice eodPrice = (EODPrice) pojo;
if (eodPrice.allFieldsEqual()) {
// Only serialize if all fields are equal
writer.serializeAsField(pojo, jgen, provider);
}
}
#Override
protected boolean include(BeanPropertyWriter writer) {
return true;
}
#Override
protected boolean include(PropertyWriter writer) {
return true;
}
};
Finally, setup the mapper. This testcase illustrates that the filter kicks in:
#Test
public void testJsonRoundtrip() throws IOException {
final FilterProvider filters = new SimpleFilterProvider().addFilter("allFilter", allFilter);
final EODPrice eodPriceWithAll = new EODPrice(BigDecimal.ONE, BigDecimal.ONE, BigDecimal.ONE, BigDecimal.ONE);
final EODPrice eodPriceWithoutAll = new EODPrice(BigDecimal.TEN, BigDecimal.ONE, BigDecimal.ONE, BigDecimal.ONE);
final ObjectMapper mapper = new ObjectMapper();
mapper.setFilters(filters);
// First, test serialization
final String eodWithAllAsStr = mapper.writeValueAsString(eodPriceWithAll);
final String eodWithoutAllAsStr = mapper.writeValueAsString(eodPriceWithoutAll);
Assert.assertTrue(eodWithAllAsStr.contains("all"));
Assert.assertFalse(eodWithoutAllAsStr.contains("all"));
// Then, test deserialization
final EODPrice eodPriceWithAll2 = mapper.readValue(eodWithAllAsStr, EODPrice.class);
final EODPrice eodPriceWithoutAll2 = mapper.readValue(eodWithoutAllAsStr, EODPrice.class);
Assert.assertTrue(eodPriceWithAll2.allFieldsEqual());
Assert.assertFalse(eodPriceWithoutAll2.allFieldsEqual());
}
EDIT: After updates from the OP deserialization is added to the POJO. Furthermore, business logic is moved from the filter to the POJO.

Related

Java Stream API with non-pure functions chain

Let's assume I have a class Person
public class Person {
private final String name;
private final int age;
private boolean rejected;
private String rejectionComment;
public void reject(String comment) {
this.rejected = true;
this.rejectionComment = comment;
}
// constructor & getters are ommited
}
and my app is something like that
class App {
public static void main(String[] args) {
List<Person> persons = Arrays.asList(
new Person("John", 10),
new Person("Sarah", 20),
new Person("Daniel", 30)
)
persons.forEach(p -> {
rejectIfYoungerThan15(p);
rejectIfNameStartsWithD(p);
// other rejection functions
}
}
private static void rejectIfYoungerThan15(Person p) {
if (!p.isRejected() && p.getAge() < 15) {
p.reject("Too young")
}
}
private static void rejectIfNameStartsWithD(Person p) {
if (!p.isRejected() && p.getName().startsWith("D")) {
p.reject("Name starts with 'D'")
}
}
// other rejection functions
}
The thing is I don't like that I have to perform !p.isRejected() check in every rejection function. Moreover, it doesn't make sense to pass an already rejected person to next filters.
So my idea is to use a mechanism of Stream.filter and make something like
persons.stream().filter(this::rejectIfYoungerThan15).filter(this::rejectIfNameStartsWithD)...
And change signature for these methods to return true if a passed Person has not been rejected and false otherwise.
But it seems to me that it's a very bad idea to use filter with non-pure functions.
Do you have any ideas of how to make it in more elegant way?
When you change the check functions to only check the condition (i.e. not to call p.isRejected()) and return boolean, you already made the necessary steps to short-circuit:
private static boolean rejectIfYoungerThan15(Person p) {
if(p.getAge() < 15) {
p.reject("Too young");
return true;
}
return false;
}
private static boolean rejectIfNameStartsWithD(Person p) {
if(p.getName().startsWith("D")) {
p.reject("Name starts with 'D'");
return true;
}
return false;
}
usable as
persons.forEach(p -> {
if(rejectIfYoungerThan15(p)) return;
if(rejectIfNameStartsWithD(p)) return;
// other rejection functions
}
}
A Stream’s filter operation wouldn’t do anything other than checking the returned boolean value and bail out. But depending on the Stream’s actual terminal operation the short-circuiting could go even farther and end up in not checking all elements, so you should not bring in a Stream operation here.
Calling these methods from lambda is fine, however, for better readability, you can rename these methods to show what they are doing and return boolean, e.g.:
private boolean hasEligibleAge(Person p){..}
private boolean hasValidName(Person p){..}
Another approach would be to wrap these methods into another method (to reflect the business logic/flow), e.g.:
private boolean isEligible(Person p){
//check age
//check name
}
You should make Person immutable, and let the reject-methods return a new Person. That will allow you to chain map-calls. Something like this:
public class Person {
private final String name;
private final int age;
private final boolean rejected;
private final String rejectionComment;
public Person reject(String comment) {
return new Person(name, age, true, comment);
}
// ...
}
class App {
// ...
private static Person rejectIfYoungerThan15(Person p) {
if (!p.isRejected() && p.getAge() < 15) {
return p.reject("Too young");
}
return p;
}
}
Now you can do this:
persons.stream()
.map(App::rejectIfYoungerThan15)
.map(App::rejectIfNameStartsWithD)
.collect(Collectors.toList());
If you want to remove rejected persons, you can add a filter after the mapping:
.filter(person -> !person.isRejected())
EDIT:
If you need to short circuit the rejections, you could compose your rejection functions into a new function and make it stop after the first rejection. Something like this:
/* Remember that the stream is lazy, so it will only call new rejections
* while the person isn't rejected.
*/
public Function<Person, Person> shortCircuitReject(List<Function<Person, Person>> rejections) {
return person -> rejections.stream()
.map(rejection -> rejection.apply(person))
.filter(Person::isRejected)
.findFirst()
.orElse(person);
}
Now your stream can look like this:
List<Function<Person, Person>> rejections = Arrays.asList(
App::rejectIfYoungerThan15,
App::rejectIfNameStartsWithD);
List<Person> persons1 = persons.stream()
.map(shortCircuitReject(rejections))
.collect(Collectors.toList());

Adding Java final keyword to working method that builds instances inside a loop

Take the following POJOs:
public class Widget {
private String fizz;
private Long buzz;
private List<Fidget> collaborators;
// Constructor, getters & setters
}
public class Fidget {
private String fizz;
private String foo;
// Constructor, getters & setters
}
And the following (working) method:
public void compriseWidgets(List<Fidget> fidgetList) {
List<Widget> widgets = new ArrayList<Widget>();
Widget currentWidget = null;
for (Fidget fidget : fidgetList) {
if (currentWidget == null ||
!currentWidget.getFizz().equals(fidget.getFizz())) {
currentWidget = new Widget();
widgets.add(currentWidget);
currentWidget.setFizz(fidget.getFizz());
currentWidget.setBuzz(fidget.getFoo().length());
}
currentWidget.getCollaborators().add(fidget);
}
return widgets;
}
Here we want to return a List<Widget> and populate that list only:
From the first Fidget in the input list (hence currentWidget == null); and
If the Fidget and currentWidget have the same fizz value
Furthermore, we want to keep appending collaborators to the currentWidget regardless of whether the fizzes match or not.
My problem
A new code style guideline is requiring that we declare ALL variables with final...meaning I need to get the above code refactored to look like so:
public void compriseWidgets(final List<Fidget> fidgetList) {
final List<Widget> widgets = new ArrayList<Widget>();
final Widget currentWidget = null;
for (final Fidget fidget : fidgetList) {
...
}
return widgets;
}
Because it requires both the creation of a new Widget inside the loop, but an external (outside the loop) reference to a Widget that we can add collaborators to, I'm at a total loss for how to rewrite this with final. Any ideas? Also, please note, this is nothing that I can "push back" on, I just need to figure it out and get it working with the new coding standard.
To expand on my comment, you could convert your example code more or less mechanically, like so:
public List<Widget> compriseWidgets(final List<Fidget> fidgetList) {
final List<Widget> widgets = new ArrayList<Widget>();
final Widget[] currentWidget = new Widget[] {null};
for (final Fidget fidget : fidgetList) {
if (currentWidget[0] == null ||
!currentWidget[0].getFizz().equals(fidget.getFizz())) {
currentWidget[0] = new Widget();
widgets.add(currentWidget);
currentWidget.setFizz(fidget.getFizz());
currentWidget.setBuzz(fidget.getFoo().length());
}
currentWidget.getCollaborators().add(fidget);
}
return widgets;
}
Many variables can be made final without any particular impact, including the lists of Fidgets and Widgets, and the loop variable in the enhanced for loop. The only other variable in the original method was currentWidget, which the implementation modifies. This can be replaced with a (final) array of length 1, whose zeroth element can then be used as a drop-in replacement for the original variable.
A more troublesome requirement along the same lines would be that you may not use assignment statements (initializers in variable declarations not being considered "assignments"). This is pushing toward a more functional style of programming, which I suppose may be the intent of your new guideline. You might, then, approach it something like this:
public List<Widget> compriseWidgets(final List<Fidget> fidgetList) {
final List<Widget> widgets = new ArrayList<Widget>();
final ListIterator<Fidget> fidgets = fidgetList.listIterator();
while (addWidget(widgets, fidgets)) { /* empty */ }
return widgets;
}
private boolean addWidget(final List<Widget> widgets, final ListIterator<Fidget> fidgets) {
if (fidgets.hasNext()) {
final Fidget firstFidget = fidgets.next();
final Widget currentWidget = new Widget();
widgets.add(currentWidget);
currentWidget.setFizz(firstFidget.getFizz());
currentWidget.setBuzz(firstFidget.getFoo().length());
currentWidget.getCollaborators().add(firstFidget);
while (fidgets.hasNext()) {
final nextFidget = fidgets.next();
if (currentWidget.getFizz().equals(nextFidget.getFizz())) {
currentWidget.getCollaborators().add(nextFidget);
} else {
fidgets.previous();
return true;
}
}
}
return false;
}
This is pretty much the same trick, just a little less obvious. The mutable state is hidden in the call stack (each invocation of addWidget() stands in for a mutation of the original method's currentWidget()) and in a container object, this time a ListIterator.
One could go further in the functional programming direction. In general, for example, you could look toward Stream-based approaches, though I don't think that works out completely cleanly in this particular case. More general functional programming does not have the constraints that apply to Streams, however.
The Builder design pattern is a great way to build immutable objects.
Source:
https://stackoverflow.com/a/15461337/4245294
What I love about this version of this design pattern is how it gives you the perfect spot for validation rules before object creation.
Example applied to this problem:
public class Widget {
private final String fizz;
private final Long buzz;
private final List<Fidget> collaborators;
private Widget(Builder builder) {
this.fizz = builder.fizz;
this.buzz = builder.buzz;
this.collaborators = builder.collaborators;
}
public static Builder builder() {
return new Builder();
}
public static class Builder {
private String fizz;
private Long buzz;
private List<Fidget> collaborators = new ArrayList<>();
public Builder addFizz(String fizz) {
this.fizz = fizz;
return this;
}
public Builder addBuzz(Long buzz) {
this.buzz = buzz;
return this;
}
public Builder addCollaborators(List<Fidget> fidgets) {
collaborators.addAll(fidgets);
return this;
}
public Builder addCollaborator(Fidget fidget) {
collaborators.add(fidget);
return this;
}
private void validate() throws InvalidArgumentException{
ArrayList<String> invalidArguments = new ArrayList<>();
boolean failedValidation = false;
if (collaborators.isEmpty()) {
invalidArguments.add("collaborators");
failedValidation = true;
}
if (this.fizz == null) {
invalidArguments.add("fizz");
failedValidation = true;
}
if (this.buzz == null) {
invalidArguments.add("buzz");
failedValidation = true;
}
if (failedValidation) {
throw new InvalidArgumentException(invalidArguments.toArray(new String[0]));
}
}
public Widget build() {
validate();
return new Widget(this);
}
}
}
And you create a valid Widget object like so:
Widget widget = Widget.builder().addFizz("test").addBuzz(999).addCollaborators(fidgets).build();
Your compriseWidget method has problems that I mentioned in a comment to the Question, otherwise I would provide an example for that as well.

How to clone old builder to make a new builder object?

I have a builder class which I am using in one of my project.
Let's say I have metricA as builder based on below class.
I need to make a new builder metricB based on metricA by cloning metricA so that metricB contains all the values which were already there in metricA.
In the constructor of MetricHolder I am initializing some fields (which are not set directly) basis on fields that have been set already.
clientTypeOrPayId - I am initializing this field. If payId is present, then I will set this value or I will set clientType.
clientKey - I am initializing this field as well in the same constructor.
And most importantly, I am putting few mandatory fields in the clientPayload map. I am not sure what is the right way to do that. But I need to add is_clientid and is_deviceid into the map. (In general I am adding few more fields).
And then in the last of the constructor, I am calculating latency difference and sending it to some other system.
Below is my class:
public final class MetricHolder {
private final String clientId;
private final String deviceId;
private final String payId;
private final String clientType;
private final String clientTypeOrPayId;
private final Schema schema;
private final String schemaId;
private final String clientKey;
private final Map<String, String> clientPayload;
private final Record record;
private final long clientCreateTimestamp;
private final long clientSentTimestamp;
private MetricHolder(Builder builder) {
this.payId = builder.payId;
this.siteId = builder.siteId;
this.clientType = builder.clientType;
this.clientId = builder.clientId;
this.deviceId = builder.deviceId;
this.schema = builder.schema;
this.schemaId = builder.schemaId;
// populating all the required fields in the map and make it immutable
// not sure whether this is right?
builder.clientPayload.put("is_clientid", (clientId == null) ? "false" : "true");
builder.clientPayload.put("is_deviceid", (clientId == null) ? "true" : "false");
this.clientPayload = Collections.unmodifiableMap(builder.clientPayload);
this.clientTypeOrPayId = Strings.isNullOrEmpty(payId) ? clientType : payId;
this.record = builder.record;
this.clientKey = "process:" + System.currentTimeMillis() + ":"
+ ((clientId == null) ? deviceId : clientId);
this.clientCreateTimestamp = builder.clientCreateTimestamp;
this.clientSentTimestamp = builder.clientSentTimestamp;
// this will be called twice while cloning
// what is the right way to do this then?
SendData.getInstance().insert(clientTypeOrPayId,
System.currentTimeMillis() - clientCreateTimestamp);
SendData.getInstance().insert(clientTypeOrPayId,
System.currentTimeMillis() - clientSentTimestamp);
}
public static class Builder {
private final Record record;
private Schema schema;
private String schemaId;
private String clientId;
private String deviceId;
private String payId;
private String clientType;
private Map<String, String> clientPayload;
private long clientCreateTimestamp;
private long clientSentTimestamp;
// this is for cloning
public Builder(MetricHolder packet) {
this.record = packet.record;
this.schema = packet.schema;
this.schemaId = packet.schemaId;
this.clientId = packet.clientId;
this.deviceId = packet.deviceId;
this.payId = packet.payId;
this.clientType = packet.clientType;
// make a new map and check whether mandatory fields are present already or not
// and if they are present don't add it again.
this.clientPayload = new HashMap<>();
for (Map.Entry<String, String> entry : packet.clientPayload.entrySet()) {
if (!("is_clientid".equals(entry.getKey()) || "is_deviceid".equals(entry.getKey())) {
this.clientPayload.put(entry.getKey(), entry.getValue());
}
}
this.clientCreateTimestamp = packet.clientCreateTimestamp;
this.clientSentTimestamp = packet.clientSentTimestamp;
}
public Builder(Record record) {
this.record = record;
}
public Builder setSchema(Schema schema) {
this.schema = schema;
return this;
}
public Builder setSchemaId(String schemaId) {
this.schemaId = schemaId;
return this;
}
public Builder setClientId(String clientId) {
this.clientId = clientId;
return this;
}
public Builder setDeviceId(String deviceId) {
this.deviceId = deviceId;
return this;
}
public Builder setPayId(String payId) {
this.payId = payId;
return this;
}
public Builder setClientType(String clientType) {
this.clientType = clientType;
return this;
}
public Builder setClientPayload(Map<String, String> payload) {
this.clientPayload = payload;
return this;
}
public Builder setClientCreateTimestamp(long clientCreateTimestamp) {
this.clientCreateTimestamp = clientCreateTimestamp;
return this;
}
public Builder setClientSentTimestamp(long clientSentTimestamp) {
this.clientSentTimestamp = clientSentTimestamp;
return this;
}
public MetricHolder build() {
return new MetricHolder(this);
}
}
// getters
}
Question:-
Below is how I make metricA builder object:
MetricHolder metricA = new MetricHolder.Builder(record).setClientId("123456").setDeviceId("abcdefhg")
. setPayId("98765").setClientPayload(payloadMapHolder).setClientCreateTimestamp(createTimestamp)
.setClientSentTimestamp(sentTimestamp).build();
Now this is how I clone the metricA object later on in the code when I get all other fields as shown below:
MetricHolder metricB = new MetricHolder.Builder(metricA).setSchema(schema).setSchemaId("345").build();
I see two problem now:
First of all, my SendData.getInstance() line in the MetricHolder constructor will be called twice. First is when I make metricA and second when I make metricB by cloning metricA. But I just want to call it only once when I try to create metricA builder object? How can I make this possible?
Second is, the way I am populating clientPayload map with two mandatory fields in the MetricHolder constructor doesn't look right to me. Is there any other better way to do the same thing?
I guess the whole problem is happening because the way I am cloning metricA to make a metricB builder object? What is the best way to do this? I want to achieve above two things but in a right way.
But I just want to call it only once when I try to create metricA builder object? How can I make this possible?
The most straightforward way is to have a flag in the builder indicating whether it was created by Record or by cloning:
class Builder {
final boolean cloned;
Builder(MetricHolder packet) {
this.cloned = true;
// ...
}
Builder(Record record) {
this.cloned = false;
// ...
}
}
Then, in the constructor of MetricHolder:
if (!builder.cloned) {
SendData.getInstance().whatever();
}
But it's worth pointing out that making this call to SendData is an example of doing too much work in the constructor. You should think carefully about whether you really want to be making this call in the constructor, or whether you can factor that out into another method.
Second is, the way I am populating clientPayload map with two mandatory fields in the MetricHolder constructor doesn't look right to me. Is there any other better way to do the same thing?
You've misunderstood the "unmodifiable" bit of using Collections.unmodifiableMap: it's only an unmodifiable view of the map parameter; you can still modify the underlying map.
Here's a JUnit test to demonstrate:
Map<String, String> original = new HashMap<>();
original.put("hello", "world");
// Obviously false, we just put something into it.
assertFalse(original.isEmpty());
Map<String, String> unmodifiable = Collections.unmodifiableMap(original);
// We didn't modify the original, so we don't expect this to have changed.
assertFalse(original.isEmpty());
// We expect this to be the same as for the original.
assertFalse(unmodifiable.isEmpty());
try {
unmodifiable.clear();
fail("Expected this to fail, as it's unmodifiable");
} catch (UnsupportedOperationException expected) {}
// Yep, still the same contents.
assertFalse(original.isEmpty());
assertFalse(unmodifiable.isEmpty());
// But here's where it gets sticky - no exception is thrown.
original.clear();
// Yep, we expect this...
assertTrue(original.isEmpty());
// But - uh-oh - the unmodifiable map has changed!
assertTrue(unmodifiable.isEmpty());
The thing is that the map is only unmodifiable if there is no other reference to it hanging around: if you don't have a reference to original, unmodifiable actually is unmodifiable; otherwise, you can't rely upon the map never changing.
In your particular case, you are simply wrapping the clientPayload map in your unmodifiable collection. So, you're overwrite values for previously-constructed instances.
For example:
MetricHolder.Builder builder = new MetricHolder.Builder();
MetricHolder first = builder.build();
assertEquals("false", first.clientPayload.get("is_clientid"));
assertEquals("true", first.clientPayload.get("is_deviceid"));
builder.setClientId("").build();
// Hmm, first has changed.
assertEquals("true", first.clientPayload.get("is_clientid"));
assertEquals("false", first.clientPayload.get("is_deviceid"));
The correct approach is not to wrap builder.clientPayload. Take a copy of the map, modify it, and then wrap with unmodifiableMap:
{
Map<String, String> copyOfClientPayload = new HashMap<>(builder.clientPayload);
copyOfClientPayload.put("is_clientid", (clientId == null) ? "false" : "true");
copyOfClientPayload.put("is_deviceid", (clientId == null) ? "true" : "false");
this.clientPayload = Collections.unmodifiableMap(copyOfClientPayload);
}
The surrounding {} aren't strictly necessary, but they restrict the scope of copyOfClientPayload, so you can't accidentally reuse it later in the constructor.

Passing values from database in "allowableValues"?

I am using Swagger version 2 with Java Spring. I have declared a property and it works fine and it generates a drop down list of value I assigned.
#ApiParam(value = "Pass any one Shuttle provider ID from the list", allowableValues = "1,2,3,4,10")
private Long hotelId;
Now, I need a way to populate this list which is passed in allowableValues from my database as it could be random list as well as huge data. How can I assign list of values dynamically from database in this allowableValues?
This question is bit old, I too faced the same problem so thought of adding here which may help some one.
//For ApiModelProperty
#ApiModelProperty(required = true, allowableValues = "dynamicEnum(AddressType)")
#JsonProperty("type")
private String type;
Created a component which implements ModelPropertyBuilderPlugin
#Component
#Order(SwaggerPluginSupport.SWAGGER_PLUGIN_ORDER + 1)
public class ApiModelPropertyPropertyBuilderCustom implements ModelPropertyBuilderPlugin {
private final DescriptionResolver descriptions;
#Autowired
public ApiModelPropertyPropertyBuilderCustom(DescriptionResolver descriptions) {
this.descriptions = descriptions;
}
public void apply(ModelPropertyContext context) {
try {
AllowableListValues allowableListValues = (AllowableListValues) FieldUtils.readField(context.getBuilder(),
"allowableValues", true);
if(allowableListValues!=null) {
String allowableValuesString = allowableListValues.getValues().get(0);
if (allowableValuesString.contains("dynamicEnum")) {
String yourOwnStringOrDatabaseTable = allowableValuesString.substring(allowableValuesString.indexOf("(")+1, allowableValuesString.indexOf(")"));
//Logic to Generate dynamic values and create a list out of it and then create AllowableListValues object
context.getBuilder().allowableValues(allowableValues);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
public boolean supports(DocumentationType delimiter) {
return SwaggerPluginSupport.pluginDoesApply(delimiter);
}
}
Similary for ApiParam we can create component which will implement ParameterBuilderPlugin
#Override
public void apply(ParameterContext context) {
#SuppressWarnings("Guava") final Optional<ApiParam> apiParam =
context.resolvedMethodParameter().findAnnotation(ApiParam.class);
if (apiParam.isPresent()) {
final String allowableValuesString = apiParam.get().allowableValues();
//Your logic here
context.parameterBuilder().allowableValues(allowableValues);
}
}
You need to create constructor in SwaggerConfiguration class.
#Autowire service and withdraw data you need from database
assign this to final variable
assign this final variable to allowableValues in annotation
enjoy not efficient api
private final String allowableValues;
public SwaggerConfiguration() {
List<YourEntitiy> list = someService.findAll();
//code to get every value you need and add create comma separated String
StringJoiner stringJoiner = new StringJoiner(",");
stringJoiner.add(list.get(0).getValue());
this.allowableValues = stringJoiner.toString();
}
#ApiParam(allowableValues = allowableValues)
But I think it's bad idea getting all ids from database just to create allowable values. Just validate in api method if that id exist and/or Create new api to get ids from database, use pagination from Spring Data project, like PageImpl<> javadocs

Orika: How to map a boolean value evaluating a condition

I've two classes I need to map (one way at the moment).
class Order1 {
int number;
String status;
}
class Order2 {
int number;
boolean canceled;
boolean confirmed;
}
I would like to do something like:
mapperFactory.classMap(Order1.class, Order2.class)
.map("status == 'Canceled'", "canceled")
.map("status == 'Confirmed'", "confirmed")
.byDefault().register();
Is there a way to do something like this?
Edit: I tried using a CustomMapper like this:
.customize(new CustomMapper<Order1, Order1>() {
#Override
public void mapAtoB(final Order1 a, final Order1 b, final MappingContext context) {
String status = a.getStatus();
b.setCanceled("Canceled".equals(status));
b.setConfirmed("Confirmed".equals(status));
}
})
This works, but it doesn't seem possible to add many customized mappers for the same pair of classes.
Instead, I used custom converters as Sidi's answer explains.
Thanks.
Can you just add public boolean isCanceled() and boolean isConfirmed() to class Order1 ?
Orika will auto map it
Or
Use you can create a converter StatusConverter taking String parameter, register it
converterFactory.registerConverter("canceled", new StatusConverter("Canceled"));
converterFactory.registerConverter("confirmed", new StatusConverter("Confirmed"));
Then
mapperFactory.classMap(Order1.class, Order2.class)
.fieldMap("status", "canceled").converter("canceled").add()
.fieldMap("status", "confirmed") .converter("confirmed").add()
.byDefault().register();
this converter should convert String to Boolean, return true if the given parameter is equals to the value.

Categories

Resources