Hi Team,
Usecase: Need to utilize model JSON response in the component sightly file.
Basically, we use Jackson Exporter to get JSON response for SlingModel but it's a servlet call on the component resource. But my requirement is to get the JSON response for SlingModel from one of the getter functions of the Sling Model.
something like -
@Model(adaptables = {
SlingHttpServletRequest.class
}, adapters = {
Button2.class,
ComponentExporter.class
}, resourceType = Button2Impl.RESOURCE_TYPE)
@Exporter(name = ExporterConstants.SLING_MODEL_EXPORTER_NAME, extensions = ExporterConstants.SLING_MODEL_EXTENSION)
public class Button2Impl implements Button2 {
@ValueMapValue(injectionStrategy = InjectionStrategy.OPTIONAL)
private String label;
public String getJsonString() {
String json = "logic to get model json response like sling model exporter;
return json;
}
}
<sly data-sly-use.buttonProvider="${'com.sample.general.models.Button2'}">
<span> ${buttonProvider.jsonString} </span>
</sly>
Could anyone suggest how to achieve the above scenario?
Thanks,
Aman
SlingModel Implementation Class -
import com.adobe.cq.export.json.ComponentExporter;
import com.adobe.cq.export.json.ExporterConstants;
import com.day.cq.wcm.api.Page;
import com.day.cq.wcm.api.PageManager;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.sample.sample.data_providers.general.models.Button2;
import com.sample.sample.services.utils.LinkUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.felix.scr.annotations.Reference;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.api.resource.ResourceResolverFactory;
import org.apache.sling.models.annotations.Exporter;
import org.apache.sling.models.annotations.Model;
import org.apache.sling.models.annotations.injectorspecific.InjectionStrategy;
import org.apache.sling.models.annotations.injectorspecific.OSGiService;
import org.apache.sling.models.annotations.injectorspecific.SlingObject;
import org.apache.sling.models.annotations.injectorspecific.ValueMapValue;
import org.apache.sling.models.factory.ExportException;
import org.apache.sling.models.factory.MissingExporterException;
import org.apache.sling.models.factory.ModelFactory;
import java.util.HashMap;
import java.util.Map;
@Model(adaptables = {
SlingHttpServletRequest.class
}, adapters = {
Button2.class,
ComponentExporter.class
}, resourceType = Button2Impl.RESOURCE_TYPE)
@Exporter(name = ExporterConstants.SLING_MODEL_EXPORTER_NAME, extensions = ExporterConstants.SLING_MODEL_EXTENSION)
public class Button2Impl
implements Button2
{
public static final String RESOURCE_TYPE = "sample/components/general/button-2-0";
private ResourceResolverFactory resourceResolverFactory;
@SlingObject
private ResourceResolver resourceResolver;
@ValueMapValue(injectionStrategy = InjectionStrategy.OPTIONAL)
private String iconAlignment;
@ValueMapValue(injectionStrategy = InjectionStrategy.OPTIONAL)
private String buttonAlignment;
@ValueMapValue(injectionStrategy = InjectionStrategy.OPTIONAL)
private String marginBottom;
@SlingObject
private Resource resource;
@SlingObject
private SlingHttpServletRequest request;
@OSGiService
ModelFactory modelFactory;
public String getIconAlignment() { return iconAlignment; }
public String getButtonAlignment() { return buttonAlignment; }
@JsonIgnore
public String getMarginBottom() { return marginBottom; }
public String getExportedType() {
return resource.getResourceType();
}
public String getJsonString() {
Button2 buttonModel = request.adaptTo(Button2Impl.class); // tried adapting with resource as well but no luck
Map<String,String> options = new HashMap();
String s = null;
try {
s = modelFactory.exportModel(buttonModel,ExporterConstants.SLING_MODEL_EXPORTER_NAME,String.class,options);
} catch (ExportException e) {
e.printStackTrace();
} catch (MissingExporterException e) {
e.printStackTrace();
}
return s;
}
}
The solution to this query -
@JsonIgnore
public String getJsonString() {
ObjectMapper mapper = new ObjectMapper().configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
JsonNode node = mapper.valueToTree(this);
String jsonString =null;
try {
jsonString = mapper.writeValueAsString(node);
} catch (JsonProcessingException e) {
e.getMessage();
}
return jsonString;
}
Solved! Go to Solution.
Another option is to use 'ObjectMapper' (com.fasterxml.jackson.databind.ObjectMapper) to map the model object as json. Add below method in your sling model class and either map the Button resource object or custom map object (with the specific value you need in json).
public String getJsonString() {
ObjectMapper mapper = new ObjectMapper();
JsonNode node = mapper.valueToTree(<button object>);
String jsonString ="no value";
try {
jsonString = mapper.writeValueAsString(node);
} catch (JsonProcessingException e) {
LOG.error("Cannot output json", e);
}
return jsonString;
}
You need to have the 'Button' object in the model
@Self @Via(type = ResourceSuperType.class)
private Button2 button;
You can use below code reference. Its a JSP code, you can convert it to Java and use in your sling model. The code uses Sling ModelFactory API to export a sling model.
<%
//Get reference to model factory
org.apache.sling.models.factory.ModelFactory mf = sling.getService(org.apache.sling.models.factory.ModelFactory.class);
//Get sling model
Title title = slingRequest.adaptTo(com.adobe.cq.wcm.core.components.models.Title.class);
Map<String,String> options = new HashMap();
//Export and print json
String s = mf.exportModel(title,"jackson",String.class,options);
out.println(s);
%>
Output-
{"id":"title-d61980eb4b","linkDisabled":false,"type":"h3","text":"Standard",":type":"core/wcm/components/title/v2/title"}
Views
Replies
Total Likes
Can you share more lines of code ? I hope the buttonModel object is not null. The above code works for me. I tried it for Title core component and it works.
Views
Replies
Total Likes
Attached the complete Java class in the query placeholder. Please check.
Views
Replies
Total Likes
Views
Replies
Total Likes
Its working for me, in Sling Model class - use @Self for Model class variable and pass to ModelFactory Exporter.
Views
Replies
Total Likes
Views
Replies
Total Likes
Another way is to do something like this within the servlet.
1. Set the response content-type.
2. Using business logic, locate the Sling resource you need.
3. Adapt the Sling resource to Button2Impl.
4. output the JSON string.
Example:
@Override
protected void doGet(SlingHttpServletRequest request, SlingHttpServletResponse response) throws IOException {
response.setContentType(APPLICATION_JSON_UTF8);
Resource buttonResource = request.getResourceResolver().getResource("/content/my-page/jcr:content/par/button");
BUtton2 button2 = buttonResource.adaptTo(Button2Impl.class);
if (button2 != null) {
response.getWriter().write(button2.getJsonString());
}
...
}
Views
Replies
Total Likes
Views
Replies
Total Likes
Another option is to use 'ObjectMapper' (com.fasterxml.jackson.databind.ObjectMapper) to map the model object as json. Add below method in your sling model class and either map the Button resource object or custom map object (with the specific value you need in json).
public String getJsonString() {
ObjectMapper mapper = new ObjectMapper();
JsonNode node = mapper.valueToTree(<button object>);
String jsonString ="no value";
try {
jsonString = mapper.writeValueAsString(node);
} catch (JsonProcessingException e) {
LOG.error("Cannot output json", e);
}
return jsonString;
}
You need to have the 'Button' object in the model
@Self @Via(type = ResourceSuperType.class)
private Button2 button;
Views
Replies
Total Likes
@aman_goyal_15Yes that's the case when you're extending core component model. For your custom component model, you can directly pass the resource and it will work (unless resource is null).
The constraint in this approach would be that it would not return the values post model transformation but the original values on the resource and trying to access the exported model object from within the model will lead to infinite recursion.
As I mentioned earlier you can pass the custom object as per your needs (json structure/ fields as needed) in the ObjectMapper and below logic would work. It gives the flexibility in terms of what fields you want to include/ exclude in the json and pipelines can be attached to the mapper.
@JsonIgnore
public String getJsonString() {
ObjectMapper mapper = new ObjectMapper().configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
JsonNode node = mapper.valueToTree(new JsonConfig(getTitle(), getPretitle()));
String jsonString ="no value";
try {
jsonString = mapper.writeValueAsString(node);
} catch (JsonProcessingException e) {
LOG.error("Cannot output json", e);
}
return jsonString;
}
Here I created the custom object/ pojo as an inner class
private class JsonConfig {
private String title;
private String pretitle;
public JsonConfig() {};
public JsonConfig(String title, String pretitle) {
this.title = title;
this.pretitle = pretitle;
}
public String getTitle() {
return title;
}
public String getPretitle() {
return pretitle;
}
}
Please add @JsonIgnore annotations to all the methods that doesn't need to be part of .model.json request.
Views
Replies
Total Likes
Views
Replies
Total Likes
Can you try
@ScriptVariable
Resource resource;
and then
resource.adaptTo(Button2Impl.class);
Views
Replies
Total Likes