Parsing JSON Response using Jackson - web-services

I have written a web-service and hosted in the following URL http://192.168.2.34:8081/MyWebService/facility/university
Response from this web service in JSON format is:
{
"university": [
{
"emailOfPrincipal": "prin1#gmail.com",
"labLists": [
{
"instrumentLists": [
{
"instrumentId": "1",
"instrumentName": "Instrument 1"
},
{
"instrumentId": "2",
"instrumentName": "instrument 2"
}
],
"labId": "11",
"labName": "Lab 11"
},
{
"instrumentLists": [
{
"instrumentId": "3",
"instrumentName": "instrument 3"
},
{
"instrumentId": "4",
"instrumentName": "instrument 4"
}
],
"labId": "22",
"labName": "Lab 22"
}
],
"univAddress": "Kolkata",
"univId": "111",
"univName": "University 111",
"univPrincipalName": "Principal 1"
},
{
"emailOfPrincipal": "prin2#gmail.com",
"labLists": [
{
"instrumentLists": [
{
"instrumentId": "5",
"instrumentName": "Instrument 5"
},
{
"instrumentId": "6",
"instrumentName": "Instrument 6"
}
],
"labId": "33",
"labName": "Lab 33"
},
{
"instrumentLists": [
{
"instrumentId": "7",
"instrumentName": "Instrument 7"
},
{
"instrumentId": "8",
"instrumentName": "Instrument 8"
}
],
"labId": "44",
"labName": "Lab 44"
}
],
"univAddress": "Bangalore",
"univId": "222",
"univName": "University 222",
"univPrincipalName": "Principal 2"
},
{
"emailOfPrincipal": "prin3#gmail.com",
"labLists": [
{
"instrumentLists": [
{
"instrumentId": "9",
"instrumentName": "Instrument 9"
},
{
"instrumentId": "10",
"instrumentName": "Instrument 10"
}
],
"labId": "55",
"labName": "Lab 55"
},
{
"instrumentLists": [
{
"instrumentId": "11",
"instrumentName": "Instrument 11"
},
{
"instrumentId": "12",
"instrumentName": "Instrument 12"
}
],
"labId": "66",
"labName": "Lab 66"
}
],
"univAddress": "Hyderabad",
"univId": "333",
"univName": "University 333",
"univPrincipalName": "Principal 3"
}
]
}
I am trying to convert JSON data into Java object using jackson api. But getting an error.
My Java Objects are:
University.java
public class University {
private int univId;
private String univName;
private String univAddress;
private String univPrincipalName;
private String emailOfPrincipal;
private List<Laboratory> labLists = new ArrayList<>();
//... Getter and Setter Methods...
}
Laboratory.java
public class Laboratory {
private int labId;
private String labName;
private List<Instruments> instrumentLists = new ArrayList<>();
//... Getter and Setter Methods...
}
Instruments.java
public class Instruments {
private int instrumentId;
private String instrumentName;
private String bookingStatus;
private Date bookingFrom;
private Date bookingTo;
private Date requestedTime;
//... Getter and Setter Methods...
}
The Client written is:
public class ParseJSONData {
public static void main(String[] args) throws ClientProtocolException, IOException {
String URL = "http://192.168.2.34:8081/MyWebService/facility/university";
DefaultHttpClient httpclient = new DefaultHttpClient();
HttpGet httpget = new HttpGet(URL);
HttpResponse response = httpclient.execute(httpget);
// System.out.println(response);
HttpEntity entity = response.getEntity();
String result = EntityUtils.toString(entity);
ObjectMapper mapper = new ObjectMapper();
University universities = mapper.readValue(result, University.class);
System.out.println(universities.getEmailOfPrincipal());
}
The error I am getting says
Exception in thread "main" org.codehaus.jackson.map.exc.UnrecognizedPropertyException: Unrecognized field "university" (Class University), not marked as ignorable
at [Source: java.io.StringReader#100edc0; line: 1, column: 16] (through reference chain: University["university"])
I know my JSON data is returning List of universities. But how to map those in List I am not aware of. Any sort of help will be greatly appreciated.

You can add parent class
public class UniversityList {
private List<University> university;
//... Getter and Setter Methods...
}
and us it like this:
ObjectMapper mapper = new ObjectMapper();
UniversityList list = mapper.readValue(json, UniversityList.class));
University university = list.getUniversity().get(0)
Other option is to create custom deserializer for your University class
Here is demo:
public static void main(String[] args) throws IOException {
ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.addDeserializer(University.class, new UniversityDeserializer());
mapper.registerModule(module);
University university = mapper.readValue(json, University.class);
}
public static class UniversityDeserializer extends JsonDeserializer<University> {
private ObjectMapper unmodifiedMapper = new ObjectMapper();
#Override
public University deserialize(JsonParser p, DeserializationContext ctxt)
throws IOException{
JsonNode node = p.readValueAsTree();
JsonNode array = node.get("university");
if (array != null && array.isArray()){
JsonNode uniNode = array.get(0);
return unmodifiedMapper.treeToValue(uniNode, University.class);
}
return null;
}
}

Related

How to read a json obj created with the nlohmann::json lib

I'm getting a JSON string from a curl request, like this:
[
{
"name": "P309",
"path": "P309",
"sha": "b9dbfa7ceb9db3b73c84ff326a418bdd50c1b227",
"type": "file",
"_links": {
"self": "https://api.github.com/repos//files/contents/P309?ref=main",
"git": "https://api.github.com/repos//files/git/blobs/b9dbfdd50c1b227",
"html": "https://github.com//files/blob/main/P309"
}
},
{
"name": "P310",
"path": "P310",
"sha": "b9dbfa7ceb9db3b73c84ff326a418bdd50c1b227",
"_links": {
"self": "https://api.github.com/repos//files/contents/P309?ref=main",
"git": "https://api.github.com/repos//files/git/blobs/b9dbfdd50c1b227",
"html": "https://github.com//files/blob/main/P309"
}
},
]
How can I parse/read it using the nlohmann::json lib?
I've found this example:
/*
"emulators": [
{
"general_info": {
"dev_id": "0123456789"
}
}
]
*/
for(json& o : data["emulators"]) {
json& gi = o["general_info"];
std::cout << gi["dev_id"] << '\n';
}
My JSON being parsed doesn't have any 'title/header', I'm confused about how to read it.
I tried:
using json = nlohmann::json;
json data = json::parse(downloaded_data);
for(json& o : data[0]) {
auto git = o["git"].get<std::string>();
auto name = o["name"].get<std::string>();
}
git outputs the value correctly, however, when reading name it throws an exception.
This is the value of data in the debugger
and this, of o.
Can I get any help on this?

how to create Intents with Context from the DialogFlow API

I'm trying to batch create a bunch of intents, and I want to assign an Input context
As far as I can see this should not need a session as it's just a string context name.
Like this in the GUI:
The API call I make creates the intent,
doesn't throw an error,
but I can't get the contexts to show up.
Is there some weird format to the contexts parameter?
I've exported the zip and looked at the JSON files, they're just an array of strings.
I've seen other code that seems to require a user conversation sessionId to create contexts. But the intents are global - not for a single conversation. And I assume these are just for tracking context within a single conversation session (or google astronaut engineering)
The data I'm POSTing looks like the below
There's a google example here that doesn't touch contexts
https://cloud.google.com/dialogflow/es/docs/how/manage-intents#create_intent
I've tried contexts as various formats without success
// this is the part that doesn't work
// const contexts = [{
// // name: `${sessionPath}/contexts/${name}`,
// // name: 'test context name'
// }]
const contexts = [
'theater-critics'
]
createIntentRequest {
"parent": "projects/XXXXXXXX-XXXXXXXX/agent",
"intent": {
"displayName": "test 4",
"trainingPhrases": [
{
"type": "EXAMPLE",
"parts": [
{
"text": "this is a test phrase"
}
]
},
{
"type": "EXAMPLE",
"parts": [
{
"text": "this is a another test phrase"
}
]
}
],
"messages": [
{
"text": {
"text": [
"this is a test response"
]
}
}
],
"contexts": [
"theater-critics"
]
}
}
Intent projects/asylum-287516/agent/intents/XXXXXXXX-e852-4c09-bda6-e524b8329db8 created
Full JS (TS) code below for anyone else
import { DfConfig } from './DfConfig'
const dialogflow = require('#google-cloud/dialogflow');
const testData = {
displayName: 'test 4',
trainingPhrasesParts: [
"this is a test phrase",
"this is a another test phrase"
],
messageTexts: [
'this is a test response'
]
}
// const messageTexts = 'Message texts for the agent's response when the intent is detected, e.g. 'Your reservation has been confirmed';
const intentsClient = new dialogflow.IntentsClient();
export const DfCreateIntent = async () => {
const agentPath = intentsClient.agentPath(DfConfig.projectId);
const trainingPhrases = [];
testData.trainingPhrasesParts.forEach(trainingPhrasesPart => {
const part = {
text: trainingPhrasesPart,
};
// Here we create a new training phrase for each provided part.
const trainingPhrase = {
type: 'EXAMPLE',
parts: [part],
};
// #ts-ignore
trainingPhrases.push(trainingPhrase);
});
const messageText = {
text: testData.messageTexts,
};
const message = {
text: messageText,
};
// this is the part that doesn't work
// const contexts = [{
// // name: `${sessionPath}/contexts/${name}`,
// // name: 'test context name'
// }]
const contexts = [
'theater-critics'
]
const intent = {
displayName: testData.displayName,
trainingPhrases: trainingPhrases,
messages: [message],
contexts
};
const createIntentRequest = {
parent: agentPath,
intent: intent,
};
console.log('createIntentRequest', JSON.stringify(createIntentRequest, null, 2))
// Create the intent
const [response] = await intentsClient.createIntent(createIntentRequest);
console.log(`Intent ${response.name} created`);
}
// createIntent();
update figured out based on this
https://cloud.google.com/dialogflow/es/docs/reference/rest/v2/projects.agent.intents#Intent
const contextId = 'runner'
const contextName = `projects/${DfConfig.projectId}/agent/sessions/-/contexts/${contextId}`
const inputContextNames = [
contextName
]
const intent = {
displayName: testData.displayName,
trainingPhrases: trainingPhrases,
messages: [message],
inputContextNames
};

Wrong SQS AWS message when I'm subscribed from a SNS Topic

I'm having problems with the next design:
When I'm receiving the message in my SQS Subscriber, the model of message it's wrong, example:
{
"Type" : "Notification",
"MessageId" : "7a6789f0-02f0-5ed3-8a11-deebcd08f145",
"TopicArn" : "arn:aws:sns:us-east-2:167186109795:name_sns_topic",
"Message" : "My JSON message",
"Timestamp" : "1987-04-23T17:17:44.897Z",
"SignatureVersion" : "1",
"Signature" : "string",
"SigningCertURL" : "url",
"UnsubscribeURL" : "url",
"MessageAttributes" : {
"X-Header1" : {"Type":"String","Value":"value1"},
"X-Header2" : {"Type":"String","Value":"value2"},
"X-Header3" : {"Type":"String","Value":"value3"},
"X-HeaderN" : {"Type":"String","Value":"value4"}
}
}
The common model when recieve message from SQS should be:
{
"Records": [
{
"messageId": "19dd0b57-b21e-4ac1-bd88-01bbb068cb78",
"receiptHandle": "MessageReceiptHandle",
"body": "Hello from SQS!",
"attributes": {
"ApproximateReceiveCount": "1",
"SentTimestamp": "1523232000000",
"SenderId": "123456789012",
"ApproximateFirstReceiveTimestamp": "1523232000001"
},
"messageAttributes": {},
"md5OfBody": "7b270e59b47ff90a553787216d55d91d",
"eventSource": "aws:sqs",
"eventSourceARN": "arn:{partition}:sqs:{region}:123456789012:MyQueue",
"awsRegion": "{region}"
}
]
}
In my handler Java Lambda (example code) is throwing an exception because the estructure of de message received is not SQS Event:
public class MyHandler implements RequestHandler<SQSEvent, String> {
#Override
public String handleRequest(SQSEvent event, Context context) {
LambdaLogger logger = context.getLogger();
for (SQSEvent.SQSMessage msg : event.getRecords()) {
logger.log("SQS message body: " + msg.getBody());
logger.log("Get attributes: " + msg.getMessageAttributes().toString());
msg.getMessageAttributes()
.forEach(
(k, v) -> {
logger.log("key: " + k + "value: " + v.getStringValue());
});
}
return "Successful";
}
}
How can I do for handle the message thats its receiving ?
In my opinion this isn't documented too well but it's not bad once you figure it out.
The first thing is that I don't use the predefined Lambda objects. I read everything into a String and take it from there. So the base of my Lamda function is:
public void handleRequest(InputStream inputStream, OutputStream outputStream, Context context) throws IOException {
// copy InputStream to String, avoiding 3rd party libraries
ByteArrayOutputStream result = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int length;
while ((length = inputStream.read(buffer)) != -1) {
result.write(buffer, 0, length);
}
String jsonString = result.toString();
}
When you "go direct" from SNS to Lambda the message looks something like (some fields removed for sake of length):
{
"Records": [
{
"EventSource": "aws:sns",
"EventVersion": "1.0",
"Sns": {
"Type": "Notification",
"Subject": "the message subject",
"Message": "{\"message\": \"this is the message\", \"value\": 100}",
"Timestamp": "2020-04-24T21:44:28.220Z",
"SignatureVersion": "1"
}
}
]
}
I had sent in a test message in JSON with two simple fields. Using JsonPath the "message" field inside of everything is read with:
String snsMessage = JsonPath.read(jsonString, "$.Records[0].Sns.Message");
String realMessage = JsonPath.read(snsMessage, "$.message");
But when it goes SNS -> SQS -> Lambda (or, indeed any SNS -> SQS path) the SNS message is now mostly wrapped and escaped in an SQS message:
{
"Records": [
{
"messageId": "ca8c53e5-8417-4479-a720-d4ecf970ca68",
"body": "{\n \"Type\" : \"Notification\",\n \"Subject\" : \"the message subject\",\n \"Message\" : \"{\\\"message\\\": \\\"this is the message\\\", \\\"value\\\": 100}\"\n}",
"attributes": {
"ApproximateReceiveCount": "1"
},
"md5OfBody": "6a4840230aca6a7bf7934bf191a529b8",
"eventSource": "aws:sqs"
}
]
}
So in this case, the value is in Records[0].body but that contains another JSON object. I'll admit that there is likely an easier way but from what I found I had to parse 3 times:
String sqsBody = <as read in lambda>;
String recordBody = JsonPath.read(sqsBody, "$.Records[0].body");
String internalMessage = JsonPath.read(recordBody, "$.Message");
// now read out of the sns message
String theSnsMessage = JsonPath.read(message, "$.message");

Determine velocity template request body property type?

I have an API Gateway that uses velocity templates as a thin wrapper to allow users to do CRUD operations on a DynamoDB table.
I'm trying to write the update operation as dynamically as possible, but where I'm stuck is with determining type from the request body's properties from within the velocity template. This is what I'm working with:
#set($body = $input.path('$'))
#set($updateExpression = "set")
#set($expressionAttributeNames = "")
#set($expressionAttributeValues = "")
#foreach($attrName in $body.keySet())
#set($updateExpression = "${updateExpression} #$attrName = :${attrName},")
#set($expressionAttributeNames = "${expressionAttributeNames}""#${attrName}"":""${attrName}""")
#set($attrValue = $input.json("$.${attrName}"))
#if($attrValue.matches("^-?\\d+$"))
#set($attrValue = """:${attrName}"": { ""N"": ${attrValue}, ")
#else
#set($attrValue = """:${attrName}"": { ""S"": """ + $util.escapeJavaScript($attrValue) + """ },")
#end
#set($expressionAttributeValues = "${expressionAttributeValues} ${attrValue}")
#if($foreach.hasNext)
#set($expressionAttributeNames = "${expressionAttributeNames}, ")
#end
#end
{
"TableName": "TABLE",
"Key": { "id": { "S": "$input.params('id')" } },
"UpdateExpression": "${updateExpression} updatedOn = :updatedOn",
"ExpressionAttributeNames": {$expressionAttributeNames},
"ExpressionAttributeValues": {
$expressionAttributeValues
":updatedOn": { "N": "$context.requestTimeEpoch" }
}
}
Edit: This would be a sample request body:
https://api/v1/endpoint/123
{
"location": {
"lat": 42,
"lon": -71
},
"rating": 4
}
This is the current transformation I get:
{
"TableName": "users",
"Key": { "gcn": { "S": "123" } },
"UpdateExpression": "set #number = :number, #location = :location, updatedOn = :updatedOn",
"ExpressionAttributeNames": {"#number":"number", "#location":"location"},
"ExpressionAttributeValues": {
":number": { "S": "1" }, ":location": { "S": "{\"lat\":26.89199858375187,\"lon\":75.77141155196833}" },
":updatedOn": { "N": "" }
}
}
I currently just have a test for checking if a value is a number...and it isn't working.
After doing some more digging I reached what I set out for. I have a dynamic Velocity Template mapping for AWS API Gateway for the purpose of updating DynamoDB items.
So far it supports strings, numbers, booleans, and string-escaped objects, as that's how my project stores them (they are not query-able). ExpressionAttributeNames exists in case you use a reserved keyword for an attribute name...like I did for 'location'.
If anyone has any improvements/enhancements please let me know, it's a beast of a script.
#set($body = $input.path('$'))
#set($updateExpression = "set")
#set($expressionAttributeNames = "")
#set($expressionAttributeValues = "")
#foreach($attrName in $body.keySet())
#set($updateExpression = "${updateExpression} #$attrName = :${attrName},")
#set($expressionAttributeNames = "${expressionAttributeNames}""#${attrName}"":""${attrName}""")
#set($attrValue = $input.json("$.${attrName}"))
#if($attrValue.toString().matches("[+-]?\d+"))
#set($attrValue = """:${attrName}"": { ""N"": ""${attrValue}"" }, ")
#elseif($attrValue.toString() == "true" || $attrValue.toString() == "false")
#set($attrValue = """:${attrName}"": { ""BOOL"": ${attrValue} }, ")
#elseif(($attrValue.toString().startsWith("{") && $attrValue.toString().endsWith("}")) ||
($attrValue.toString().startsWith("[") && $attrValue.toString().endsWith("]")) )
#set($attrValue = """:${attrName}"": { ""S"": """ + $util.escapeJavaScript($attrValue) + """ },")
#else
#set($attrValue = """:${attrName}"": { ""S"": " + $attrValue + " },")
#end
#set($expressionAttributeValues = "${expressionAttributeValues} ${attrValue}")
#if($foreach.hasNext)
#set($expressionAttributeNames = "${expressionAttributeNames}, ")
#end
#end
{
"TableName": "", ## Insert your table here.
"Key": { "gcn": { "S": "$input.params('')" } }, ## Insert your key expression here.
## Update below if `updatedOn` is not your audit attribute.
"UpdateExpression": "${updateExpression} updatedOn = :updatedOn",
"ExpressionAttributeNames": {$expressionAttributeNames},
"ExpressionAttributeValues": {
$expressionAttributeValues
":updatedOn": { "N": "$context.requestTimeEpoch.toString()" }
}
}
Sample Request Body:
{
"firstName": "John",
"isActive": true,
"_status": 1
}
Sample Transformation:
{
"TableName": "users",
"Key": {
"id": {
"S": "1"
}
},
"UpdateExpression": "set #firstName = :firstName, #isActive = :isActive, #_status = :_status, updatedOn = :updatedOn",
"ExpressionAttributeNames": {
"#firstName": "firstName",
"#isActive": "isActive",
"#_status": "_status"
},
"ExpressionAttributeValues": {
":firstName": {
"S": "John"
},
":isActive": {
"BOOL": true
},
":_status": {
"N": "1"
},
":updatedOn": {
"N": "123456789"
}
}
}

Alexa conversational skill Error

I'm creating a skill in Alexa that does the following.
User : Hi
Alexa, hello please give me your name
User : John
Alexa : Hi John, good to meet you. How old are you
User : 25
Below are my intents
{
"intents": [
{
"intent": "StartTheFlow",
"slots": [
{
"name": "custName",
"type": "list_of_userNames"
},
{
"name": "age",
"type": "AMAZON.NUMBER"
}
]
},
{
"intent": "AMAZON.HelpIntent"
},{
"intent": "Welcome"
},
{
"intent": "AMAZON.StopIntent"
},
{
"intent": "AMAZON.CancelIntent"
}
]
}
And below are my utterances
StartTheFlow Hi
StartTheFlow {custName}
StartTheFlow {age}
Below is my onIntent()
#Override
public SpeechletResponse onIntent(final IntentRequest request, final Session session) throws SpeechletException {
log.info("onIntent requestId={}, sessionId={}", request.getRequestId(), session.getSessionId());
Intent intent = request.getIntent();
String intentName = (intent != null) ? intent.getName() : null;
if ("StartTheFlow".equals(intentName)) {
return getTheFlow(intent, session);
} else if ("AMAZON.HelpIntent".equals(intentName)) {
return getHelpResponse();
} else if ("WelcomeChubb".equals(intentName)) {
return getWelcomeResponse();
} else {
throw new SpeechletException("Invalid Intent");
}
}
And I'm trying to handle this as below
private SpeechletResponse getTheFlow(Intent intent, Session session) {
boolean isAskResponse = true;
String responseText = "";
String nameFromSession = (String) session.getAttribute("name");
if (StringUtils.isNullOrEmpty(nameFromSession)) {
responseText = "please give me your name";
getTheNameText(intent, session);
} else {
System.out.println(session.getAttribute("nameFromSession"));
responseText = "please give me your date of birth";
}
return getSpeechletResponse(responseText, "", isAskResponse);
}
private String getTheNameText(Intent intent, Session session) {
String userNameFrmIntent = getNameFromSlot(intent).toString();
session.setAttribute("nameFromSession", userNameFrmIntent);
return getNameFromSlot(intent).toString();
}
private String getNameFromSlot(Intent intent) {
Slot userName = intent.getSlot(Slot_Name);
return userName.getValue();
}
Also, I've defined a slot in the top as below.
private static final String Slot_Name = "custName";
But here when I type Hi, Instead of asking me my name, it is giving me an error in logs it shows Java NullPointer Exception. the response that I get when I type Hi is as below.
{
"session": {
"sessionId": "SessionId.a2740ca4-73ff-4a15-856d-6461b3c7b2e1",
"application": {
"applicationId": "amzn1.ask.skill.e3dfb30e-0089-423c-a325-30ad28dd2e2b"
},
"attributes": {},
"user": {
"userId": "amzn1.ask.account.AEQYTT5HFHEGGDSUCT3NW45HKR7O3FBL5YCBSZIS7P5LNP5BXFEMUR7AUYOZVKC2FT5V6RKJC7RNA5VMZVREBAXAQP3NFNTQSFSSKSEXIYT4FQYMS5JCI2CCAOPUF4FN4C6DHEU6ONNY3D6GN5AWK75KOQNJH2IWROIIXTPNXSNI6FLQYRBBMP7TRSOWVNCY73WJUT2VLHDACWA"
},
"new": true
},
"request": {
"type": "IntentRequest",
"requestId": "EdwRequestId.cf686fc0-cbfd-4496-bb09-c41714563507",
"locale": "en-US",
"timestamp": "2017-02-15T20:12:44Z",
"intent": {
"name": "StartTheFlow",
"slots": {
"custName": {
"name": "custName",
"value": "hi"
}
}
}
},
"version": "1.0"
}
Can someone please let me know where am I going wrong and how can I fix this, I've quite a number of questions to be linked, like 25, can Someone please let me know if there is a better way to do this in java.
Thanks
I would recommend creating a separate intent for each thing that the user says. So for example, HelloIntent, NameIntent, and AgeIntent.
Then be sure to pass those bits of information forward to all following intents in the session. So each intent could use a common function at the beginning to read each string from the session (if exists), add the new slot data to it, and then write all the strings back to the response session before finishing.
Since you'll then have separate intents, and the user could conceivably say them out of order, you may want to check that all the needed strings have been entered, or else prompt the user for any missing strings.
The problem with saving data in the session is that the data will be gone the next time the user starts the skill. To resolve this, you could use a database to hold the users data, saving it keyed to the userId. There are lots of examples on how to do that. Be careful that some databases are essentially free, but others will charge you depending on how many times it is used each month.