Parsing _embedded items from JSON HAL response - java

We are trying to parse the below JSON to get a list of people.
JSON Response:
{
"_embedded": {
"people": [
{
"id": 35356,
"name": "Jon",
"description": "Test",
"type": "person",
"_links": {
"self": {
"href": "http://localhost/api/v1/50452/people/35356"
},
"items": {
"href": "http://localhost/api/v1/50452/items?person_id=35356"
},
"enabled_services": [
{
"title": "Water Company",
"href": "http://localhost/api/v1/50452/services/103890"
}
]
}
},
{
"id": 46363,
"name": "Kevin",
"description": "",
"type": "person",
"_links": {
"self": {
"href": "http://localhost/api/v1/50452/people/46363"
},
"items": {
"href": "http://localhost/api/v1/50452/items?person_id=46363"
},
"enabled_services": [
{
"title": "Water Company",
"href": "http://localhost/api/v1/50452/services/103890"
}
]
}
}
]
},
"_links": {
"self": {
"href": "http://localhost/api/v1/50452/people"
}
}
}
Our code:
ParameterizedTypeReference<Resources<Person>> resource = new ParameterizedTypeReference<Resources<Person>>() {};
Traverson traverson = new Traverson(new URI("http://localhost/api/v1/people"), MediaType.APPLICATION_JSON_UTF8);
// Create our own LinkDiscoverer as our service returns application/json instead of application/json+hal
List<LinkDiscoverer> linkDiscoverers = new ArrayList<>();
linkDiscoverers.add(new JsonPathLinkDiscoverer("$._links..['%s']..href", MediaType.APPLICATION_JSON_UTF8));
traverson.setLinkDiscoverers(linkDiscoverers);
HttpHeaders headers = new HttpHeaders();
headers.add("App-Key", Globals.Appkey);
headers.add("App-Id", Globals.AppId);
Resources<Person> personResources = traverson.follow("people").withHeaders(headers).toObject(resource);
However we are getting the following error:
java.lang.IllegalStateException: Expected to find link with rel 'people' in response {"_embedded":{"people":[{"id":31350,"name":"Jon Blue","description":"Developer","type":"person","deleted":false,"disabled":false,"company_id":50452,"order":31350,"phone_prefix":"44","_links":{"self":{"href":"http://localhost/api/v1/50452/people/31350"},"items":{"href":"http://localhost/api/v1/50452/items?person_id=31350"},"enabled_services":[{"title":"Water ...
Based on the (very limited) client docs this seems to be the correct way to do things. Does anyone know what we might be missing here?
Thanks

The Traverson is meant to be used to follow links:
Component to ease traversing hypermedia APIs by following links with
relation types. Highly inspired by the equally named JavaScript
library.
There is no link with rel people in your response, so it cannot be followed.
I could imagine you wanted to call the top-level resource that has a link to people:
Traverson traverson = new Traverson(new URI("http://localhost/api/v1/"), MediaType.APPLICATION_JSON_UTF8);
Otherwise I would suggest to use a RestTemplate to get the Resource:
restTemplate.exchange(
URI.create("http://localhost/api/v1/people"),
HttpMethod.GET,
new HttpEntity<Void>(headers),
resource).getBody();

Related

GSON serialize of SNSEvent resulting in lower case fields which lambda expects Capitalize words

I have a lambda function which takes SNSEvent(http://javadox.com/com.amazonaws/aws-lambda-java-events/1.1.0/com/amazonaws/services/lambda/runtime/events/SNSEvent.html) as the input and then works on that. For writing the some integration test cases, I wanted to call this lambda with the SNSEvent serialized using using lambda.invoke in which the message would be string.
So, for that, I was doing GSON.toJson([object of SNSEvent type]) and from this, I was getting a string. But the problem is, when I am using this string the invoke the lambda, I am getting an exception that deserialization of this string is failing.
Eg. GSON.toJson is deserializing to :
{
"records": [
{
"eventSource": "aws:sns",
"eventVersion": "1.0",
"eventSubscriptionArn": "arn:aws:sns:us-west-2:{{{accountId}}}:ExampleTopic",
"sns": {
"type": "Notification",
"messageId": "95df01b4-ee98-5cb9-9903-4c221d41eb5e",
"topicArn": "arn:aws:sns:us-west-2:{{accountId}}:ExampleTopic",
"subject": "example subject",
"message": "example message",
"timestamp": "1970-01-01T00:00:00.000Z",
"signatureVersion": "1",
"signature": "EXAMPLE",
"signingCertUrl": "EXAMPLE",
"unsubscribeUrl": "EXAMPLE",
"messageAttributes": {
"test": {
"type": "String",
"value": "TestString"
},
"testBinary": {
"type": "Binary",
"value": "TestBinary"
}
}
}
}
]
}
while, the actual, it wants is (with capital letters):
{
"Records": [
{
"EventSource": "aws:sns",
"EventVersion": "1.0",
"EventSubscriptionArn": "arn:aws:sns:us-west-2:{{{accountId}}}:ExampleTopic",
"Sns": {
"Type": "Notification",
"MessageId": "95df01b4-ee98-5cb9-9903-4c221d41eb5e",
"TopicArn": "arn:aws:sns:us-west-2:{{accountId}}:ExampleTopic",
"Subject": "example subject",
"Message": "example message",
"Timestamp": "1970-01-01T00:00:00.000Z",
"SignatureVersion": "1",
"Signature": "EXAMPLE",
"SigningCertUrl": "EXAMPLE",
"UnsubscribeUrl": "EXAMPLE",
"MessageAttributes": {
"Test": {
"Type": "String",
"Value": "TestString"
},
"TestBinary": {
"Type": "Binary",
"Value": "TestBinary"
}
}
}
}
]
}
Any idea why is it so? And how can I fix it? Should I use some other object instead of SNSEvent while invoking the lambda.

HAL-based REST service client using Traverson

I am trying to write a Java program to consume the output from a REST-based web service I wrote utilizing the tutorials at spring.io. When I run SpringBoot bootRun the JSON output is built nicely with embedded data and links, and looks like this:
{
"_embedded": {
"invoiceList": [
{
"id": 4,
"seqNum": 1,
"fileId": null,
"fileName": null,
"invoiceNumber": "10080",
"invoiceDate": "2018-06-18T05:00:00.000+0000",
"invoiceTotal": 1000,
"sourceLastModified": "2018-11-30T16:22:23.000+0000",
"lastModified": "2018-11-30T16:22:23.000+0000",
"validFrom": "2018-11-30T16:22:23.000+0000",
"validTo": "9999-12-31T06:00:00.000+0000",
"_links": {
"self": {
"href": "http://localhost:8086/vadir/dental/invoices/4"
},
"invoices": {
"href": "http://localhost:8086/vadir/dental/invoices"
},
"claims": {
"href": "http://localhost:8086/vadir/dental/claims?invoice=10080"
}
}
},
{
"id": 5,
"seqNum": 1,
"fileId": null,
"fileName": null,
"invoiceNumber": "10080",
"invoiceDate": "2018-06-18T05:00:00.000+0000",
"invoiceTotal": 500,
"sourceLastModified": "2018-11-30T16:22:23.000+0000",
"lastModified": "2018-11-30T16:22:23.000+0000",
"validFrom": "2018-11-30T16:22:23.000+0000",
"validTo": "9999-12-31T06:00:00.000+0000",
"_links": {
"self": {
"href": "http://localhost:8086/vadir/dental/invoices/5"
},
"invoices": {
"href": "http://localhost:8086/vadir/dental/invoices"
},
"claims": {
"href": "http://localhost:8086/vadir/dental/claims?invoice=10080"
}
}
}
]
},
"_links": {
"self": {
"href": "http://localhost:8086/vadir/dental/invoices/last"
}
}
}
I have found the documentation for Traverson and the SO question at Consuming HAL-based REST. However, when I use
ParameterizedTypeReference<Resources<Resource<DentalInvoice>>> parameterizedTypeReference =
new ParameterizedTypeReference<Resources<Resource<DentalInvoice>>> () {
};
Traverson traverson =
new Traverson (new URI ("http://localhost:8086/vadir/dental/invoices/last"), MediaTypes.HAL_JSON);
Resources<Resource<DentalInvoice>> invoiceResources = traverson
//.follow ((String) null)
.follow ("$._embedded.invoiceList")
.toObject (parameterizedTypeReference);
I get an error: Illegal character in scheme name at index 0: [{"id":4,...
It looks like Traverson is expecting the content of the follow reference to be a link rather than an embedded object. How can I get Traverson to parse the embedded object into a list of Resources of DentalInvoices?
Do I have to create a controller method that only outputs the links to the possible actions just so Traverson has a link to follow?

Spring embedded reading json+hal from JSON content-type

"_embedded": {
"employees": [
{
"id": "1",
"name": "Some Employee",
"_links": {
"self": {
"href": "http://localhost:8080/employees/1"
}
}
},
{
"id": "2",
"name": "Some Employee",
"_links": {
"self": {
"href": "http://localhost:8080/employees/2"
}
}
},
{
"id": "3",
"name": "Some Employee",
"_links": {
"self": {
"href": "http://localhost:8080/employees/3"
}
}
}
]
},
{
"_links": {
"self": {
"href": "http://localhost:8080/employees{?page,size,sort}"
},
"search": {
"href": "http://localhost:8080/employees/search"
}
},
"page": {
"size": 20,
"totalElements": 3,
"totalPages": 1,
"number": 0
}
}
I'm reading a third-party Rest service that returns json content with the above structure or similar (content-type is application/json).
Currently, I created a custom json converter to a generic object that has custom objects for links,pages and a generic type for the embedded object. So that, I read it in some sort of generic way. However, I would like to do it via hateoas objects. I tried several approaches but any of them are working.
Following others questions I tried many things like configuring the RestTamplate
#Bean
public OAuth2RestTemplate getOAuth2Rest() {
[...]
OAuth2RestTemplate template = new OAuth2RestTemplate(resourceDetails, clientContext);
List<HttpMessageConverter<?>> existingConverters = template.getMessageConverters();
List<HttpMessageConverter<?>> newConverters = new ArrayList<>();
newConverters.add(getHalMessageConverter());
newConverters.addAll(existingConverters);
template.setMessageConverters(newConverters);
return template;
}
private HttpMessageConverter getHalMessageConverter() {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
objectMapper.registerModule(new Jackson2HalModule());
MappingJackson2HttpMessageConverter halConverter =
new TypeConstrainedMappingJackson2HttpMessageConverter(ResourceSupport.class);
halConverter.setSupportedMediaTypes(Arrays.asList(HAL_JSON));
halConverter.setObjectMapper(objectMapper);
return halConverter;
}
Then
ResponseEntity<Resources<MyDto>> entity = restTemplate.exchange(
myUri,
HttpMethod.GET, null,
new ParameterizedTypeReference<Resources<MyDto>>() {}, Collections.emptyMap());
There are similar questions like Consuming Spring Hateoas Pageable but they are consuming json+hal content. I think here is my problem because the deserializer uses the json converter and not the hal converter.
I tried
#EnableHypermediaSupport(type = EnableHypermediaSupport.HypermediaType.HAL)
Didn't work and interferes with my REST configuration.
I tried this blog

How to parse a DiagnosticReport from a JSON and print the same JSON again?

I'm parsing a DiagnosticReport from a JSON file and It works fine, but when I try to print the same JSON file throught IParser encode function, the JSON is different to the original. I need to print the same JSON.
Original JSON (String json)
{
"resourceType": "DiagnosticReport",
"text": {
"status": "generated",
"div": "<div><p><b>Narrative A</b></p></div>"
},
"contained": [
{
"resourceType": "Patient",
"id": "1"
},
{
"resourceType": "Observation",
"id": "2",
"meta": {
"lastUpdated": "2017-03-22T22:00:28.089-05:00"
},
"text": {
"div": "<div><p><b>Narrative B</b></p></div>"
},
"comment": "a comment"
}
],
"status": "appended",
"code": {
"coding": [
{
"code": "Report01"
}
]
},
"subject": {
"reference": "#1"
},
"effectiveDateTime": "2017-03-22T22:00:28-05:00",
"issued": "2017-03-22T22:00:28.070-05:00",
"result": [
{
"reference": "#2"
}
]
}
First step is parse and the second step is encode and print
DiagnosticReport report = parser.parseResource(DiagnosticReport.class, json);
String encodeJSON = parser.encodeResourceToString(report);
System.out.println(encodeJSON);
And the result is different because the text tag in the Observation is not showed
{
"resourceType": "DiagnosticReport",
"text": {
"status": "generated",
"div": "<div xmlns=\"http://www.w3.org/1999/xhtml\"><p><b>Narrative A</b></p></div>"
},
"contained": [
{
"resourceType": "Patient",
"id": "1"
},
{
"resourceType": "Observation",
"id": "2",
"meta": {
"lastUpdated": "2017-03-22T22:00:28.089-05:00"
},
"comment": "a comment"
}
],
"status": "appended",
"code": {
"coding": [
{
"code": "Report01"
}
]
},
"subject": {
"reference": "#1"
},
"effectiveDateTime": "2017-03-22T22:00:28-05:00",
"issued": "2017-03-22T22:00:28.070-05:00",
"result": [
{
"reference": "#2"
}
]
}
I'm trying this because I have a DiagnosticReport generated by my software and I need print it completely in a JSON file.
Thanks for your help!!
It's not legal to have narrative in a contained resource, nor is it legal to have meta/lastUpdated. There are invariants that prohibit both. Ideally, the parsing software should have thrown an exception, but it's not overly surprising that the serializer has trouble serializing content that's not supposed to be there.
Look at dom-1 and dom-4 in dstu3 or dstu2

REST hypermedia/links to collections

Part of REST best practice is to make use of links in the responses to allow clients to navigate from one entity to another.
For example if I had a customer object type which has child account. If I was to request a customer using /customers/1 then I might provide the following response
{
"self": "http://localhost:43002/rest/v1/customers/1",
"id": 1,
"name": "Isabella Button",
"number": "000001",
"forename": "Isabella",
"surname": "Button",
"accounts": [
{
"self": "http://localhost:43002/rest/v1/accounts/1",
"id": 1,
"name": "Main Account",
"number": "000001",
"currency": "GBP",
"fromDate": "2013-01-01",
"toDate": "9999-01-01",
"createdDttm": "2013-01-01T00:00:00.000"
}
]
}
Note the self property holds the links.
However let's say I didn't want to return the accounts in the customer query, perhaps the number of accounts might be very large so I don't want to return them by default.
{
"self": "http://localhost:43002/rest/v1/customers/1",
"id": 1,
"name": "Isabella Button",
"number": "000001",
"forename": "Isabella",
"surname": "Button"
}
A resource URL for a customer's accounts could be /customers/1/accounts
However with the customer response above the client would be unable to discover the /customers/1/accounts link.
Is there a best practice for providing hyperlinks in a response that point to "child" collections of the returned resource?
One practice is to use a links element like this:
{
"self": "http://localhost:43002/rest/v1/customers/1",
"id": 1,
"name": "Isabella Button",
"number": "000001",
"forename": "Isabella",
"surname": "Button",
"links" : [
{
"rel" : "http://www.yourapi.com/rels/accounts",
"href" : "http://localhost:43002/rest/v1/customers/1/accounts"
},
{
"rel" : "http://www.yourapi.com/rels/someOtherCollection",
"href" : "http://localhost:43002/rest/v1/customers/1/someOtherCollection",
}
]
}
Or, if you find easier to construct/read the response, you can put the same links as Link http headers.
provide a links attribute like in this example http://caines.ca/blog/programming/json-is-under-defined-for-rest/
{
"links": {
"self" : { "href": "{id}" },
"up" : { "href": "{upId}" },
"children" : { "href": "{id}/children" }
}
}

Categories