Expose JSON for All Components in AEM Page | Community
Skip to main content
Level 2
May 1, 2025
Solved

Expose JSON for All Components in AEM Page

  • May 1, 2025
  • 5 replies
  • 1204 views
Hi All,

 

1)
I am trying to expose all components on the page in JSON format. When I use OOTB feature pageUrl.model.json, I see a lot of unnecessary data being shown, such as allowed components and other irrelevant details.
 
I want to expose a clean JSON and have already implemented Sling Models using jackson exporter,  However, when I execute pageUrl.model.json, it doesn’t return the custom properties I've added in the Sling Model.
 
On the other hand, when I use componentPath.model.json, it returns all the custom properties from the Sling Model.
I'm trying to understand how to expose all component data as JSON via pageUrl.model.json. 



2)

Additionally, I attempted to create a custom Sling Model for the page component, but this doesn't provide the JSON for all the components within the page. I thought about traversing through each child node and adapting each resource to a Sling Model, but the code below always returns null. The model doesn’t resolve to the correct Sling Model.

 

Object model = modelFactory.createModel(resource, Object.class); // This line always returns null
Text model = modelFactory.createModel(resource, Text.class); // This works mean resourceType Mapping is correct, but I want to dynamically bind the class name since I don't know which component is placed on page

 

Your input would be very helpful!
Best answer by AmitVishwakarma

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

5 replies

Level 4
May 1, 2025

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.

Sku4Author
Level 2
May 1, 2025

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); @586265 private Resource resource; @586265 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; }
SantoshSai
Community Advisor
Community Advisor
May 1, 2025

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!

Santosh Sai
AmitVishwakarma
Community Advisor
AmitVishwakarmaCommunity AdvisorAccepted solution
Community Advisor
May 1, 2025

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

Sku4Author
Level 2
May 1, 2025

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! 😊