Skip to content

Commit

Permalink
#1258 - Add convenience methods to controller specs to work with JSON
Browse files Browse the repository at this point in the history
  • Loading branch information
ipolevoy committed Dec 14, 2022
1 parent d1b6bb9 commit 39b6817
Show file tree
Hide file tree
Showing 7 changed files with 183 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,21 @@ public RequestBuilder content(String content) {
return content(content.getBytes());
}


/**
* Convenience method. Expects the content to be some JSON document (object or array).
* This method does NOT check the validity of the document. It is a responsibility of the controller.
* <br>
* This method will automatically set the header "Content-Type" to be "application/json", and that is the convenience.
*
* @param content JSON document is expected
* @return self.
*/
public RequestBuilder json(String content) {
contentType("application/json");
return content(content.getBytes());
}

/**
* Use to post content to a tested controller.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@


import org.javalite.common.Convert;
import org.javalite.json.JSONList;
import org.javalite.json.JSONMap;
import org.javalite.test.jspec.TestException;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
Expand All @@ -27,8 +29,6 @@

import java.util.*;

import static org.javalite.common.Util.blank;

/**
* This class is not used directly in applications.
*
Expand Down Expand Up @@ -80,6 +80,25 @@ protected String responseContent(){
}
}


/**
* Provides content generated by controller in the {@link JSONMap} format, after controller execution - if views were integrated.
*
* @return content generated by controller/view already converted to <code>JSONMap</code?> object.
*/
protected JSONMap responseJSONMap(){
return new JSONMap(responseContent());
}

/**
* Provides content generated by controller in the {@link JSONList} format, after controller execution - if views were integrated.
*
* @return content generated by controller/view already converted to <code>JSONList</code?> object.
*/
protected JSONList responseJSONList(){
return new JSONList(responseContent());
}

/**
* Provides content generated by controller as bytes.
*
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package app.controllers;

import org.javalite.activeweb.AppController;
import org.javalite.activeweb.annotations.POST;

import org.javalite.json.JSONMap;

public class JSONController extends AppController {

@POST
public void index1(){
JSONMap request = getRequestJSONMap();
request.put("last_name", "Doe");
respondJSON(request);
}

@POST
public void index2(){
respondJSON(getRequestJSONList());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package app.controllers;

import org.javalite.activeweb.ControllerSpec;
import org.javalite.json.JSONList;
import org.javalite.json.JSONMap;
import org.junit.Test;

public class JSONControllerSpec extends ControllerSpec {

@Test
public void shouldPostJSONMap(){

String doc = """
{
"first_name" : "John"
}
""";
request().json(doc).post("index1");

JSONMap response = responseJSONMap();

the(response).shouldContain("first_name");
the(response).shouldContain("last_name");

the(response.get("first_name")).shouldContain("John");
the(response.get("last_name")).shouldContain("Doe");

the(contentType()).shouldBeEqual("application/json");
}

@Test
public void shouldPostJSONList(){

String doc = """
[
"John" , "Doe"
]
""";
request().json(doc).post("index2");

JSONList response = responseJSONList();

the(response).shouldContain("John");
the(response).shouldContain("Doe");
the(contentType()).shouldBeEqual("application/json");
}

@Test
public void shouldPostJSONString(){

String doc = """
[
"John" , "Doe"
]
""";
request().json(doc).post("index2");

JSONList response = responseJSONList();

the(response).shouldContain("John");
the(response).shouldContain("Doe");
the(contentType()).shouldBeEqual("application/json");
}
}
42 changes: 40 additions & 2 deletions activeweb/src/main/java/org/javalite/activeweb/HttpSupport.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
import org.javalite.common.Convert;
import org.javalite.json.JSONHelper;
import org.javalite.common.Util;
import org.javalite.json.JSONList;
import org.javalite.json.JSONMap;
import org.jsoup.Jsoup;
import org.jsoup.safety.Safelist;
import org.slf4j.Logger;
Expand Down Expand Up @@ -491,8 +493,7 @@ protected <T extends AppController> HttpBuilder redirect(Class<T> controllerClas
}

/**
* This method will send the text to a client verbatim. It will not use any layouts. Use it to build app.services
* and to support AJAX.
* This method will send the text to a client verbatim. It will not use any layouts. Use it to support AJAX or API functions.
*
* @param text text of response.
* @return {@link HttpSupport.HttpBuilder}, to accept additional information.
Expand All @@ -502,10 +503,28 @@ protected HttpBuilder respond(String text){
text = "null";
}
DirectResponse resp = new DirectResponse(text);
String contentType = RequestContext.getHttpRequest().getHeader("Content-Type");
//TODO: is this some spaghetti code? Seems we have Content-type in RequestContext as well as in ControllerResponse?
if(contentType != null){
resp.setContentType(contentType);
}

RequestContext.setControllerResponse(resp);
return new HttpBuilder(resp);
}

/**
* Serializes the argument as a JSON string and sets the "Content-Type" header to "application/json".
* See {@link HttpSupport#respond(String)}.
*
* @param object object to serialize to JSON
* @return self
*/
protected HttpBuilder respondJSON(Object object){
header("Content-Type", "application/json");
return respond(JSONHelper.toJsonString(object));
}

/**
* Convenience method for downloading files. This method will force the browser to find a handler(external program)
* for this file (content type) and will provide a name of file to the browser. This method sets an HTTP header
Expand Down Expand Up @@ -1340,6 +1359,25 @@ protected InputStream getRequestStream() {
}
}


/**
* Reads entire request data and converts it to {@link JSONMap}.
*
* @return data sent by client as {@link JSONMap}.
*/
protected JSONMap getRequestJSONMap() {
return new JSONMap(getRequestString());
}

/**
* Reads entire request data and converts it to {@link JSONList}.
*
* @return data sent by client as {@link JSONList}.
*/
protected JSONList getRequestJSONList() {
return new JSONList(getRequestString());
}

/**
* Reads entire request data as String. Do not use for large data sets to avoid
* memory issues, instead use {@link #getRequestInputStream()}.
Expand Down
13 changes: 13 additions & 0 deletions javalite-common/src/main/java/org/javalite/json/JSONList.java
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,15 @@
* @see JSONList#getMap(int)
*/
public class JSONList extends ArrayList {


public JSONList(List jsonList){
super(jsonList);
}

/**
* @param jsonList JSON array as string.
*/
public JSONList(String jsonList){
super(JSONHelper.toList(jsonList));
}
Expand Down Expand Up @@ -145,4 +151,11 @@ public String toString() {
public String toJSON(boolean pretty){
return JSONHelper.toJsonString(this, pretty);
}

/**
* @return a JSON representation of this object
*/
public String toJSON(){
return JSONHelper.toJsonString(this, false);
}
}
10 changes: 9 additions & 1 deletion javalite-common/src/main/java/org/javalite/json/JSONMap.java
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,15 @@ public JSONMap(String ... keysAndValues){
super(map(keysAndValues));
}

/**
* Converts the String into a JSONMap.
*
* @param jsonString JSON Object document as string.
*/
public JSONMap(String jsonString){
super(JSONHelper.toJSONMap(jsonString));
}

public JSONMap(){}

/**
Expand Down Expand Up @@ -285,7 +294,6 @@ public String toJSON(boolean pretty){
}

/**
*
* @return a JSON representation of this object
*/
public String toJSON(){
Expand Down

0 comments on commit 39b6817

Please sign in to comment.