2)
Solved! Go to Solution.
Topics help categorize Community content and increase your ability to discover relevant content.
Views
Replies
Total Likes
Hi @Sku4 ,
Your Core Challenge:
You're using:
modelFactory.createModel(res, Object.class);
which doesn’t dynamically resolve to the specific Sling Model of the component. This happens because Object.class is not a base type Sling Model can resolve to. Sling doesn’t know what model to adapt to unless a class is explicitly defined or the model is marked with an interface.
Solution: Use a Common Interface for Your Component Models
Define a base interface (e.g., ComponentExporterModel) for all your component Sling Models.
public interface ComponentExporterModel {
// marker interface, or add common getters if needed
}
Update your component models to implement this interface:
@Model(adaptables = Resource.class,
resourceType = "project/components/mycomponent",
defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL,
adapters = ComponentExporterModel.class)
@Exporter(name = "jackson", extensions = "json")
public class MyComponentModel implements ComponentExporterModel {
@Inject
@JsonProperty
private String title;
}
In your PageModel, change this:
Object model = modelFactory.createModel(res, Object.class);
To this:
if (modelFactory.canCreateFromAdaptable(res, ComponentExporterModel.class)) {
ComponentExporterModel model = modelFactory.createModel(res, ComponentExporterModel.class);
String jsonString = modelFactory.exportModel(model, "jackson", String.class, null);
return new JSONObject(jsonString);
}
Benefits of This Approach:
Scalable: You don't have to update your PageModel when new components are added.
Cleaner JSON: No OOTB junk like cq:allowedComponents.
Reusable: Component models stay modular.
Secure: Only models implementing your interface are exported.
Regards,
Amit
Hi @Sku4 ,
You can create a Sling Model to represent the JSON you want to expose. I recently worked on a similar implementation.
@Model(adaptables = Resource.class, resourceType = "yourproject/components/page", defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL)
@Exporter(name = "jackson", extensions = "json")
public class PageModel {
@Self
private Resource resource;
private Resource contentResource;
private String title;
private String description;
private List<ComponentModel> components;
@PostConstruct
protected void init() {
contentResource = resource.getChild("root").getChild("container").getChild("container");
if (contentResource != null) {
title = contentResource.getValueMap().get("jcr:title", String.class);
description = contentResource.getValueMap().get("jcr:description", String.class);
components = new ArrayList<>();
for (Resource child : contentResource.getChildren()) {
String componentTitle = child.getValueMap().get("jcr:title", "");
String resourceType = child.getResourceType();
String path = child.getPath();
components.add(new ComponentModel(componentTitle, resourceType, path));
}
}
}
public String getPath() {
return resource.getPath();
}
public String getTitle() {
return title;
}
public String getDescription() {
return description;
}
public List<ComponentModel> getComponents() {
return components;
}
}
And ComponentModel will be like below:-
public class ComponentModel {
private final String title;
private final String resourceType;
private final String path;
public ComponentModel(String title, String resourceType, String path) {
this.title = title;
this.resourceType = resourceType;
this.path = path;
}
public String getTitle() {
return title;
}
public String getResourceType() {
return resourceType;
}
public String getPath() {
return path;
}
}
Try this and let me know if it works.
Thanks.
Views
Replies
Total Likes
Thank you for your response.
I have many different components on the page, and I would like to generate a JSON representation of all of them. Each component has its own individual Sling Model, but the page component itself uses a generic Sling Model. We want to avoid modifying the page model every time a new property or component is added. I'm looking for a more generic and scalable solution. Below is my current code — please let me know if you have any suggestions or ideas.
@Model(adaptables = Resource.class, resourceType = "project/components/page")
@Exporter(name = "jackson", extensions = "json")
public class PageModel {
private static final Logger LOGGER = LoggerFactory.getLogger(PageModel.class);
@inject
private Resource resource;
@inject
private ModelFactory modelFactory;
private JSONArray componentsJson;
@PostConstruct
protected void init() {
componentsJson = new JSONArray();
Resource contentResource = resource;
if (contentResource != null) {
traverse(contentResource);
} else {
LOGGER.warn("No jcr:content found under page: {}", resource.getPath());
}
}
public JSONObject getPageDataAsJson() throws JSONException {
JSONObject json = new JSONObject();
json.put("components", componentsJson);
return json;
}
private void traverse(Resource res) {
if (res == null) return;
JSONObject componentJson = exportComponent(res);
if (componentJson != null) {
componentsJson.put(componentJson);
}
for (Resource child : res.getChildren()) {
traverse(child);
}
}
private JSONObject exportComponent(Resource res) {
try {
Object model = modelFactory.createModel(res, Object.class); // this line is not adopting to corresponding component sling model
if (model != null) {
String jsonString = modelFactory.exportModel(model, "jackson", String.class, null);
return new JSONObject(jsonString);
} else {
LOGGER.debug("Resource {} does not adapt to any model", res.getPath());
}
} catch ( org.json.JSONException e) {
LOGGER.warn("Failed to export model for resource {}: {}", res.getPath(), e.getMessage());
} catch (Exception e) {
LOGGER.error("Unexpected error during model export for {}: {}", res.getPath(), e.getMessage());
}
return null;
}
Views
Replies
Total Likes
Hi @Sku4,
Have you tried using ModelFactory.canCreateFromAdaptable() and cast to SlingModel interface?
Refer below example where I have tried to modify your code:
package com.project.core.models;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.models.annotations.Exporter;
import org.apache.sling.models.annotations.Model;
import org.apache.sling.models.factory.ModelFactory;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.osgi.service.component.annotations.Reference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.PostConstruct;
import javax.inject.Inject;
@Model(adaptables = Resource.class, resourceType = "project/components/page")
@Exporter(name = "jackson", extensions = "json")
public class PageModel {
private static final Logger LOGGER = LoggerFactory.getLogger(PageModel.class);
@Inject
private Resource resource;
@Inject
private ModelFactory modelFactory;
private JSONArray componentsJson;
@PostConstruct
protected void init() {
componentsJson = new JSONArray();
if (resource != null) {
traverse(resource);
} else {
LOGGER.warn("Resource is null for page model");
}
}
private void traverse(Resource res) {
if (res == null) return;
JSONObject componentJson = exportComponent(res);
if (componentJson != null) {
componentsJson.put(componentJson);
}
for (Resource child : res.getChildren()) {
traverse(child);
}
}
private JSONObject exportComponent(Resource res) {
try {
if (modelFactory.canCreateFromAdaptable(res, Object.class)) {
Object model = modelFactory.createModel(res, Object.class);
if (model != null) {
String jsonString = modelFactory.exportModel(model, "jackson", String.class, null);
return new JSONObject(jsonString);
}
}
} catch (JSONException e) {
LOGGER.warn("JSON error for resource {}: {}", res.getPath(), e.getMessage());
} catch (Exception e) {
LOGGER.error("Export error for resource {}: {}", res.getPath(), e.getMessage());
}
return null;
}
@JsonProperty("components")
public JSONArray getComponentsJson() {
return componentsJson;
}
}
Also, it’s better to return the final JSON via a @JsonProperty getter so that AEM returns it directly when you hit .model.json
Make sure each component has a properly registered Sling Model with:
@Model(adaptables = Resource.class, resourceType = "project/components/mycomponent", defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL)
@Exporter(name = "jackson", extensions = "json")
public class MyComponentModel {
@Inject
@JsonProperty
private String title;
}
Hope that helps!
Hi @Sku4 ,
Your Core Challenge:
You're using:
modelFactory.createModel(res, Object.class);
which doesn’t dynamically resolve to the specific Sling Model of the component. This happens because Object.class is not a base type Sling Model can resolve to. Sling doesn’t know what model to adapt to unless a class is explicitly defined or the model is marked with an interface.
Solution: Use a Common Interface for Your Component Models
Define a base interface (e.g., ComponentExporterModel) for all your component Sling Models.
public interface ComponentExporterModel {
// marker interface, or add common getters if needed
}
Update your component models to implement this interface:
@Model(adaptables = Resource.class,
resourceType = "project/components/mycomponent",
defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL,
adapters = ComponentExporterModel.class)
@Exporter(name = "jackson", extensions = "json")
public class MyComponentModel implements ComponentExporterModel {
@Inject
@JsonProperty
private String title;
}
In your PageModel, change this:
Object model = modelFactory.createModel(res, Object.class);
To this:
if (modelFactory.canCreateFromAdaptable(res, ComponentExporterModel.class)) {
ComponentExporterModel model = modelFactory.createModel(res, ComponentExporterModel.class);
String jsonString = modelFactory.exportModel(model, "jackson", String.class, null);
return new JSONObject(jsonString);
}
Benefits of This Approach:
Scalable: You don't have to update your PageModel when new components are added.
Cleaner JSON: No OOTB junk like cq:allowedComponents.
Reusable: Component models stay modular.
Secure: Only models implementing your interface are exported.
Regards,
Amit
Thank you, Shivam, Amit, and Santhosh, for your responses. I truly appreciate you taking the time to help. @AmitVishwakarma — your solution worked perfectly. Thanks again for your support!
Views
Replies
Total Likes
Views
Likes
Replies
Views
Likes
Replies
Views
Likes
Replies