How to validate list body in Javalin - java

My DELETE request accepts a list of Items that should be deleted.
I want to validate that the request body is a valid list of Item objects.
The example given in the Javalin docs doesn't mention lists.
In order to get the code to compile, I had to do this:
TypedValidator<List> requestValidator = ctx.validatedBodyAsClass(List.class);
List<Item> items = requestValidator.getOrThrow();
logger.info("Received delete request for {}", Arrays.toString(items.toArray()));
logger.info("items is type {}", items.getClass());
for (Item item : items) {
logger.info("Deleting {}", item.name);
}
The validation passes and the ctx body is printed correctly in the following line.
The problem is, there is an unchecked assignment at getOrThrow() and indeed the loop doesn't work:
[qtp1679441380-34] INFO com.myorg.MyClass - Received delete request for [{name=FooName, type=BarType}]
[qtp1679441380-34] INFO com.ericsson.cdzm.ws.controllers.ScheduleController - items is type class java.util.ArrayList
[qtp1679441380-34] WARN io.javalin.core.ExceptionMapper - Uncaught exception
java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to com.myorg.Item
at com.myorg.MyClass.deleteItems(MyClass.java:51)
Edit: The java.util.LinkedHashMap seems to be because actually, items is of type ArrayList<LinkedHashMap<String,String>>. In other words, Javalin didn't parse or validate the body contents at all! It only converted the Json name=value mappings into a Java Map.
What would be a better way to validate the incoming Json and parse it to a list of Items?
I've tested on Javalin 2.6.0 and 2.8.0.

What I had missed, and can't find in the Javalin docs, was that I have to use array types rather than parameterized Collection types. This works:
TypedValidator<Item[]> requestValidator = ctx.bodyValidator(Item[].class);
List<Item> items = Arrays.asList(requestValidator.get());
Still, would love to know why this is - I suspect it is related to Java's type system somehow?
I could also directly access the Jackson ObjectMapper object and use it like you would use Jackson. The drawback is that I don't benefit from Javalin's automatic throwing of BadRequestResponse etc. I think using array types is a small price to pay for this.
try {
List<ScheduleRequest> items = JavalinJackson.getObjectMapper().readValue(ctx.body(), new TypeReference<List<ScheduleRequest>>(){});
} catch (IOException e) {
throw new BadRequestResponse(e.getMessage());
}

Related

Vert.x - Not able to decode json to list of objects

I'm using the latest version of Vert.x (4.3.2) and I wrote and handler for a route that serves a POST method.
Below my code to convert the JSON body of that method to a List of objects:
Class<List<CartLineItem>> cls = (Class<List<CartLineItem>>)(Object)List.class;
List<CartLineItem> itemsToAdd = rc.body().asPojo(cls);
Unforntunately whenever I try to iterate over itemsToAdd I get the exception: java.lang.ClassCastException: class java.util.LinkedHashMap cannot be cast to class com.acme.CartLineItem.
How can I fix this issue without using any other library except Vert.x (I mean .. using Jackson I would provide a TypeReference<List> object)?
In Vert.x 3 there was method that you could use:
Json.decodeValue(result.bodyAsString(), new TypeReference<List<ConditionDTO>>() {});
Since in Vert.x 4 this was removed you will need to use specific class DatabindCodec:
io.vertx.core.json.jackson.DatabindCodec.fromString(result.bodyAsString(), new TypeReference<List<SimpleDTO>>() {})
If you don't want to use external libraries there is an option to iterate over an jsonArray that you have in body assuming body is something like this:
[{"name":"Lazar", "age":27},{"name":"Nikola", "age":33}]
List<SimpleDTO> list = routingContext.body().asJsonArray()
.stream()
.map(json -> Json.decodeValue(json.toString(), SimpleDTO.class))
.collect(Collectors.toList());

Read a list of parameters in order /1;2;3;4;5

Is it possible to read a parameter list like example.com/1;2;3;4;5 in the same order as provided by the URL?
My current not working approach is:
public Response(#PathParam("list") PathSegment list) {
Set<String> listParams = list.getMatrixParameters().keySet();
List<String> listList = new ArrayList<>(list.size() + 1);
listList.add(list.getPath());
for (String param : list) {
listList.add(param);
}
}
It does work for 1;2;3;4;5, but it does not work for 1;5;4;3;2. In both cases the output would be 1;2;3;4;5. The matrixParameters appear to be sorted alphabetically and not by the input order. Just reading the whole parameter as a String and parsing it manually would be fine, but couldn't find a way to do this either.
Please, take a careful look at the PathSegment documentation.
The matrix parameters are stored in MultivaluedMap. Its only implementation is MultivaluedHashMap. It does not preserve the order of the elements.
I've done a quick check how to get the raw request parameters in JAX-RS. There seems to be no pure JAX-RS way. You could try to work with the raw HttpServletRequest object as in this question to get the information.

Handling inconsistent data types in REST results (json) in Java

I'm new, and attempting to work with the Rest API on setlist.fm from Android Studio, but am having some issues when fitting my GET request results into my Java data model.
Particularly, I have modeled "sets" ("set" refers to a set played at a concert) as a Java class. But commonly, I get results back from my HTTP requests that have "set" as an empty string or even an array.
I'll use this following GET request for all Radiohead setlists as an example:
http://api.setlist.fm/rest/0.1/artist/a74b1b7f-71a5-4011-9441-d0b5e4122711/setlists.json
Notice how, for the most part, "sets" is an object. But in some instances, it is a String. In other instances it is an array.
My Android Studio is giving me the following error when I try to parse the json with Gson into my data model using the following line of code:
gson.fromJson(result.toString(),Response.class);
It appears to be failing on an instance where "sets" is shown an empty string rather than an object:
Expected BEGIN_OBJECT but was STRING at line 1 column 942 path $.setlists.setlist[0].sets
Does anyone have advice on how to handle this type of thing? I've noticed it with all artists I've looked up so far.
Thanks!
Assuming Response is a class you wrote containing the main fields of the json and that at some point in it you have:
#SerializedName("setlist")
private List<MyItem> setlist;
I also assume your MyItem class contains the field:
#SerializedName("sets")
private List<MySet> sets;
if you let Gson parse it it will fail when it found a string instead of a list (-> array) of MySet object.
But you can write a custom TypeAdapter for your MyItem.
There's plenty of documentation about how to write a Gson TypeAdapter, look for it.
Use instanceOf operator to determine the type and cast accordingly.
JSONObject response=new JSONObject(res);
if(res.get("key") instanceOf JSONObject)
{
// code for JSONObject
}
else if(res.get("key") instanceOf JSONArray)
{
// code for JSONOArray
}
And so on

abstractlist protobuf java

I have an object containing a list sent from a C# client to a Java server. The serialization with protobuf work perfectly and the object is received perfectly in Java. But the class generated with protoc.exe (can we call it a proto class?) have a list that i can't modify. Basically, I have to add some values in it before returning it to C#, but when I try to add a value, i have an exception :
Exception in thread "main" java.lang.UnsupportedOperationException
at java.util.AbstractList.add(AbstractList.java:148)
at java.util.AbstractList.add(AbstractList.java:108)
...
Here's how i'm adding values:
MyProtoObject.MyResult result = MyProtoObject.MyResut.NewBuilder()
.setId(1)
.setValue(9.135)
.build();
MyObject.getResultList().add(result);
How can i insert values in it?
Maybe it's somewhat of a workaround, but you could try this:
List<MyResult> l = new ArrayList<MyResult>(MyObject.getResultList());
l.add(result);
MyObject.setResultList(l);
Ok after regenerating the proto class, it appears some methods was missing (I probably made mistakes in first generation). So now i can add values in the list :
MyObjectProto.MyObject o = MyObjectProto.MyObject.newBuilder()
.addAllResults(listOfCalculations)
.build();
listOfCalculation is a List of results objects
or just :
MyObjectProto.MyObject o = MyObjectProto.MyObject.newBuilder()
.addResult(calculationResult)
.build();
CalculationResult is a single result object
Thanks to Flavio

json parsing problems

data: [
{
type: "earnings"
info: {
earnings: 45.6
dividends: 4052.94
gains: 0
expenses: 3935.24
shares_bought: 0
shares_bought_user_count: 0
shares_sold: 0
shares_sold_user_count: 0
}
created: "2011-07-04 11:46:17"
}
{
type: "mentions"
info: [
{
type_id: "twitter"
mentioner_ticker: "LOANS"
mentioner_full_name: "ERICK STROBEL"
}
]
created: "2011-06-10 23:03:02"
}
]
Here's my problem : like you can see the "info" is different in each of one, one is a json object, and one is a json array, i usually choose Gson to take the data, but with Gson we can't do this kind of thing . How can i make it work ?
If you want to use Gson, then to handle the issue where the same JSON element value is sometimes an array and sometimes an object, custom deserialization processing is necessary. I posted an example of this in the Parsing JSON with GSON, object sometimes contains list sometimes contains object post.
If the "info" element object has different elements based on type, and so you want polymorphic deserialization behavior to deserialize to the correct type of object, with Gson you'll also need to implement custom deserialization processing. How to do that has been covered in other StackOverflow.com posts. I posted a link to four different such questions and answers (some with code examples) in the Can I instantiate a superclass and have a particular subclass be instantiated based on the parameters supplied thread. In this thread, the particular structure of the JSON objects to deserialize varies from the examples I just linked, because the element to indicate the type is external of the object to be deserialized, but if you can understand the other examples, then handling the problem here should be easy.
Both key and value have to be within quotes, and you need to separate definitions with commas:
{
"key0": "value0",
"key1": "value1",
"key2": [ "value2_0", "value2_1" ]
}
That should do the trick!
The info object should be of the same type with every type.
So check the type first. Pseudocode:
if (data.get('type').equals("mentions") {
json_arr = data.get('info');
}
else if (data.get('type').equals("earnings") {
json_obj = data.get('info');
}
I'm not sure that helps, cause I'm not sure I understand the question.
Use simply org.json classes that are available in android: http://developer.android.com/reference/org/json/package-summary.html
You will get a dynamic structure that you will be able to traverse, without the limitations of strong typing.....
This is not a "usual" way of doing things in Java (where strong typing is default) but IMHO in many situations even in Java it is ok to do some dynamic processing. Flexibility is better but price to pay is lack of compile-time type verification... Which in many cases is ok.
If changing libraries is an option you could have a look at Jackson, its Simple Data Binding mode should allow you to deserialize an object like you describe about. A part of the doc that is probably quite important is this, your example would already need JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES to work...
Clarification for Bruce: true, in Jackson's Full Data Binding mode, but not in Simple Data Binding mode. This is simple data binding:
public static void main(String[] args) throws IOException {
File src = new File("test.json");
ObjectMapper mapper = new ObjectMapper();
mapper.configure(JsonParser.Feature. ALLOW_UNQUOTED_FIELD_NAMES, true);
mapper.configure(JsonParser.Feature.ALLOW_COMMENTS,true);
Object root = mapper.readValue(src, Object.class);
Map<?,?> rootAsMap = mapper.readValue(src, Map.class);
System.out.println(rootAsMap);
}
which with OP's sightly corrected sample JSON data gives:
{data=[{type=earnings, info={earnings=45.6, dividends=4052.94, gains=0,
expenses=3935.24, shares_bought=0, shares_bought_user_count=0, shares_sold=0,
shares_sold_user_count=0}, created=2011-07-04 11:46:17}, {type=mentions,
info=[{type_id=twitter, mentioner_ticker=LOANS, mentioner_full_name=ERICK STROBEL}],
created=2011-06-10 23:03:02}]}
OK, some hand-coding needed to wire up this Map to the original data, but quite often less is more and such mapping code, being dead simple has the advantage of being very easy to read/maintain later on.

Categories