How can I return only several Json fields of an inner object with Jackson - rest

I'm using Jackson + spring RestControlles
Lets say I have 3 classes
class Card {
String id;
String pan;
String holder;
String expiry;
}
class CardProfile {
String id;
Card card;
...
}
class Recipient {
String name;
Card card;
...
}
So when I serialize a CardProfile object I want all fields of object card to be percent in result json.
But in case Recipient object , Json has to have only part of object card(For example : pan, holder) .
Is there a way to present Card object in json with different set of fields?
Use of annotations like #JsonIgnore will remove fields from both cases.
Thanks.

Thinking again - is this approach scalable? What if you need something similar again?
Why not rely on inheritance and create a base class and multiple subclasses with attributes you are interested in?
class Card{
// bare min attribute
.....
}
class SubCard1 extends Card{
//add more attribute
.....
}
class SubCard2 extends Card{
//add more attribute
.....
}
Use SubCard1 or SubCard2 based on what you need? Same can be used for other classes. If this is more dynamic - look at design patterns.

when you serialize the object you can keep only the parameter that are interest for you.
I post a simple java code to resolve this stuff you can convert the code in the lenguage that you need.
the following code is prt of the Recipient class which contains two field: String name and Card card
To write the json object:
#Override
public final void toJSON(final OutputStream out) throws IOException {
final JsonGenerator jg = JSON_FACTORY.createGenerator(out);
jg.writeStartObject();
jg.writeFieldName("Recipient");
jg.writeStartObject();
jg.writeNumberField("id",card.getID());
jg.writeStringField("name", name);
jg.writeStringField("pan", card.getPan());
jg.writeStringField("holder", card.getHolder());
jg.writeEndObject();
jg.writeEndObject();
jg.flush();
}
to get the the object from the json:
public static Recipient fromJSON(final InputStream in) throws IOException {
int jId = -1;
String jName = null;
String jHolder = null;
String jPan = null;
//Obtain a new JsonParser to parse Recipient from JSON
final JsonParser jp = JSON_FACTORY.createParser(in);
while (jp.getCurrentToken() != JsonToken.FIELD_NAME || "Recipient".equals(jp.getCurrentName()) == false) {
// there are no more events
if (jp.nextToken() == null) {
throw new IOException("Unable to parse JSON: no employee object found.");
}
}
while (jp.nextToken() != JsonToken.END_OBJECT) {
if (jp.getCurrentToken() == JsonToken.FIELD_NAME) {
switch (jp.getCurrentName()) {
case "id":
jp.nextToken();
jId = jp.getIntValue();
break;
case "name":
jp.nextToken();
jName = jp.getText();
break;
case "pan":
jp.nextToken();
jPan = jp.getText();
break;
case "holder":
jp.nextToken();
jHolder = jp.getText();
break; }
} }
//create new card object, the values not present in the json will set to null
Card card = new Card(jId,null,jPan,jHolder,null);
return new Recipient(jName, card);
}
}
I tried to adapt the code for you but it is just to give you an idea,I hope this will be usefull for you.

Related

Why ContentFragment.adaptTo() return null?

I want to create a custom sling model which can be adapted from com.adobe.cq.dam.cfm.ContentFragment
like below,
import com.adobe.cq.dam.cfm.ContentFragment;
#Model(adaptables = ContentFragment.class, adapters = EventInfo.class)
public class EventInfoImpl implements EventInfo{
#Self
ContentFragment cf;
#Override
public String getTitle(){
return cf.getElement("title").getContent();
}
}
but in a caller class,
EventInfo e = contentFragment.adaptTo(EventInfo.class);
adaptTo() returns null.(variable "e" is null)
Why adaptTo() returns null? and How do I adapt correctly in this case?
Sling models can only be adapted from Resource or SlingHttpServletRequest. For anything else you need a classic AdapterFactory.
https://sling.apache.org/apidocs/sling11/org/apache/sling/api/adapter/AdapterFactory.html
How to implement a custom AdapterFactory for Sling Resource?
You can see it in the Sling source code of the ModelAdapterFactory. There is the method createModel:
<ModelType> ModelType createModel(#NotNull Object adaptable, #NotNull Class<ModelType> type)
https://github.com/apache/sling-org-apache-sling-models-impl/blob/master/src/main/java/org/apache/sling/models/impl/ModelAdapterFactory.java
If you dig down, the real filtering happens in the helper-class AdapterImplementations (line 258-268)
https://github.com/apache/sling-org-apache-sling-models-impl/blob/master/src/main/java/org/apache/sling/models/impl/AdapterImplementations.java
if (adaptableType == Resource.class) {
map = resourceTypeMappingsForResources;
resourceTypeRemovalLists = resourceTypeRemovalListsForResources;
} else if (adaptableType == SlingHttpServletRequest.class) {
map = resourceTypeMappingsForRequests;
resourceTypeRemovalLists = resourceTypeRemovalListsForRequests;
} else {
log.warn("Found model class {} with resource type {} for adaptable {}. Unsupported type for resourceType binding.",
new Object[] { clazz, resourceType, adaptableType });
return;
}

Is there a way to show constraint message

I have class CategoryDTO and i want to show the message "Description can't be null" in rest docs instead of "Must not be null".Though i know i can change this message my creating a constraint properties file and adding below line to it
javax.validation.constraints.NotNull.description=Must not be blank or null
But i want to show the message in NotNull annotation
public class CategoryDTO
{
private String id;
#NotNull(message = "Description can't be null")
#Size(min = 2 , max=30 , message = "Size must be greater than 2 and less than 30")
private String description;
}
Edit:
#Test
void testFindAll()
{
CategoryDTO fruits = new CategoryDTO();
fruits.setDescription("Fruits");
fruits.setId(UUID.randomUUID().toString());
CategoryDTO Nuts = new CategoryDTO();
Nuts.setDescription("Nuts");
Nuts.setId(UUID.randomUUID().toString());
ConstrainedFields fields = new ConstrainedFields(CategoryDTO.class);
BDDMockito.when(categoryService.findAll()).thenReturn(Flux.just(fruits,Nuts));
webTestClient.get().uri(CategoryController.rootURL + "/categories")
.exchange().expectBodyList(CategoryDTO.class).
hasSize(2).consumeWith(WebTestClientRestDocumentationWrapper.document("v1/get-all-categories",
responseFields(
fields.withPath("[]").description("An array of categories"),
fields.withPath("[].id").description("Id of category"),
fields.withPath("[].description").description("Description of category")
)
));
}
By default, REST Docs' ConstraintDescriptions uses a ResourceBundleConstraintDescriptionResolver to obtain a description for each constraint. As its name suggests, it uses a ResourceBundle to provide the descriptions. You can provide your own implementation of ConstraintDescriptionResolver to use a different mechanism. In your case, you want to use the message from the constraint annotation as shown in the following example:
ConstraintDescriptions descriptions = new ConstraintDescriptions(CategoryDTO.class, (constraint) -> {
return (String) constraint.getConfiguration().get("message");
});
List<String> descriptionProperty = descriptions.descriptionsForProperty("description");
System.out.println(descriptionProperty);
When executed, the above will output the following:
[Description can't be null, Size must be greater than 2 and less than 30]
If you don't always configure the message attribute, you may want to fall back to the resource bundle resolver, as shown in the following example:
ResourceBundleConstraintDescriptionResolver fallback = new ResourceBundleConstraintDescriptionResolver();
ConstraintDescriptions descriptions = new ConstraintDescriptions(CategoryDTO.class, (constraint) -> {
String message = (String) constraint.getConfiguration().get("message");
if (message != null) {
return message;
}
return fallback.resolveDescription(constraint);
});
With the help of Andy's answer here is the final outcome
import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath;
import static org.springframework.restdocs.snippet.Attributes.key;
import java.util.regex.Pattern;
import org.springframework.restdocs.constraints.ConstraintDescriptions;
import org.springframework.restdocs.constraints.ResourceBundleConstraintDescriptionResolver;
import org.springframework.restdocs.payload.FieldDescriptor;
import org.springframework.util.StringUtils;
public class ConstrainedFields
{
private final ConstraintDescriptions constraintDescriptions;
public ConstrainedFields(Class<?> input) {
ResourceBundleConstraintDescriptionResolver fallback = new ResourceBundleConstraintDescriptionResolver();
this.constraintDescriptions = new ConstraintDescriptions(input, (constraint) -> {
String message = (String) constraint.getConfiguration().get("message");
if (message != null && !Pattern.compile("\\{(.*?)\\}").matcher(message).matches()) {
return message;
}
return fallback.resolveDescription(constraint);
});
}
public FieldDescriptor withPath(String path)
{
return fieldWithPath(path).attributes(key("constraints").value(StringUtils
.collectionToDelimitedString(constraintDescriptions
.descriptionsForProperty(path), ". ")));
}
}

How can I ignore a "$" in a DocumentContent to save in MongoDB?

My Problem is, that if I save a Document with a $ inside the content, Mongodb gives me an exception:
java.lang.IllegalArgumentException: Invalid BSON field name $ xxx
I would like that mongodb ignores the $ character in the content.
My Application is written in java. I read the content of the File and put it as a string into an object. After that the object will be saved with a MongoRepository class.
Someone has any ideas??
Example content
Edit: I heard mongodb has the same problem wit dot. Our Springboot found i workaround with dot, but not for dollar.
How to configure mongo converter in spring to encode all dots in the keys of map being saved in mongo db
If you are using Spring Boot you can extend MappingMongoConverter class and add override methods that do the escaping/unescaping.
#Component
public class MappingMongoConverterCustom extends MappingMongoConverter {
protected #Nullable
String mapKeyDollarReplacemant = "characters_to_replace_dollar";
protected #Nullable
String mapKeyDotReplacement = "characters_to_replace_dot";
public MappingMongoConverterCustom(DbRefResolver dbRefResolver, MappingContext<? extends MongoPersistentEntity<?>, MongoPersistentProperty> mappingContext) {
super(dbRefResolver, mappingContext);
}
#Override
protected String potentiallyEscapeMapKey(String source) {
if (!source.contains(".") && !source.contains("$")) {
return source;
}
if (mapKeyDotReplacement == null && mapKeyDollarReplacemant == null) {
throw new MappingException(String.format(
"Map key %s contains dots or dollars but no replacement was configured! Make "
+ "sure map keys don't contain dots or dollars in the first place or configure an appropriate replacement!",
source));
}
String result = source;
if(result.contains(".")) {
result = result.replaceAll("\\.", mapKeyDotReplacement);
}
if(result.contains("$")) {
result = result.replaceAll("\\$", mapKeyDollarReplacemant);
}
//add any other replacements you need
return result;
}
#Override
protected String potentiallyUnescapeMapKey(String source) {
String result = source;
if(mapKeyDotReplacement != null) {
result = result.replaceAll(mapKeyDotReplacement, "\\.");
}
if(mapKeyDollarReplacemant != null) {
result = result.replaceAll(mapKeyDollarReplacemant, "\\$");
}
//add any other replacements you need
return result;
}
}
If you go with this approach make sure you override the default converter from AbstractMongoConfiguration like below:
#Configuration
public class MongoConfig extends AbstractMongoConfiguration{
#Bean
public DbRefResolver getDbRefResolver() {
return new DefaultDbRefResolver(mongoDbFactory());
}
#Bean
#Override
public MappingMongoConverter mappingMongoConverter() throws Exception {
MappingMongoConverterCustom converter = new MappingMongoConverterCustom(getDbRefResolver(), mongoMappingContext());
converter.setCustomConversions(customConversions());
return converter;
}
.... whatever you might need extra ...
}

Gson: Custom deserialization if certain field is present

I have a class that looks as follows
class Person {
Long id;
String firstName;
int age;
}
and my input either looks like this:
{ "id": null, "firstName": "John", "age": 10 }
or like this:
{ "id": 123 }
The first variant represents a "new" (non-persisted) person and the second refers to a person by its database id.
If id is non-null, I would like to load the object from database during deserialization, otherwise fallback on regular parsing and deserialize it as a new object.
What I've tried: I currently have a JsonDeserializer for database-deserialization, but as I understand it, there is no way to "fall back" on regular parsing. According to this answer I should use a TypeAdapterFactory and the getDelegateAdapter. My problem with this approach is that I'm given a JsonReader (and not for instance a JsonElement) so I can't determine if the input contains a valid id without consuming input.
Any suggestions on how to solve this?
I don't know if I understand your question correctly, but if you already have a JsonDeserializer, you should have a method like this one in there:
public Person deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) { ... }
In this method you have the object context of type JsonDeserializationContext, which allows you to invoke default deserialization on a specified object.
So, you could do something like inside your custom deserializer:
//If id is null...
Person person = context.deserialize(json, Person.class);
See JsonDeserializationContext documentation.
I think I managed to figure this out with the help of the answer over here.
Here is a working type adapter factory:
new TypeAdapterFactory() {
#Override
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
final TypeAdapter<T> delegate = gson.getDelegateAdapter(this, type);
final TypeAdapter<JsonElement> elementAdapter =
gson.getAdapter(JsonElement.class);
// Are we asked to parse a person?
if (!type.getType().equals(Person.class))
return null;
return new TypeAdapter<T>() {
#Override
public T read(JsonReader reader) throws IOException {
JsonElement tree = elementAdapter.read(reader);
JsonElement id = tree.getAsJsonObject().get("id");
if (id == null)
return delegate.fromJsonTree(tree);
return (T) findObj(id.getAsLong());
}
#Override
public void write(JsonWriter writer, T obj) throws IOException {
delegate.write(writer, obj);
}
};
}
}
I haven't fully tested it yet and I'll get back and revise it if needed. (Posting it now to open up for feed back on the approach.)

Get and Set attribute values of a class using aspectJ

I am using aspectj to add some field to a existing class and annotate it also.
I am using load time weaving .
Example :- I have a Class customer in which i am adding 3 string attributes. But my issues is that I have to set some values and get it also before my business call.
I am trying the below approach.
In my aj file i have added the below, my problem is in the Around pointcut , how do i get the attribute and set the attribute.
public String net.customers.PersonCustomer.getOfflineRiskCategory() {
return OfflineRiskCategory;
}
public void net.customers.PersonCustomer.setOfflineRiskCategory(String offlineRiskCategory) {
OfflineRiskCategory = offlineRiskCategory;
}
public String net.customers.PersonCustomer.getOnlineRiskCategory() {
return OnlineRiskCategory;
}
public void net.customers.PersonCustomer.setOnlineRiskCategory(String onlineRiskCategory) {
OnlineRiskCategory = onlineRiskCategory;
}
public String net.customers.PersonCustomer.getPersonCommercialStatus() {
return PersonCommercialStatus;
}
public void net.customers.PersonCustomer.setPersonCommercialStatus(String personCommercialStatus) {
PersonCommercialStatus = personCommercialStatus;
}
#Around("execution(* net.xxx.xxx.xxx.DataMigration.populateMap(..))")
public Object invoke(ProceedingJoinPoint joinPoint) throws Throwable {
Object arguments[] = joinPoint.getArgs();
if (arguments != null) {
HashMap<String, String> hMap = (HashMap) arguments[0];
PersonCustomer cus = (PersonCustomer) arguments[1];
return joinPoint.proceed();
}
If anyone has ideas please let me know.
regards,
FT
First suggestion, I would avoid mixing code-style aspectj with annotation-style. Ie- instead of #Around, use around.
Second, instead of getting the arguments from the joinPoint, you should bind them in the pointcut:
Object around(Map map, PersonCustomer cust) :
execution(* net.xxx.xxx.xxx.DataMigration.populateMap(Map, PersonCustomer) && args(map, cust) {
...
return proceed(map, cust);
}
Now, to answer your question: you also need to use intertype declarations to add new fields to your class, so do something like this:
private String net.customers.PersonCustomer.OfflineRiskCategory;
private String net.customers.PersonCustomer.OnlineRiskCategory;
private String net.customers.PersonCustomer.PersonCommercialStatus;
Note that the private keyword here means private to the aspect, not to the class that you declare it on.