I am writing a program which accepts a JSON input with the following format from client:
{
"campaignID": 1,
"clientID": 1,
"pmapID": 1,
"ward": "1-Bedded (Private)",
"age": 20,
"attr1": "EXA1(A)",
"attr2": "EO",
"attr3": "11/02/2012",
"attr4": "SIN",
"attr5": "N",
"attr6": "Y"
}
I'd like to read the JSON input, save all the attributes into local variables (String, int, ...) and finally respond with a POST("JSON") which will return a single float/double value (e.g. {"PMC": 30.12} ).
public class RestletApplication extends Application
{
#Override
public synchronized Restlet createInboundRoot()
{
Router router = new Router(getContext());
router.attach("/pmc/calculate", PMCResource.class);
return router;
}
}
I have written the function so far but am lost how to read the JSON input:
public class PMCResource extends ServerResource
{
#Post("JSON")
public Representation post(Representation entity) throws ResourceException {
try {
if (entity.getMediaType().isCompatible(MediaType.APPLICATION_JSON))
{
// Read JSON file and parse onto local variables
// Do processing & return a float value
}
} catch (Exception e) {
getResponse().setStatus(Status.SERVER_ERROR_INTERNAL);
}
}
}
5 May 2016 - Edited the resource class
// Imports
public class PMCResource extends ServerResource
{
static Logger LOGGER = LoggerFactory.getLogger(PMCResource.class);
#Override
#Post("JSON")
public Representation post(Representation entity) throws ResourceException
{
PMCMatrixDAO matrix = new PMCMatrixDAOImpl();
JsonObjectBuilder response = Json.createObjectBuilder();
try
{
if (entity.getMediaType().isCompatible(MediaType.APPLICATION_JSON))
{
InputStream is = new FileInputStream(getClass().getResource("/input.json").getFile());
try (JsonReader reader = Json.createReader(is)) {
JsonObject obj = reader.readObject();
double result = matrix.calculatePMC(obj);
response.add("PMC", result);
}
}
} catch (Exception e) {
getResponse().setStatus(Status.SERVER_ERROR_INTERNAL);
}
return new StringRepresentation(response.build().toString());
}
}
The Implementation class
public class PMCMatrixDAOImpl implements PMCMatrixDAO
{
public double calculatePMC(JsonObject obj)
{
int campaignID = obj.getInt("campaignID");
int clientID = obj.getInt("clientID");
int pmapID = obj.getInt("pmapID");
String ward = obj.getString("ward");
int age = obj.getInt("age");
String attr1 = obj.getString("attr1");
String attr2 = obj.getString("attr2");
String attr3 = obj.getString("attr3");
String attr4 = obj.getString("attr4");
String attr5 = obj.getString("attr5");
String attr6 = obj.getString("attr6");
// SQL processing
double dPMC = sqlQueryCall(...);
return dPMC;
}
}
In order to parse your JSON file, and since you're using Maven I'll assume you have it on your classpath, you can do it using a FileInputStream or a FileReader. So, assuming your JSON file is called input.json and it is on the root of your src/main/resources folder, you can load it the following way:
using a FileInputStream:
InputStream is = new FileInputStream(getClass().getResource("/input.json").getFile());
try (JsonReader reader = Json.createReader(is)) {
// file processing is done here
}
using a FileReader:
FileReader fr = new FileReader(getClass().getResource("/input.json").getFile());
try (JsonReader reader = Json.createReader(fr)) {
// file processing is done here
}
Ok, so now that we have our JsonReader created, lets retrieve the contents of our JSON file:
InputStream is = new FileInputStream(getClass().getResource("/input.json").getFile());
try (JsonReader reader = Json.createReader(is)) {
JsonObject obj = reader.readObject();
// retrieve JSON contents
int campaingID = obj.getInt("campaignID");
int clientID = obj.getInt("clientID");
int pmapID = obj.getInt("pmapID");
String ward = obj.getString("ward");
int age = obj.getInt("age");
String attr1 = obj.getString("attr1");
String attr2 = obj.getString("attr2");
String attr3 = obj.getString("attr3");
String attr4 = obj.getString("attr4");
String attr5 = obj.getString("attr5");
String attr6 = obj.getString("attr6");
}
As an alternative of having several variables across your method, you could create a simple POJO, having those variable as attributes, and then populate it using Jackson:
public class MyPojo {
private int campaingID;
private int clientID;
private int pmapID;
private String ward;
private int age;
private String attr1;
private String attr2;
private String attr3;
private String attr4;
private String attr5;
private String attr6;
// getters & setters
}
Finally, in order to send the response back to your client, you could do this:
JsonObject response = Json.createObjectBuilder().add("PMC", 30.12).build();
return new StringRepresentation(response.toString());
So, the entire solution could look like this:
#Override
#Post("JSON")
public Representation post(Representation entity) throws ResourceException {
JsonObjectBuilder response = Json.createObjectBuilder();
try {
if (entity.getMediaType().isCompatible(MediaType.APPLICATION_JSON)) {
InputStream is = new FileInputStream(getClass().getResource("/input.json").getFile());
try (JsonReader reader = Json.createReader(is)) {
JsonObject obj = reader.readObject();
// retrieve JSON contents
int campaingID = obj.getInt("campaignID");
int clientID = obj.getInt("clientID");
int pmapID = obj.getInt("pmapID");
String ward = obj.getString("ward");
int age = obj.getInt("age");
String attr1 = obj.getString("attr1");
String attr2 = obj.getString("attr2");
String attr3 = obj.getString("attr3");
String attr4 = obj.getString("attr4");
String attr5 = obj.getString("attr5");
String attr6 = obj.getString("attr6");
}
// Do processing & execute your SQL query call here
double result = sqlQueryCall(...);
response.add("PMC", result);
}
} catch (Exception e) {
getResponse().setStatus(Status.SERVER_ERROR_INTERNAL);
}
return new StringRepresentation(response.build().toString());
}
As a side note, the JsonReader class belongs to the Java EE API which, for compiling purposes it's okay. Although, for running purposes, one requires the declaration of a JSON-API implementation dependency in one's Maven project. For instance:
<dependency>
<groupId>org.glassfish</groupId>
<artifactId>javax.json</artifactId>
<version>1.0.4</version>
</dependency>
Below is the way one can communicate to the REST web service through a client:
Create a simple POJO object that will contain the information to send, as mentioned above (MyPojo).
Your REST service would look something like this:
public class PMCResource extends ServerResource {
static Logger LOGGER = Logger.getLogger(RestletMain.class.getName());
#Post("JSON")
public Representation post(MyPojo entity) throws ResourceException {
PMCMatrixDAO matrix = new PMCMatrixDAOImpl();
JsonObjectBuilder response = Json.createObjectBuilder();
try {
double result = matrix.calculatePMC(entity);
response.add("PMC", result);
} catch (Exception e) {
getResponse().setStatus(Status.SERVER_ERROR_INTERNAL);
}
return new StringRepresentation(response.build().toString());
}
}
Modify your PMCMatrixDAOImpl in order to process your POJO:
public double calculatePMC(MyPojo pojo) {
(...)
}
Create a client that allows you to test your REST service:
public class PMCResourceMain {
public static void main(String[] args) {
// take into account the context-root, if exists, and path to your REST service
ClientResource resource = new ClientResource("http://<host>:<port>");
MyPojo myPojo = new MyPojo();
myPojo.setCampaingID(1);
myPojo.setClientID(1);
myPojo.setPmapID(1);
myPojo.setWard("1-Bedded (Private)");
myPojo.setAge(20);
myPojo.setAttr1("EXA1(A)");
myPojo.setAttr2("EO");
myPojo.setAttr3("11/02/2012");
myPojo.setAttr4("SIN");
myPojo.setAttr5("N");
myPojo.setAttr6("Y");
try {
resource.post(myPojo, MediaType.APPLICATION_JSON).write(System.out);
} catch (ResourceException | IOException e) {
e.printStackTrace();
}
}
}
Full Restlet documentation can be found here.
For the benefit of those who landed in the same situation as me, here's my solution:
Resource class
#Override
#Post("JSON")
public Representation post(Representation entity) throws ResourceException
{
PMCMatrixDAO matrix = new PMCMatrixDAOImpl();
JsonObjectBuilder response = Json.createObjectBuilder();
try {
String json = entity.getText(); // Get JSON input from client
Map<String, Object> map = JsonUtils.toMap(json); // Convert input into Map
double result = matrix.calculatePMC(map);
response.add("PMC", result);
} catch (IOException e) {
LOGGER.error(this.getClass() + " - IOException - " + e);
getResponse().setStatus(Status.SERVER_ERROR_INTERNAL);
}
return new StringRepresentation(response.build().toString());
}
JSON conversion utility class
public class JsonUtils {
private static final Logger LOG = LoggerFactory.getLogger(JsonUtils.class);
private JsonUtils() {
}
public static String toJson(Object object) {
String jsonString = null;
ObjectMapper mapper = new ObjectMapper();
try {
jsonString = mapper.writeValueAsString(object);
} catch (JsonProcessingException e) {
LOG.error(e.getMessage(), e);
}
return jsonString;
}
public static Map<String, Object> toMap(String jsonString) {
Map<String, Object> map = new ConcurrentHashMap<>();
ObjectMapper mapper = new ObjectMapper();
try {
map = mapper.readValue(jsonString, new TypeReference<Map<String, Object>>() {
});
} catch (IOException e) {
LOG.error(e.getMessage(), e);
}
return map;
}
}
And the implementation class which handles all the processing
public class PMCMatrixDAOImpl implements PMCMatrixDAO
{
public double calculatePMC(Map<String, Object> map)
{
int campaignID = (int) map.get("campaignID");
int clientID = (int) map.get("clientID");
int pmapID = (int) map.get("pmapID");
String ward = (String) map.get("ward");
int age = (int) map.get("age");
String attr1 = (String) map.get("attr1");
String attr2 = (String) map.get("attr2");
String attr3 = (String) map.get("attr3");
String attr4 = (String) map.get("attr4");
String attr5 = (String) map.get("attr5");
String attr6 = (String) map.get("attr6");
// SQL processing
double dPMC = sqlQueryCall(...);
return dPMC;
}
}
Related
I am converting the below JsonObject to a String and keeping the string values in Hashmap. When I executed the application, I am getting an error
JSONObject["global_collector"] is not a string
How to solve the error?
#Service
public class CreditRiskService
{
public static String original1 = "";
public static String str;
public void getMatchingOpco() throws ParseException, FileNotFoundException, IOException
//public Map<String, String> getMatchingOpco() throws ParseException
{
File file1 = new File(getClass().getClassLoader().getResource("CredirRisk_corainputtoIIB.json").getFile());
JSONTokener jsonDataFile1 = new JSONTokener(new FileInputStream(file1));
JSONObject jsonObject1 = new JSONObject(jsonDataFile1);
//jsonObject1.optString(original1, original1)
File file2 = new File(getClass().getClassLoader().getResource("opco.json").getFile());
JSONTokener jsonDataFile2 = new JSONTokener(new FileInputStream(file2));
JSONObject jsonObject2 = new JSONObject(jsonDataFile2);
if(!JSONObject.NULL.equals(jsonObject1))
{
String jsonObjToString1 = (String)jsonObject1.toString();
original1=jsonObjToString1;
HashMap<String, String> map1 = new HashMap<>();
Iterator<String> itr1 = jsonObject1.keys();
while(itr1.hasNext())
{
String key = itr1.next();
String value = jsonObject1.getString(key);
System.out.println("Setting map key and value pairs");
map1.put(key,value);
System.out.println(key + ":" + value);
}
}
}
}
Following is the Json:
{
"target": "SUS",
"source": "CORA",
"request_tracker_id": "201013051429",
"request_datetimestamp": "2020-10-13 05:14:29",
"risk_code": null,
"payment_terms": null,
"op_code": "056",
"global_collector": null,
"credit_analyst": "CRDAB000",
"account_key": "USBL056155266B"
}
I have a JSON response from an API like this:
{"asalas77":
{"id":23519033,"name":"Asalas77","profileIconId":22,"revisionDate":1487214366000,"summonerLevel":30}
}
And I need to extract the inner object from it. I tried using a deserializer like shown in this question Get nested JSON object with GSON using retrofit but it doesn't work for me.
public class SummonerDeserializer implements JsonDeserializer<Summoner> {
#Override
public Summoner deserialize(JsonElement je, Type type, JsonDeserializationContext jdc)
throws JsonParseException {
long id = je.getAsJsonObject().get("id").getAsLong();
String name = je.getAsJsonObject().get("name").getAsString();
int profileIconId = je.getAsJsonObject().get("profileIconId").getAsInt();
long revisionDate = je.getAsJsonObject().get("revisionDate").getAsLong();
long summonerLevel = je.getAsJsonObject().get("summonerLevel").getAsLong();
Summoner s = new Summoner();
s.setId(id);
s.setName(name);
s.setProfileIconId(profileIconId);
s.setRevisionDate(revisionDate);
s.setSummonerLevel(summonerLevel);
return s;
}
}
But the problem is I can't access the inner fields from JsonElement je and the name asalas77 is a variable (it's a search query) so I can't extract the inner object directly.
You must have a wrapper class in order not to clash deserialization strategies. Assume it's as follows:
final class SummonerResponse {
private final Summoner summoner;
private SummonerResponse(final Summoner summoner) {
this.summoner = summoner;
}
static SummonerResponse summonerResponse(final Summoner summoner) {
return new SummonerResponse(summoner);
}
Summoner getSummoner() {
return summoner;
}
}
Then you can either create a custom response deserializer:
final class SummonerWrapperDeserializer
implements JsonDeserializer<SummonerResponse> {
private static final JsonDeserializer<SummonerResponse> summonerDeserializer = new SummonerWrapperDeserializer();
private SummonerWrapperDeserializer() {
}
static JsonDeserializer<SummonerResponse> getSummonerResponseDeserializer() {
return summonerDeserializer;
}
#Override
public SummonerResponse deserialize(final JsonElement jsonElement, final Type type, final JsonDeserializationContext context)
throws JsonParseException {
// Pick the root as a JSON object
final JsonObject outerJsonObject = jsonElement.getAsJsonObject();
// And check how many properties does it have
final Iterable<? extends Entry<String, JsonElement>> outerJsonObjectEntries = outerJsonObject.entrySet();
if ( outerJsonObject.size() != 1 ) {
throw new JsonParseException("Expected one property object, the actual properties are: " + getPropertyName(outerJsonObjectEntries));
}
// If it has only one property, just get the property and take its inner value
final JsonElement innerJsonElement = outerJsonObjectEntries.iterator().next().getValue();
// Once it's obtained, just delegate the parsing to a downstream parser - no need to create Summoner instances by hands
return summonerResponse(context.deserialize(innerJsonElement, Summoner.class));
}
private static Set<String> getPropertyName(final Iterable<? extends Entry<String, JsonElement>> entries) {
final Set<String> keys = new LinkedHashSet<>();
for ( final Entry<String, JsonElement> entry : entries ) {
keys.add(entry.getKey());
}
return keys;
}
}
Or save some memory (the JSON (de)serializers require some memory because they work with JSON trees) and create a more low level type adapter:
final class SummonerResponseTypeAdapterFactory
implements TypeAdapterFactory {
private static final TypeAdapterFactory summonerResponseTypeAdapterFactory = new SummonerResponseTypeAdapterFactory();
private SummonerResponseTypeAdapterFactory() {
}
static TypeAdapterFactory getSummonerResponseTypeAdapterFactory() {
return summonerResponseTypeAdapterFactory;
}
#Override
public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> typeToken) {
// Check if we can handle SummonerResponse. Classes can be compared with `==`
if ( typeToken.getRawType() == SummonerResponse.class ) {
final TypeAdapter<SummonerResponse> typeAdapter = getSummonerResponseTypeAdapter(gson);
#SuppressWarnings("unchecked")
final TypeAdapter<T> castTypeAdapter = (TypeAdapter<T>) typeAdapter;
return castTypeAdapter;
}
return null;
}
}
final class SummonerResponseTypeAdapter
extends TypeAdapter<SummonerResponse> {
private final Gson gson;
private SummonerResponseTypeAdapter(final Gson gson) {
this.gson = gson;
}
static TypeAdapter<SummonerResponse> getSummonerResponseTypeAdapter(final Gson gson) {
return new SummonerResponseTypeAdapter(gson);
}
#Override
#SuppressWarnings("resource")
public void write(final JsonWriter out, final SummonerResponse summonerResponse)
throws IOException {
// The incoming object may be null
if ( summonerResponse == null && gson.serializeNulls() ) {
out.nullValue();
return;
}
// Generate the inner object
out.beginObject();
out.name(summonerResponse.getSummoner().name);
gson.toJson(summonerResponse.getSummoner(), Summoner.class, out);
out.endObject();
}
#Override
public SummonerResponse read(final JsonReader in)
throws IOException {
// is it a null?
if ( in.peek() == NULL ) {
return null;
}
// make sure that the inner read JSON contains an inner object
in.beginObject();
// ignore the name
in.nextName();
// delegate parsing to the backing Gson instance in order to apply downstream parsing
final Summoner summoner = gson.fromJson(in, Summoner.class);
// check if there are more properties within the inner object
if ( in.peek() == NAME ) {
throw new MalformedJsonException("Unexpected: " + in.nextName());
}
// consume the "}" token
in.endObject();
return summonerResponse(summoner);
}
}
Then any of the options above can be used like this:
final Gson gson = new GsonBuilder()
.registerTypeAdapter(SummonerResponse.class, getSummonerResponseDeserializer())
.create();
final SummonerResponse summonerResponse = gson.fromJson(JSON, SummonerResponse.class);
final Summoner summoner = summonerResponse.getSummoner();
out.println(summoner.id + " => " + summoner.name);
or
final Gson gson = new GsonBuilder()
.registerTypeAdapterFactory(getSummonerResponseTypeAdapterFactory())
.create();
final SummonerResponse summonerResponse = gson.fromJson(JSON, SummonerResponse.class);
final Summoner summoner = summonerResponse.getSummoner();
out.println(summoner.id + " => " + summoner.name);
out.println(gson.toJson(summonerResponse));
The outputs are
23519033 => Asalas77
and
23519033 => Asalas77
{"Asalas77":{"id":23519033,"name":"Asalas77","profileIconId":22,"revisionDate":1487214366000,"summonerLevel":30}}
respectively.
I am banging my head hear on why my property ReCaptchaResponse JSONProperty will not bind to my model. The others do just find, and my JSON Value Provider class hits just fine. Any clue at all? It is always NULL.
Ajax Request
{"Name":"Joe","Email":"","Message":"","g-recaptcha-response":"data"}
ContactUsController.cs
[HttpPost]
public virtual ActionResult Index(ContactUsModel model)
{
_contactUsService.ContactUs(model);
return Json(new SuccessResponse("Submitted Successfully"));
}
ContactUsMode.cs
[JsonObject, DataContract]
public class ContactUsModel
{
public string Name { get; set; }
public string Email { get; set; }
public string Message { get; set; }
[JsonProperty(PropertyName = "g-recaptcha-response"), DataMember(Name = "g-recaptcha-response")]
public string ReCaptchaResponse { get; set; }
}
JsonNetValueProviderFactory.cs
namespace Tournaments.Models.Mvc
{
public class JsonNetValueProviderFactory : ValueProviderFactory
{
public override IValueProvider GetValueProvider(ControllerContext controllerContext)
{
// first make sure we have a valid context
if (controllerContext == null)
throw new ArgumentNullException("controllerContext");
// now make sure we are dealing with a json request
if (!controllerContext.HttpContext.Request.ContentType.StartsWith("application/json", StringComparison.OrdinalIgnoreCase))
return null;
// get a generic stream reader (get reader for the http stream)
var streamReader = new StreamReader(controllerContext.HttpContext.Request.InputStream);
// convert stream reader to a JSON Text Reader
var jsonReader = new JsonTextReader(streamReader);
// tell JSON to read
if (!jsonReader.Read())
return null;
// make a new Json serializer
var jsonSerializer = new JsonSerializer();
jsonSerializer.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
// add the dyamic object converter to our serializer
jsonSerializer.Converters.Add(new ExpandoObjectConverter());
// use JSON.NET to deserialize object to a dynamic (expando) object
Object jsonObject;
// if we start with a "[", treat this as an array
if (jsonReader.TokenType == JsonToken.StartArray)
jsonObject = jsonSerializer.Deserialize<List<ExpandoObject>>(jsonReader);
else
jsonObject = jsonSerializer.Deserialize<ExpandoObject>(jsonReader);
// create a backing store to hold all properties for this deserialization
var backingStore = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
// add all properties to this backing store
AddToBackingStore(backingStore, String.Empty, jsonObject);
// return the object in a dictionary value provider so the MVC understands it
return new DictionaryValueProvider<object>(backingStore, CultureInfo.CurrentCulture);
}
private static void AddToBackingStore(Dictionary<string, object> backingStore, string prefix, object value)
{
var d = value as IDictionary<string, object>;
if (d != null)
{
foreach (KeyValuePair<string, object> entry in d)
{
AddToBackingStore(backingStore, MakePropertyKey(prefix, entry.Key), entry.Value);
}
return;
}
var l = value as IList;
if (l != null)
{
for (int i = 0; i < l.Count; i++)
{
AddToBackingStore(backingStore, MakeArrayKey(prefix, i), l[i]);
}
return;
}
// primitive
backingStore[prefix] = value;
}
private static string MakeArrayKey(string prefix, int index)
{
return prefix + "[" + index.ToString(CultureInfo.InvariantCulture) + "]";
}
private static string MakePropertyKey(string prefix, string propertyName)
{
return (String.IsNullOrEmpty(prefix)) ? propertyName : prefix + "." + propertyName;
}
}
}
Try ModelBinder. ValueProviderFactory does not work because of ExpandoObject.
internal class JsonNetModelBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
controllerContext.HttpContext.Request.InputStream.Position = 0;
var stream = controllerContext.RequestContext.HttpContext.Request.InputStream;
var readStream = new StreamReader(stream, Encoding.UTF8);
var json = readStream.ReadToEnd();
return JsonConvert.DeserializeObject(json, bindingContext.ModelType);
}
}
ContactUsController.cs
[HttpPost]
public virtual ActionResult Index([ModelBinder(typeof(JsonNetModelBinder))]ContactUsModel model)
{
_contactUsService.ContactUs(model);
return Json(new SuccessResponse("Submitted Successfully"));
}
I am using the below code to receive Tweets from Twitter4j Search API in the form of JSON response. I am receiving the result in the form of List as specified in Twitter4j search API in the line
List<Status> tweets = result.getTweets();
The problem is that the tweets returned as List where one Status entry is having non-empty and non-null GeoLocation whereas another Status entry is having a null or empty GeoLocation. Since to retrieve the relevant fields from each Status entry (i.e. Tweet), I iterate over the List and call getters which is throwing me null for the Status entries where the GeoLocation field is null.
The approach I tried to follow:
I created a POJO TweetJSON_2 (defined at the bottom of the post) with the relevant fields and their getters and setters. I am using Jackson ObjectMapper to handle null values like below:
JsonGenerator generator = new JsonFactory().createGenerator(os);
generator.setPrettyPrinter(new DefaultPrettyPrinter());
TweetJSON_2 rawJSON;
ObjectMapper mapper = new ObjectMapper();
//mapper.setVisibility(PropertyAccessor.FIELD, Visibility.ANY);
mapper.configure(SerializationFeature.WRITE_NULL_MAP_VALUES, false);
mapper.setSerializationInclusion(Include.NON_NULL);
// ... rawJSON is populated ...
mapper.writeValue(generator, rawJSON);
However, when I am trying to get the geoLocation field from Status, using the below line which is marked with **
List<Status> tweets = result.getTweets();
I am getting the Java NullPointerException as follows:
[Mon Apr 20 11:32:47 IST 2015]{"statuses":[{"retweeted_status":{"contributors":null,"text":"<my text>",**"geo":null**,"retweeted":false,"in_reply_to_screen_name":null,"truncated":false,"lang":"en","entities":{"symbols":[],"urls":[],"hashtags": ... &include_entities=1","since_id_str":"0","completed_in":0.029}}
**Exception in thread "main" java.lang.NullPointerException
at analytics.search.twitter.SearchFieldsTweetsJSON_2.main(SearchFieldsTweetsJSON_2.java:78)**
For example: If I input a json String as
String s = "{\"first\": 123, \"second\": [{\"second_first\":null, \"second_second\":null}, {\"second_third\":null}, null], \"third\": 789, \"fourth\":null}";
The output should be like
"{\"first\": 123, \"third\": 789}";
What I want, is to replace all null elements from JSONArrays and all null key-value pairs from JSONObjects no matter at whatever level they are nested in my JSON response.
Object vs Tree Model Approach
I tried the Object Model parsing mechanism which is a javax.json.stream.JsonParser.Event based method but would need multiple times of access and object replacement on the JSON String depending on at what level the null is nested making this approach very complicated. At the same time if I use Tree Model mechanism, the entire JSON response would have to be stored as a Tree which may overflow my JVM heap memory because the JSON size can be pretty large based on my query parameters. I need to find a workable solution to overcome this problem. Any suggestions on solving the above discussed problem will be highly appreciated.
The code is as follows:
public class SearchFieldsTweetsJSON_2 {
/* Searches specific fields from Tweets in JSON format */
public static void main(String[] args) throws FileNotFoundException, IOException {
if (args.length < 2) {
System.out.println("java twitter4j.examples.search.SearchTweets [query][outputJSONFile]");
System.exit(-1);
}
ConfigurationBuilder cb = new ConfigurationBuilder();
cb.setDebugEnabled(true)
.setOAuthConsumerKey("XXXXXXXXXXXXXXXXXXXXXXXXXX")
.setOAuthConsumerSecret("XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX")
.setOAuthAccessToken("NNNNNNNNN-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX")
.setOAuthAccessTokenSecret("XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX")
.setJSONStoreEnabled(true);
Twitter twitter = new TwitterFactory(cb.build()).getInstance();
try {
Query query = new Query(args[0]);
QueryResult result;
File jsonFile = new File(args[1]);
System.out.println("File Path : " + jsonFile.getAbsolutePath());
OutputStreamWriter os = new OutputStreamWriter(new FileOutputStream(jsonFile));
JsonGenerator generator = new JsonFactory().createGenerator(os);
generator.setPrettyPrinter(new DefaultPrettyPrinter());
TweetJSON_2 rawJSON;
ObjectMapper mapper = new ObjectMapper();
//mapper.setVisibility(PropertyAccessor.FIELD, Visibility.ANY);
mapper.configure(SerializationFeature.WRITE_NULL_MAP_VALUES, false);
mapper.setSerializationInclusion(Include.NON_NULL);
do {
result = twitter.search(query);
List<Status> tweets = result.getTweets();
for (Status tweet : tweets) {
rawJSON = new TweetJSON_2();
rawJSON.setStatusId(Long.toString(tweet.getId()));
rawJSON.setUserId(Long.toString(tweet.getUser().getId()));
rawJSON.setUserName(tweet.getUser().getScreenName());
rawJSON.setStatusText(tweet.getText());
rawJSON.setGeoLocation(tweet.getGeoLocation().toString()); **<< Giving error at tweet.getGeoLocation() since GeoLocation is null**
mapper.writeValue(generator, rawJSON);
System.out.println(rawJSON.toString());
}
} while ((query = result.nextQuery()) != null);
generator.close();
System.out.println(os.toString());
} catch (TwitterException te) {
te.printStackTrace();
System.out.println("Failed to search tweets : " + te.getMessage());
System.exit(-1);
}
}
}
I have defined my TweetJSON_2 Java object as follows:
public class TweetJSON_2 {
public String statusId;
public String statusText;
public String userId;
public String userName;
public String geoLocation;
public String getStatusId() {
return statusId;
}
public void setStatusId(String statusId) {
this.statusId = statusId;
}
public String getStatusText() {
return statusText;
}
public void setStatusText(String statusText) {
this.statusText = statusText;
}
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getGeoLocation() {
return geoLocation;
}
public void setGeoLocation(String geoLocation) {
this.geoLocation = geoLocation;
}
#Override
public String toString() {
return "TweetJSON_2 [ statusId = " + statusId + ", statusText = " + statusText + "]";
}
}
I have tried with reconfiguring my POJO in the below way and it successfully replaced all the nulls as specified in the setter methods. Didn't need to follow either Tree or Event-based model parsing of JSON string. HTH
The modified TweetJSON_2 POJO:
public class TweetJSON_2 {
public Long statusId = null;
public String statusText = null;
public Long userId = null;
public String userName = null;
public GeoLocation geoLocation = null;
public Long getStatusId() {
if (this.statusId==null)
return new Long(0L);
return statusId;
}
public void setStatusId(Long statusId) {
if (statusId==null)
this.statusId = new Long(0L);
else
this.statusId = statusId;
}
public String getStatusText() {
if (this.statusText==null)
return new String("");
return statusText;
}
public void setStatusText(String statusText) {
if (statusText==null)
this.statusText = new String("");
else
this.statusText = statusText;
}
public Long getUserId() {
if (this.userId==null)
return new Long(0L);
return userId;
}
public void setUserId(Long userId) {
if (userId==null)
this.userId = new Long(0L);
else
this.userId = userId;
}
public String getUserName() {
if (this.userName==null)
return new String("");
return userName;
}
public void setUserName(String userName) {
if (userName==null)
this.userName = new String("");
else
this.userName = userName;
}
public GeoLocation getGeoLocation() {
if (this.geoLocation==null)
return new GeoLocation(0.0,0.0);
return geoLocation;
}
public void setGeoLocation(GeoLocation geoLocation) {
if (geoLocation==null)
this.geoLocation = new GeoLocation(0.0,0.0);
else
this.geoLocation = geoLocation;
}
#Override
public String toString() {
return "TweetJSON_2 [ statusId = " + statusId + ", statusText = " + statusText + "]";
}
}
I have a Java class in a servlet that uses GSON to render posted JSON Strings into a Java object. The beauty of the approach is, that GSON filters out all JSON elements that don't match a class property, so I never end up with JSON content that I don't want to process. The servlet's doPost (simplified) looks like this:
protected void doPost(HttpServletRequest request, HttpServletResponse response) {
PrintWriter out = null;
try {
InputStream in = request.getInputStream();
Demo d = Demo.load(in);
in.close();
response.setContentType("text/plain");
response.setStatus(HttpServletResponse.SC_OK);
out = response.getWriter();
out.println(d.toJson);
} catch (Exception e) {
e.printStackTrace();
out.println(e.getMessage());
}
out.close();
}
The Demo class (and that's the one I need to recreate in common.js or node.js looks like this:
#JsonIgnoreProperties(ignoreUnknown = true)
public class Demo implements Serializable {
public static Demo load(InputStream in) {
Demo result = null;
try {
Gson gson = new GsonBuilder().create();
result = gson.fromJson(new InputStreamReader(in), Demo.class);
} catch (Exception e) {
result = null;
}
return result;
}
#TypeDiscriminator
#JsonProperty("_id")
private String id = UUID.randomUUID().toString();
private Date created = new Date();
private String color;
private String name;
private String taste;
public String getColor() {
return this.color;
}
public String getName() {
return this.name;
}
public String getTaste() {
return this.taste;
}
public Date getCreated() {
return this.created;
}
public String getId() {
return this.id;
}
public void setName(String name) {
this.name = name;
}
public void setTaste(String taste) {
this.taste = taste;
}
public void setColor(String color) {
this.color = color;
}
public String toJson() {
GsonBuilder gb = new GsonBuilder();
gb.setPrettyPrinting();
gb.disableHtmlEscaping();
Gson gson = gb.create();
return gson.toJson(this);
}
}
Obviously I stripped out all the processing logic and the servlet just echos the JSON back, which is not what the app does, but serves to illustrate the point. I can throw pretty any String in a HTTP Post at that example and I only get valid Demo objects.
How would I do something like this in node.js?
Node.js is Javascript so has built in support for json. You can use JSON.parse to convert from string to json and wrap in try catch block.
To only include select properties there is no built in feature in node that I know of unless you are using Mongodb with mongoose, but you could do following: Have a "class" that is an object containing all properties that you want and delete those from parsed json object that are not in that "class" object.
var class = {x: null, y:null};
for(var prop in object){
if (!class.hasOwnProperty (prop)) {
delete object [prop]
}
It would be best to use this class as object and expose parseJSON function to encapsulate this functionality