Expand my Community achievements bar.

Dive into Adobe Summit 2024! Explore curated list of AEM sessions & labs, register, connect with experts, ask questions, engage, and share insights. Don't miss the excitement.
SOLVED

Need Jackson Exporter JSON response within SlingModel Class

Avatar

Level 2

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;
    }
1 Accepted Solution

Avatar

Correct answer by
Employee Advisor

Hi@aman_goyal_15 

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;

 

View solution in original post

16 Replies

Avatar

Employee Advisor

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

 

Avatar

Level 2
Thanks for your response. I tried the above approach but looks like it is not working. It is giving the null response while exporting model - String s = modelFactory.exportModel(buttonModel,ExporterConstants.SLING_MODEL_EXPORTER_NAME,String.class,options);

Avatar

Employee Advisor

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. 

Avatar

Level 2

Attached the complete Java class in the query placeholder. Please check.

Avatar

Employee Advisor
Just try this piece of code - s = modelFactory.exportModel(this,"jackson",String.class,options);

Avatar

Level 1

Its working for me, in Sling Model class - use @Self for Model class variable and pass to ModelFactory Exporter.

Avatar

Level 2
response is same with 'this' .. observing "Method threw 'java.lang.StackOverflowError' exception." when model factory tries to export.

Avatar

Community Advisor

@aman_goyal_15 

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());    
        }
        ...
    }

 

Avatar

Level 2
Thanks for your response. Servlet is fine to expose the json once we get it. But I want to check for the approach to get the jsonString for Button2 model.

Avatar

Correct answer by
Employee Advisor

Hi@aman_goyal_15 

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;

 

Avatar

Level 2
Thanks for your response. Sling Model is not working when we are trying to get the Button2 object with @Deleted Account and @Via. I was checking on the @Via(type = ResourceSuperType.class), it is basically when we want to override the core component sling model. https://github.com/adobe/aem-core-wcm-components/wiki/Delegation-Pattern-for-Sling-Models#step-2-create-a-custom-sling-model-for-the-page-headline-component

Avatar

Employee Advisor

@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.

Avatar

Level 2
Thank You Shelly .. It is working as expected. Passing 'this' reference as an argument to mapper.valueToTree is giving the actual Jackson exported model.json response.

Avatar

Community Advisor

@aman_goyal_15 

Can you try 

@ScriptVariable
Resource resource;

and then

resource.adaptTo(Button2Impl.class);

 

Avatar

Administrator
Thank you for sharing the solution with community.


Kautuk Sahni