I'm trying to take JSON objects and put them into a collection (I picked List). I've been able to unmarshal the JSON responses into single POJOs by creating BuiltCharacter with the appropriate getters and setters. For an array of JSON elements, I tried the following approaches:
List<BuiltCharacter> characters = response.readEntity(new GenericType<List<BuiltCharacter>>(){});
and
List<BuiltCharacter> characters = client.target(uri).request(MediaType.APPLICATION_JSON).get(new GenericType<List<BuiltCharacter>>(){});
Using those approaches got me the following:
Exception in thread "main" java.lang.ClassCastException: BuiltCharacter cannot be cast to java.util.List
I originally used Character as the class name but was reminded that that is a reserved class name. Still, I can't figure out what's going on here!
Answer in regards to bounty, but it seems to be the same problem from the original post
Looking at the link you provided, all the examples return SalesType, and not a List<SalesType>. You can't expect a SalesType to be converted to a List<SalesType>. If you are returning SalesType from your resource class, the exception provided.
Even from the link for the original post, the JSON is coming in as JSON object. List doesn't map to a JSON object ({}), but instead a JSON array ([]).
If you want a list of SalesType, then simply return a list from the resource. Below is a complete example
import java.util.*;
import javax.ws.rs.*;
import javax.ws.rs.core.*;
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.test.JerseyTest;
import org.junit.Test;
class SalesType {
public String test;
}
public class MOXyTest extends JerseyTest {
#Path("test")
public static class TestResource {
#GET
#Produces(MediaType.APPLICATION_JSON)
public Response getSalesTypeList() {
List<SalesType> list = new ArrayList<>();
SalesType sales = new SalesType();
sales.test = "test";
list.add(sales);
list.add(sales);
return Response.ok(
new GenericEntity<List<SalesType>>(list){}).build();
}
}
#Override
public Application configure() {
return new ResourceConfig(TestResource.class);
}
#Test
public void testGetSalesTypeList() {
List<SalesType> list = target("test").request()
.get(new GenericType<List<SalesType>>(){});
for (SalesType sales: list) {
System.out.println(sales.test);
}
}
}
These are the two dependencies I used (making use of Jersey Test Framework)
<dependency>
<groupId>org.glassfish.jersey.test-framework.providers</groupId>
<artifactId>jersey-test-framework-provider-inmemory</artifactId>
<version>${jersey2.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-moxy</artifactId>
<version>${jersey2.version}</version>
</dependency>
Your problem can easily be reproduced by return the sales in the resource class from the example above. The current code above works, but just to see the error, if you replace the GenericEntity (which is a wrapper for generic types being returned in a Response), you will see the same error
java.lang.ClassCastException: com.stackoverflow.jersey.test.SalesType
cannot be cast to java.util.List
Related
I'm trying to send a POJO over an RSocket requestStream:
import java.io.Serializable;
class GreetingRequest implements Serializable {
private String name;
public GreetingRequest() {}
public GreetingRequest(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
If I were to send a String I can do:
ByteBuf data = ByteBufAllocator.DEFAULT.buffer().writeBytes("Hello".getBytes());
socket.requestStream(DefaultPayload.create(data, metadata))
.map(Payload::getDataUtf8)
.toIterable()
.forEach(System.out::println);
But how can I serialise my POJO?
This is my attempt using implementation group: 'org.apache.commons', name: 'commons-lang3', version: '3.12.0' which doesn't work:
GreetingRequest pojo = new GreetingRequest("Davide");
ByteBuf data = SerializationUtils.serialize(pojo);
socket.requestStream(DefaultPayload.create(data, metadata))
.map(Payload::getDataUtf8)
.toIterable()
.forEach(System.out::println);
There is a native java serialization mechanism that I would NOT recommend, but you can read about it. Read about Serialazable interface in Java API. There are 2 options that I would recommend:
JSON-JACKSON (also known as Faster XML)
GSON (mentioned in the answer from César Ferreira)
Both convert classes to JSON and vise-versa. For JSON-JACKSON see class ObectMapper. In particular methods writeValueAsString() or writeValueAsBytes() to serialize your object to JSON string or bytes. And to convert it back look for method readValue().
Here are the Maven artifacts that you would need to use it:
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.12.3</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.12.3</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<version>2.12.3</version>
</dependency>
I recommend you using Gson converter. It helps you to convert a Java Class to a JSON String. And then you can work with the String as if you were working with simple text.
You can import the dependency:
dependencies {
implementation 'com.google.code.gson:gson:2.8.7'
}
And then, can use jsonschema2pojo to convert the JSON:
{ "name": "Test" }
to classes like this:
package com.example;
import javax.annotation.Generated;
import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;
public class GreetingRequest {
#SerializedName("name")
#Expose
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
After all is done you can do something like this in Java:
Gson converter = new Gson();
GreetingsRequest request = new GreetingRequest();
request.setName("Test");
String greetingsJSON = converter.toJson(request);
And then you can still send the JSON string as it follows:
ByteBuf data = ByteBufAllocator.DEFAULT.buffer().writeBytes(greetingsJSON.getBytes());
socket.requestStream(DefaultPayload.create(data, metadata))
.map(Payload::getDataUtf8)
.toIterable()
.forEach(System.out::println);
Data conversions:
JSON Object - Java class
Array - List<>
Helpful links:
This is the library you need to include (tutorials included) in Java: GSON Converter Git
This is an JSON to Class online converter: Jsonschema2pojo generator
If you use a framework like Spring Boot this is taken care of for you. You may want to manually control in which case the other examples are more relevant, but there are productivity benefits to Spring Boot or rsocket-rpc.
https://github.com/rsocket/rsocket-demo/blob/master/src/main/kotlin/io/rsocket/demo/chat/ChatController.kt
https://spring.io/blog/2020/03/02/getting-started-with-rsocket-spring-boot-server
or rsocket-rpc-java using protobuf instead of Serialization
https://github.com/rsocket/rsocket-rpc-java/blob/master/docs/get-started.md
I have a data class, something like this:
public class Person {
private String name;
private Long code;
// corresponding getters and setters
}
I want to write two web services that provide two different JSON representation of Person. For example, one of them provide {"name":"foo"} but the other one {"name":"foo", "code":"123"}.
As a more complicated scenario, suppose that Person has a reference property, for example address. Address has its own properties as well and I expect that both of my web services consider this property but each of which do this in their own manner.
How should my SpringMVC views be like?
Please note that I'm new to SpringMVC. So give me a sample code beside your answer, please.
UPDATE 1: After few days, all answers push me to solve the problem in controllers or by annotating the data classes. But I want to do this in views, without any more java codes. Can I do it in JSP files or thymeleaf templates or even in .properties files?
UPDATE 2: I found json-taglib. But somehow it is left out of new upgrades. Is there any similar solution?
You're using Spring-MVC so Jackson is in charge of JSON serialize and deserializing.
In this case, you can use #JsonInclude(Include.NON_NULL) to ignore null field during serialization.
public class Person {
#JsonInclude(Include.NON_NULL)
private String name;
#JsonInclude(Include.NON_NULL)
private Long code;
// corresponding getters and setters
}
If your name or code is null then it is excluded from output JSON
So if you pass code as null, your ouput JSON will look like {"name":"foo"}
When creating JSon with Spring MVC the "view renderer", by default, is Jackson. There is no need to use things like JSP or other view technology. What you want to do, is to tell Jackson how to render an object for a given situation. Multiple options are available, but I would suggest to use projections.
An example:
#RestController
#RequestMapping(value = "person")
public class PersonController {
private final ProjectionFactory projectionFactory;
public PersonController(ProjectionFactory projectionFactory) {
this.projectionFactory = projectionFactory;
}
#GetMapping("...")
public PersonBase getPerson(..., #RequestParam(value = "view", required = false, defaultValue = "base") String view) {
...
if(view.equals("extended")) {
return projectionFactory.createProjection(PersonExtended.class, person);
} else {
return projectionFactory.createProjection(PersonBase.class, person);
}
}
}
public interface PersonBase {
String getName();
}
public interface PersonExtended extends PersonBase {
Long getCode;
}
The view layer of your application are the projection classes (put then in one package, the view package if you wish).
A Controller can choose what view to render, or you could make the result dynamic as in the example.
Your question on how to render the address could be solved with another projection like this:
public interface PersonWithAddress extends PersonExtended {
AddressProjection getAddress();
}
public interface AddressProjection {
String getStreetName();
String getZipcode();
...
}
You can look for dynamic filtering of fields using MappingJacksonValue.
The filter allows you to serialize fields that meet custom criteria.
You can check my sample code here:
package com.github.tddiaz.jsonresponsefiltering;
import com.fasterxml.jackson.annotation.JsonFilter;
import com.fasterxml.jackson.databind.ser.FilterProvider;
import com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter;
import com.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider;
import jdk.nashorn.internal.objects.annotations.Getter;
import lombok.Data;
import lombok.NonNull;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.json.MappingJacksonValue;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
#SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
#RestController
class Controller {
#GetMapping("/filter")
public ResponseEntity filter() {
final Response response = new Response("value1", "value2", "value3");
//ignore field3; will only return values of field1 and field2
final SimpleBeanPropertyFilter beanPropertyFilter = SimpleBeanPropertyFilter.filterOutAllExcept("field1", "field2");
final FilterProvider filterProvider = new SimpleFilterProvider().addFilter("responseFilter", beanPropertyFilter);
final MappingJacksonValue mappingJacksonValue = new MappingJacksonValue(response);
mappingJacksonValue.setFilters(filterProvider);
return ResponseEntity.ok(mappingJacksonValue);
}
}
#JsonFilter("responseFilter")
#Data
class Response {
#NonNull
private String field1, field2, field3;
}
}
Use Projection for expose in diferent manners your json, and ResourceProcessor if you need to add more info to this model through projection, e.g another DB table perhaps.
Based on your use case, just call the controller of your choice from the jsp/js page ...For e.g. Let's say Admin is the user then call AdminController else call User Controller ...this can be done using a simple if/else condition...you can also look into Proxy Design Pattern but that depends on the use case
I recommend you to use JSON.stringify(value[, replacer[, space]]) function on frontend. I have given an example below. You have a write a custom function according to your requirements on the specific view.
Eg. The below example ignores null values. Here I have written editperson function which removes null values.
The function has two input parameters i.e. key and value. Write your logic according to the keys and values which you want to remove or change.
var springperson = { "name":"foo","code":null }
console.log(springperson); // person recieved from spring
function editjson(key, value){
if (value !== null) return value
}
var editperson = JSON.stringify(springperson, editjson); // String representation of person
var personjson=JSON.parse(editperson); // JSON object representation of person
console.log(personjson); // person as required by the view
Comment if you have any issues.
I'm calling an aws lambda with a json body. So the fields of the json are with different name from the ones in the POJO. So what I did is to add #JsonProperty on the fields to tell jackson what are the names in json. But for some reason it seems that it doesn't recognize them and all the fields are null. If I pass a json with the same field names as the POJO it's working. Here's my class:
public class Event implements Identifiable {
#JsonProperty("distinct_id")
private String distinctId;
#JsonProperty("user_id")
private Integer userId;
#JsonDeserialize(using = LocalDateTimeDeserializer.class)
#JsonSerialize(using = LocalDateTimeSerializer.class)
private LocalDateTime eventDateTime;
//Here are the getters and setters
}
If I pass
{"distinct_id":"123", "user_id":123, "dt":"2017-01-04T08:45:04+00:00"}
all the fields are null and with distinctId, userId, eventDateTime it's serializing ok with the exception that it also doesn't recognize my custom serializers/deserializers but this actually is the same problem.
My conclusion is that for some reason the aws jackson is not working with the annotations but it doesn't make sense.
So I found a way to do this. You need to implement RequestStreamHandler which gives you input and output streams which you can work with:
import com.amazonaws.services.lambda.runtime.RequestStreamHandler
public class ChartHandler implements RequestStreamHandler {
private ObjectMapper objectMapper = new ObjectMapper();
#Override
public void handleRequest(InputStream inputStream, OutputStream outputStream, Context context) throws IOException {
DeserializationClass deserializedInput = objectMapper.readValue(inputStream, DeserializationClass.class)
objectMapper.writeValue(outputStream, deserializedInput); //write to the outputStream what you want to return
}
}
Having the input and output streams makes you independent of the format and frameworks you use to parse it.
Take a look at this quote from AWS documentation:
You shouldn't rely on any other features of serialization frameworks such as annotations. If you need to customize the serialization behavior, you can use the raw byte stream to use your own serialization.
From: https://docs.aws.amazon.com/lambda/latest/dg/java-programming-model-req-resp.html
It sounds like you have version mismatch between annotation types, and databind (ObjectMapper): both MUST be the same major version. Specifically, Jackson 1.x annotations work with Jackson 1.x databind; and 2.x with 2.x.
Difference is visible via Java package: Jackson 1.x uses org.codehaus.jackson, whereas Jackson 2.x uses com.fasterxml.jackson. Make sure to import right annotations for ObjectMapper you use.
I had this same issue and needed MyCustomClass to be taken in and out of the Lambda Function correctly so that it can be passed through my State Machine in the Step Function without any hiccups.
Building off what Hristo Angelov posted, I was able to get a solution that worked for me and I'm posting it hoping that it will help others that were stuck like I was:
import java.io.InputStream;
import java.io.OutputStream;
import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.LambdaLogger;
import com.amazonaws.services.lambda.runtime.RequestStreamHandler;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
public class StaticSearchPagingLambdaFunctionHandler implements RequestStreamHandler {
LambdaLogger logger = null;
MyCustomClass myCustomClass = null;
// Register the JavaTimeModule for LocalDate conversion
ObjectMapper objectMapper = new ObjectMapper().registerModule(new JavaTimeModule());
#Override
public void handleRequest(InputStream inputStream, OutputStream outputStream, Context context) {
myCustomClass = objectMapper.readValue(inputStream, MyCustomClass .class);
// ...
// Do stuff with myCustomClass
// ...
objectMapper.writeValue(outputStream, myCustomClass);
}
}
Even though the JSON string will print out differently with the ObjectMapper writing to the OutPutStream, when the next lambda function takes it in while going through the Step Function, it will still get converted to LocalDate correctly.
Make sure that in MyCustomClass your toString() method prints correctly. My toString() method looks like this:
import java.time.LocalDate;
import org.json.JSONObject;
public class SimpleSearch {
private LocalDate startDate;
private LocalDate endDate;
// ...
// Getters and Setters for the LocalDate variables
// ...
#Override
public String toString() {
return new JSONObject(this).toString();
}
public SimpleSearch() {}
}
then your JSON printouts will always look like this when it gets sent to the lambda and not that other crazy Jackson format:
{
"startDate": "2018-11-01",
"endDate": "2018-11-16"
}
Some of the Maven dependencies I used:
<dependency>
<groupId>org.json</groupId>
<artifactId>json</artifactId>
<version>20180813</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<version>2.9.7</version>
</dependency>
Hopefully AWS fixes the Jackson conversions to be reciprocal, to and from JSON, so that we wouldn't have to resort to these custom conversions anymore.
create getter methods for the properties and put #JsonProperty on the getter methods.
I have a class with a 'read-only' function that shouldn't get serialized into a JSON object. Because the function's name starts with "get", Jackson grabs it, and does not seem to respect the attached #JsonIgnore tag:
import org.codehaus.jackson.annotate.JsonIgnore;
public class MyClass {
publicMyClass() {}
#JsonIgnore
public String getSomeValue() {return "";}
}
I also tried using the following syntax:
import org.codehaus.jackson.annotate.JsonIgnoreProperties;
#JsonIgnoreProperties({"someValue"})
public class MyClass {
publicMyClass() {}
public String getSomeValue() {return "";}
}
In either case the resultant JSON output is the same:
{someValue:""}
(We're using Jackson 1.9.13 with Spring MVC.)
Other than renaming the function to start with something other than "get", what can I do to prevent Jackson from serializing this function's output?
Turns out we were using Jackson 2 to for JSON serialization, so I needed to change the annotation package location.
Jackson 1:
import com.codehaus.jackson.annotation.JsonIgnore
Jackson 2:
import com.fasterxml.jackson.annotation.JsonIgnore
With the fasterxml import everything works as expected.
I want to provide one comprehensive REST API with support for both JSON and XML.
The domain model is of complex type and we note that to produce friendly JSON and XML on the same model using MappingJacksonHttpMessageConverter and JaxbMarshaller respectively tends to give either readable XML or readable JSON 1).
What's the best way to proceed?
1) Due to how objects such as maps, root tags and relations are modelled differently in json than in xml, the objects to serialize needs to be designed differently to get both tidy json and tidy xml. Utilities such as jaxb annotations only goes that far.
I can think of a few candidates
1) Create both a json and xml controller/model
public class Controller {
public Foo foo() {
return new Foo();
}
}
public class XmlController extends Controller {
#Override
public XmlFoo foo() {
return new new XmlFoo(super.foo());
}
}
public class JsonController extends Controller {
#Override
public JsonFoo foo() {
return new JsonFoo(super.foo());
}
}
Given a model object Foo create a JsonFoo and XmlFoo
2) Write a custom message converter
I tried this and it turned out to be a bit complicated since the view must know how to resolve e.g., a Foo to a JsonFoo to be able to serialize it into a readable format.
3) Let each model object serialize itself, e.g.,
public class Foo {
public String serialize(Serializer s) {
return s.serialize(this);
}
}
Based on some arbitration parameter let the controller inject the correct serializer
new Foo(new FooJsonSerializer());
new Foo(new FooXmlSerializer());
I'm doing this in a current project without using a ContentNegotiatingViewResolver. For one method in my controller:
#RequestMapping(value = "/test", method = RequestMethod.GET)
#ResponseBody
public HttpEntity<BasicResponse> getBasicResponse() {
return new HttpEntity<BasicResponse>(new BasicResponse());
}
I can receive the following output based on the Accept request header.
Accept: application/xml (requires JAXB2 on the classpath)
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<basicResponse>
<errors>
<message>test1</message>
<message>test2</message>
</errors>
</basicResponse>
Accept: application/json (requires Jackson on the classpath)
{
"errors" : ["test1", "test2"]
}
My response object is simple and uses normal annotations:
package org.mypackage.response;
import java.util.ArrayList;
import java.util.List;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlElementWrapper;
import javax.xml.bind.annotation.XmlRootElement;
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
public class BasicResponse {
#XmlElementWrapper(name = "errors")
#XmlElement(name = "message")
private List<String> errors = new ArrayList<String>();
public BasicResponse() {
this.errors.add("test1");
this.errors.add("test2");
}
public List<String> getErrors() {
return errors;
}
}
The SpringSource spring-mvc-showcase project is also a helpful resource. I think they separate the conversions for different methods, but I am definitely doing this for one method.
I can't quite tell by your question...but if you're looking to serialize the output more than that, #chrylis is correct in that a custom serializer would be your next move. But everything I've ran into (which can get pretty complex, with nested objects in my response) converts perfectly to valid XML or JSON.
You should use the ContentNegotiatingViewResolver.
There is an issue in that a collection of POJOs are not mapped correctly with some XML marshallers. XStream has solutions for this (Moxy too?).
Here's a place to start:
http://blog.springsource.org/2013/06/03/content-negotiation-using-views/
Basically, you use a MappingJacksonView and a similar one for XML, which is a "fake" view that uses Jackson (or an XML marshaller) to marshall your POJO(s) to the correct format.
The server will send back the correct type based on one of:
the HTTP Accept header
a "filetype extension", such as ".json"
a querystring parameter, such as "format=json"
As far as omitting fields, you cans use annotations #JsonIgnore(for Jackson) and/or #XStreamOmitField(for XStream).
Did you try this:
#RequestMapping(value = "/{id}",
method = RequestMethod.GET,
headers ={"Accept=application/json,application/xml"},
produces={"application/json", "application/xml"})