I receive the following JSON response from my http web service
{"status":100,"data":[{"name":"kitchen chair","price":25.99,"description":null}]}
Now I want to be able to deserialize this. I stumbled upon Gson from Google, which at first worked well for some small testcases but I'm having some trouble getting the above string deserialized.
I know the data field only has one item in it, but it could hold more than one which is why all responses have the data field as an array.
I was reading the Gson User Guide and ideally I would like to have a Response object which has two attributes: status and data, but the data field would have to be a List of Map objects which presumably is making it hard for Gson.
I then looked at this which is an example closer to my problem but I still can't quite figure it out. In the example the whole response is an array, whereas my JSON string has one string element and then an array.
How would I best go about deserializing this?
It's not clear what exactly was already attempted that appeared to be "hard for Gson".
The latest release of Gson handles this simply. Following is an example.
input.json
{
"status":100,
"data":
[
{
"name":"kitchen chair",
"price":25.99,
"description":null
}
]
}
Response.java
import java.util.List;
import java.util.Map;
class Response
{
int status;
List<Map> data;
}
GsonFoo.java
import java.io.FileReader;
import com.google.gson.Gson;
public class GsonFoo
{
public static void main(String[] args) throws Exception
{
Response response = new Gson().fromJson(new FileReader("input.json"), Response.class);
System.out.println(new Gson().toJson(response));
}
}
Output:
{"status":100,"data":[{"name":"kitchen chair","price":25.99}]}
Related
I do maintain an small REST API (SpringBoot) offering services to obtain data about ticketing from a small sized retailer. i.e.: you can send a request in order to get some information from a certain ticket (which has unique ID); the JSON response consists of a selection of fields from the unique ticket (which is stored as an unique document in Mongo DB).
Let say the API receives a request, then it would execute a query to Mongo DB, and then apply a projection to parse the queried data into a data model class, which in turn is finally parsed to a response JSON like, i.e.:
{
"ticketData": {
"retailerId": "023",
"ticketId": "09834723469324",
"ticketDate": "2021-06-20"
},
"buyerData": {
"buyerId": "LN4382"
}
}
Well, I am now required to return the entire queried JSON (that is, a JSON containing the whole ticket information, that has a lot of fields). ¿Is there any way to achieve this without creating a data model class with tens or hundreds of properties to match the stored ticket JSON? Even if I specify the API response using YAML and then use a codegen tool, is a lot of tedious work, and whenever the ticket JSON format evolves, I would need to change my DAO and response.
I just would like to send the original Mongo stored JSON and hand it back to the API client. Is there any way to achieve that?
You can utilize Jackson ObjectMapper which Spring already uses to serialize and deserialize JSON.
import java.io.IOException;
import java.io.UncheckedIOException;
import java.util.ArrayList;
import java.util.List;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
#RestController
public class HelloWorldController {
private final ObjectMapper objectMapper;
public HelloWorldController(ObjectMapper objectMapper) {
this.objectMapper = objectMapper;
}
#GetMapping("/jsonList")
public ResponseEntity<List<JsonNode>> getJsonList() {
List<String> data = List.of("{\"number\": 1}",
"{\"number\": 2}",
"{\"number\": 3}");
List<JsonNode> nodes = toJsonNodeList(data);
return ResponseEntity.ok(nodes);
}
private List<JsonNode> toJsonNodeList(List<String> strings) {
List<JsonNode> nodes = new ArrayList<>();
for (String s : strings) {
try {
JsonNode node = this.objectMapper.readTree(s);
nodes.add(node);
} catch (IOException ioe) {
throw new UncheckedIOException(ioe);
}
}
return nodes;
}
}
I'm currently facing a weird bahaviour when testing the following controller using Spring WebFlux (v. 5.2.6)
#RestController
#RequestMapping(path = "/address", produces = MediaType.APPLICATION_JSON_VALUE)
public class AddressController {
#GetMapping(path = "/postcode")
public Flux<String> listPostCodes(...) {
return Flux.just("4711", "4712");
}
#GetMapping(path = "/cities")
public Flux<City> listCities() {
return Flux.just(new City("foo"), new City("bar"));
}
}
This controller is embedded within a "hello world" Spring-Boot application using spring-webflux-starter and a simple main class. Class City has just one property "name".
Now I have the following test (Junit5) to ensure response from the above mentioned controller
#SpringWebFluxTest
public AddressControllerTest {
#Test
public void postcodes() {
webTestClient.get()
.uri("/address/postcode")
.exchange()
.expectStatus()
.isOk()
.expectBody()
.jsonPath("$")
.isArray()
.jsonPath("$[0]")
.isEqualTo("4711")
}
#Test
public void cities() {
webTestClient.get()
.uri("/address/cities")
.exchange()
.expectStatus()
.isOk()
.expectBody()
.jsonPath("$")
.isArray()
.jsonPath("$[0].name")
.isEqualTo("foo")
}
}
You'd expected both test to pass? Me too. Unfortunately the first fails, telling me that the response body root isn't a json array, but a Long:
Expected: an instance of java.util.List
but: <47114712L> is a java.lang.Long
Why's that? Both responses are Flux, so I'd expect both response bodies to be an array, but only if the elements aren't "simple" types this seems to work in test. If I use postman to assert that behavior everything works exactly as expected, so I'd assume a testing problem somehow.
Could somebody explain that to me or might have a solution to this?
Thanks in advance
It's not an issue with the tests, the actual behaviour isn't what you expect.
Both responses are Flux, so I'd expect both response bodies to be an array
A Flux is not analogous to a list. It's a stream of data, which can be output verbatim, or collected into some other data structure when it's complete (a list being one example.)
In this case of course, the content type specified indicates you want that Flux to be collected into a list where possible - but this isn't universally the case. With POJOs, collections & arrays, Jackson will serialise them, and output an array. But here we're using raw strings, not using a JSON serialiser for those strings, so it's just concatenating & outputting them raw as they appear. (The concatenated postcodes just so happen to be all digits of course, hence why you then get an error about a long value.)
If you want a JSON array, you'll need to use:
#GetMapping(path = "/postcode")
public Mono<List<String>> listPostCodes() {
return Flux.just("4711", "4712").collectList();
}
To dispel a common myth with this approach ahead of time - you're not blocking by using collectList(), and you're not losing anything underneath the covers (since even if you use a Flux, the framework will still need to collect to a list internally to return the JSON array.)
We've standardized on using JSON:API for our REST endpoints, however; not all of our data revolves around repositories and it seems that CRNK requires repositories in order to work.
Is that correct?
Example
I wrote a very simple Spring Boot 2.1.9 example that has a single controller and included CRNK in it, but when I get into my controller I do not get the expected JSON:API output.
Please keep in mind, I am just starting to look at CRNK and this is just a simple "hello world" type of application that I am testing with
Here is my example
package com.example.crnkdemo;
import org.springframework.web.bind.annotation.*;
#RestController
#RequestMapping("/test/v1.0")
public class Controller {
#GetMapping(value = "/{country}", produces = "application/vnd.api+json")
public Country test1(#PathVariable String country, #RequestParam(name = "filter[region]", required = false) String filter) {
return new Country(country, filter);
}
}
Country is just a dummy class I created which is:
package com.example.crnkdemo;
import io.crnk.core.resource.annotations.JsonApiId;
import io.crnk.core.resource.annotations.JsonApiResource;
#JsonApiResource(type = "country")
#AllArgsConstructor
#Data
public class Country {
#JsonApiId
private String country;
private String region;
Results
But when I use the following URL http://localhost:8080/test/v1.0/US?filter[region]=northeast I get
{
"country": "US",
"region":"northeast"
}
I would have expected the JSON API type of result
{
"data": {
"type": "country",
"id": "US",
"attributes": {
"region": "northeast"
}
}
Thanks!
I ran into similar issue and the problem was that I got io.crnk:crnk-format-plain-json in my dependencies (simply copied from an example app) which changes the way how the responses look like (non-JSON-API). So first have a look into your maven/gradle configuration.
"not all of our data revolves around repositories"
you may also have a look at http://www.crnk.io/releases/stable/documentation/#_architecture where the architecture of resource-oriented framework like Crnk and JSON:API are discussed in more detail. In principle one can model everything as repository. Applications usually follow two kinds of patterns: CRUD ones and "actions". CRUD is simple: GET, POST, PATCH, DELETE objects. A repository is a perfect match for that. In contrast, people have a harder time when it comes to "actions". But this can be modelled as well as CRUD. For example, POSTing an AddressChange resource may trigger a server to start modifying the address(es) of some objects. This may happend immediately or take a longer time. Subsequent GET requests for the POSTed resources will reveal the current status of the action. And a DELETE request can cancel the request.
Crnk itself is not in need for Controllers as Spring MVC is. This kind of "lower-level plumbing" is taken care by Crnk itself because JSON:API specifies how a REST layer is supposed to look like. So there is no need to write custom code to specify urls patterns, parameters, etc. as in the MVC example above. Instead one can implement a repository:
public class TaskRepositoryImpl extends ResourceRepositoryBase<Task, Long> {
private ConcurrentHashMap<Long, Task> tasks = new Concurrent...
public TaskRepositoryImpl() {
super(Task.class);
}
#Override
public <S extends Task> S create(S entity) {
map.put(entity.getId(), entity);
return entity;
}
#Override
public ResourceList<Task> findAll(QuerySpec querySpec) {
return querySpec.apply(tasks.values());
}
...
}
There are also many built-in defult repository implementatons like for in-memory, JPA, security to cover the most frequent use cases.
with crnk, no need of writing controllers, manager classes. By default the controllers are defined.
Once we define the resources, we can access it by http://server_name:portno/crnk-path-prefix-property/defined_resourcename & the method type
Eg. In our case, resource is country, let's say server is running in localhost:8081 and crnk-path-prefix is /api/v1, then the url is http://localhost:8081/api/v1/country & set method type is GET, it will give the desired output. Remember to set content-type as application/vnd.api+json.
For POST, same url and set method type as POST, pass the data object
For PATCH, same url along with id attribute appended to the url and set method type as PATCH & pass the data object
I am attempting to implement a filter in a micronaut microservice, using the example code documented in Section 6.18 of the documentation:
https://docs.micronaut.io/latest/guide/index.html#filters
I have a HelloWord service that is essentially the same as the service provided on the documentation, with a controller that goes to "/hello" (as documented). I am also using the same TraceService and trace filter that is provided in Section 6.18. I am compiling and running the server without problems.
Unfortunately, the filter is not being engaged when I test the microservice.
I am pretty sure that something is missing in my code, but as I said I am using the same code that is in the example:
TraceService Class
import io.micronaut.http.HttpRequest;
import io.reactivex.Flowable;
import io.reactivex.schedulers.Schedulers;
import org.slf4j.*;
import javax.inject.Singleton;
#Singleton
public class TraceService {
private static final Logger LOG = LoggerFactory.getLogger(TraceService.class);
Flowable<Boolean> trace(HttpRequest<?> request) {
System.out.println("TRACE ENGAGED!");
return Flowable.fromCallable(() -> {
if (LOG.isDebugEnabled()) {
LOG.debug("Tracing request: " + request.getUri());
}
// trace logic here, potentially performing I/O
return true;
}).subscribeOn(Schedulers.io());
}
}
Trace Filter
import io.micronaut.http.*;
import io.micronaut.http.annotation.Filter;
import io.micronaut.http.filter.*;
import org.reactivestreams.Publisher;
#Filter("/hello/**")
public class TraceFilter implements HttpServerFilter {
private final TraceService traceService;
public TraceFilter(TraceService traceService) {
System.out.println("Filter created!");
this.traceService = traceService;
}
#Override
public Publisher<MutableHttpResponse<?>> doFilter(HttpRequest<?> request, ServerFilterChain chain) {
System.out.println("Filter engaged!");
return traceService.trace(request)
.switchMap(aBoolean -> chain.proceed(request))
.doOnNext(res -> res.getHeaders().add("X-Trace-Enabled", "true")
);
}
}
The Controller
import io.micronaut.http.annotation.*;
#Controller("/hello")
public class HelloController {
#Get("/")
public String index() {
return "Hello World";
}
}
Note that the controller uses code from Section 2.2 of the documentation:
https://docs.micronaut.io/latest/guide/index.html#creatingServer
I did a number of things to try and see what was happening with the filter, including putting little printouts in strategic parts of the Service and the filter. These printouts are not printing out, which tells me that the filter is not being created or used by Micronaut.
Clearly I am missing somethning. I suspect that there is something I need to do in order to get the system to engage the filter. Unfortunately the documentation just tells how to make the filter, not how to use it in the microservice. Furthermore, there don't appear to be any complete code examples that tell how to make the request system utilize the filter (maybe there is an annotation I need to add to the controller???).
Could someone tell me what I am missing? How do I get the filter to work? At the very least, could someone provide a complete example of how to create the filter and use it in an actual microservice?
Problem solved.
It actually helps a great deal if one puts the filter and service files in the right place. It was late when I made the files and I put them in the test area, not the development area. Once placed in the right place, the filter was properly injected into the microservice.
Sorry for the waste of space here, folks. Is there any way a poster can delete an embarrassing post?
Suppose we have this JSON:
[
{
"__typename": "Car",
"id": "123",
"name": "Toyota Prius",
"numDoors": 4
},
{
"__typename": "Boat",
"id": "4567",
"name": "U.S.S. Constitution",
"propulsion": "SAIL"
}
]
(there could be many more elements to the list; this just shows two)
I have Car and Boat POJOs that use a Vehicle base class for the common fields:
public abstract class Vehicle {
public final String id;
public final String name;
}
public class Car extends Vehicle {
public final Integer numDoors;
}
public class Boat extends Vehicle {
public final String propulsion;
}
The result of parsing this JSON should be a List<Vehicle>. The problem is that no JSON parser is going to know, out of the box, that __typename is how to distinguish a Boat from a Car.
With Gson, I can create a JsonDeserializer<Vehicle> that can examine the __typename field, identify whether this is a Car or Boat, then use deserialize() on the supplied JsonDeserializationContext to parse the particular JSON object into the appropriate type. This works fine.
However, the particular thing that I am building ought to support pluggable JSON parsers, and I thought that I would try Moshi as an alternative parser. However, this particular problem is not covered well in the Moshi documentation at the present time, and I am having difficulty figuring out how best to address it.
The closest analogue to JsonDeserializer<T> is JsonAdapter<T>. However, fromJson() gets passed a JsonReader, which has a destructive API. To find out what the __typename is, I would have to be able to parse everything by hand from the JsonReader events. While I could call adapter() on the Moshi instance to try to invoke existing Moshi parsing logic once I know the proper concrete type, I will have consumed data off of the JsonReader and broken its ability to provide the complete object description anymore.
Another analogue of JsonDeserializer<Vehicle> would be a #FromJson-annotated method that returns a Vehicle. However, I cannot identify a simple thing to pass into the method. The only thing that I can think of is to create yet another POJO representing the union of all possible fields:
public class SemiParsedKindOfVehicle {
public final String id;
public final String name;
public final Integer numDoors;
public final String propulsion;
public final String __typename;
}
Then, in theory, if I have #FromJson Vehicle rideLikeTheWind(SemiParsedKindOfVehicle rawVehicle) on a class that I register as a type adapter with Moshi, Moshi might be able to parse my JSON objects into SemiParsedKindOfVehicle instances and call rideLikeTheWind(). In there, I would look up the __typename, identify the type, and completely build the Car or Boat myself, returning that object.
While doable, this is a fair bit more complex than the Gson approach, and my Car/Boat scenario is on the simple end of the possible data structures that I will need to deal with.
Is there another approach to handling this with Moshi that I am missing?
The moshi-adapters add-on library contains a PolymorphicJsonAdapterFactory class. While the JavaDocs for this library do not seem to be posted, the source does contain a detailed description of its use.
The setup for the example in my question would be:
private val moshi = Moshi.Builder()
.add(
PolymorphicJsonAdapterFactory.of(Vehicle::class.java, "__typename")
.withSubtype(Car::class.java, "Car")
.withSubtype(Boat::class.java, "Boat")
)
.build()
Now, our Moshi object knows how to convert things like List<Vehicle> to/from JSON, based on the __typename property in the JSON, comparing it to "Car" and "Boat" to create the Car and Boat classes, respectively.
UPDATE 2019-05-25: The newer answer is your best bet. I am leaving my original solution here for historical reasons.
One thing that I had not taken into account is that you can create a type adapter using a generic type, like Map<String, Object>. Given that, you can create a VehicleAdapter that looks up __typename. It will be responsible for completely populating the Car and Boat instances (or, optionally, delegate that to constructors on Car and Boat that take the Map<String, Object> as input). Hence, this is still not quite as convenient as Gson's approach. Plus, you have to have a do-nothing #ToJson method, as otherwise Moshi rejects your type adapter. But, otherwise, it works, as is demonstrated by this JUnit4 test class:
import com.squareup.moshi.FromJson;
import com.squareup.moshi.JsonAdapter;
import com.squareup.moshi.Moshi;
import com.squareup.moshi.ToJson;
import com.squareup.moshi.Types;
import org.junit.Assert;
import org.junit.Test;
import java.io.IOException;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Map;
import static org.junit.Assert.assertEquals;
public class Foo {
static abstract class Vehicle {
public String id;
public String name;
}
static class Car extends Vehicle {
public Integer numDoors;
}
static class Boat extends Vehicle {
public String propulsion;
}
static class VehicleAdapter {
#FromJson
Vehicle fromJson(Map<String, Object> raw) {
String typename=raw.get("__typename").toString();
Vehicle result;
if (typename.equals("Car")) {
Car car=new Car();
car.numDoors=((Double)raw.get("numDoors")).intValue();
result=car;
}
else if (typename.equals("Boat")) {
Boat boat=new Boat();
boat.propulsion=raw.get("propulsion").toString();
result=boat;
}
else {
throw new IllegalStateException("Could not identify __typename: "+typename);
}
result.id=raw.get("id").toString();
result.name=raw.get("name").toString();
return(result);
}
#ToJson
String toJson(Vehicle vehicle) {
throw new UnsupportedOperationException("Um, why is this required?");
}
}
static final String JSON="[\n"+
" {\n"+
" \"__typename\": \"Car\",\n"+
" \"id\": \"123\",\n"+
" \"name\": \"Toyota Prius\",\n"+
" \"numDoors\": 4\n"+
" },\n"+
" {\n"+
" \"__typename\": \"Boat\",\n"+
" \"id\": \"4567\",\n"+
" \"name\": \"U.S.S. Constitution\",\n"+
" \"propulsion\": \"SAIL\"\n"+
" }\n"+
"]";
#Test
public void deserializeGeneric() throws IOException {
Moshi moshi=new Moshi.Builder().add(new VehicleAdapter()).build();
Type payloadType=Types.newParameterizedType(List.class, Vehicle.class);
JsonAdapter<List<Vehicle>> jsonAdapter=moshi.adapter(payloadType);
List<Vehicle> result=jsonAdapter.fromJson(JSON);
assertEquals(2, result.size());
assertEquals(Car.class, result.get(0).getClass());
Car car=(Car)result.get(0);
assertEquals("123", car.id);
assertEquals("Toyota Prius", car.name);
assertEquals((long)4, (long)car.numDoors);
assertEquals(Boat.class, result.get(1).getClass());
Boat boat=(Boat)result.get(1);
assertEquals("4567", boat.id);
assertEquals("U.S.S. Constitution", boat.name);
assertEquals("SAIL", boat.propulsion);
}
}