Expand my Community achievements bar.

Adobe Summit 2025: AEM Session Recordings Are Live! Missed a session or want to revisit your favorites? Watch the latest recordings now.
SOLVED

Expose JSON for All Components in AEM Page

Avatar

Level 1
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!
Topics

Topics help categorize Community content and increase your ability to discover relevant content.

1 Accepted Solution

Avatar

Correct answer by
Community Advisor

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

View solution in original post

5 Replies

Avatar

Level 4

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.

Avatar

Level 1

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;
    }

Avatar

Community Advisor

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

AEM BlogsLinkedIn


Avatar

Correct answer by
Community Advisor

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

Avatar

Level 1

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!