I use Swagger to set up an REST Api Service. For this I used following versions:
swagger (YAML)-file version 2.0
swagger codegen cli version v2.3.0
springfox version 2.5.0
The task of the API Service is to respond with data that we store in our database, as well as its relations.
However, we have circular relations in our data. This means that each Object a can have a relation to Object b, which can have a backward relation to Object a.
Now using Swagger, this will generate an endless long JSON, resulting in an error.
The code looks like this:
public class Service extends DataObject {
#SerializedName("provides")
private List<Utility> provides;
/**
* Get provides
*
* #return provides
**/
#ApiModelProperty(required = true, value = "")
public List<Utility> getProvides() {
return provides;
}
}
public class Utility extends DataObject {
#SerializedName("providedBy")
private List<Service> providedBy;
/**
* Get providedBy
*
* #return providedBy
**/
#ApiModelProperty(required = true, value = "")
public List<Service> getProvidedBy() {
return providedBy;
}
}
Then in the response:
#Override
public ResponseEntity<List<Service>> servicesGet(
#Min(0) #ApiParam(value = "The number of layers of relations the object is enriched by. Lower numbers typically increase performance.", defaultValue = "0") #Valid #RequestParam(value = "layersOfRelations", required = false, defaultValue = "0") final Integer layersOfRelations) {
String accept = this.request.getHeader("Accept");
List<Service> services = Util.getServices();
return new ResponseEntity<List<Service>>(services, HttpStatus.OK);
}
My Question is, where and how can I change the output that will be automatically generated for the return in the method servicesGet()?
I would like to not transform the whole object b into JSON, but rather only its title, so that there will be no endless recursion.
So after some research I found out, that swagger is using Jackson to convert objects into JSON (even though the #SerializedName parameter contained in the generated code was from the gson library, at least in my case).
This means that in this case it I can simply use customization techniques that Jackson provides, and it worked like a charm.
I only had to write a custom Jackson adapter annotation:
#SerializedName("provides")
#JsonSerialize(using = DataObjectAdapter.class)
private List<Utility> provides;
and the adapter looks like this:
public static class DataObjectAdapter extends JsonSerializer<List<Utility>> {
#Override
public void serialize(final List<Utility> arg0, final JsonGenerator arg1, final SerializerProvider arg2)
throws IOException {
arg1.writeStartArray();
for (Utility object : arg0) {
arg1.writeStartObject();
arg1.writeStringField("id", object.getId());
arg1.writeStringField("title", object.getTitle());
arg1.writeStringField("description", object.getDescription());
arg1.writeEndObject();
}
arg1.writeEndArray();
}
}
So now instead of writing the whole object into JSON (and thus recursivly adding all child objects into it, he will only write the information I described in the adapter into JSON.
Similarly a deserializer, where I read out the ID in the JSON and map it to my data, would look like this
public static class ServiceDeserializer extends StdDeserializer<Service> {
public ServiceDeserializer() {
super(Service.class);
}
#Override
public Service deserialize(final JsonParser jp, final DeserializationContext ctxt)
throws IOException, JsonProcessingException {
List<Utility> objects = Util.getUtilities();
JsonNode node = jp.getCodec().readTree(jp);
String id = node.get("id").asText();
String title = node.get("title").asText();
String description = node.get("description").asText();
Service service = new Service(id, title, description);
for (JsonNode utility : node.get("provides")) {
String checkId = utility.get("id").asText();
for (DataObject object : objects) {
if (object.getId().equals(checkId)) {
service.addUtility(object);
break;
}
}
}
return service;
}
}
Related
I have an API whose response is as follows:
{
ruleId:”123”,
ruleName:”Rule1”
}
Now I am introducing a new Api which exactly has these fields but the response should not have name as ruleId ,ruleName but as id,name:
{
id:”123”,
name:”Rule1”
}
I should change in such a way so that the previous Api response should not be impacted.
Thought to use JsonProperty /JsonGetter but it will change the previous Api response as well.
Is there any way that I can have 2 getters for the same field and then use one getter for previous Apis and other one for my purpose? (My concern is only when converting Pojo to JSON)
Can anyone help?
Since you want serialize the object differently in different cases, using jackson mix-in is preferred.
Here is example how to do that.
If your pojo looks something like this:
public class CustomPojo {
private String ruleId;
private String ruleName;
public String getRuleId() {
return ruleId;
}
public void setRuleId(String ruleId) {
this.ruleId = ruleId;
}
public String getRuleName() {
return ruleName;
}
public void setRuleName(String ruleName) {
this.ruleName = ruleName;
}
}
First, you need to create one interface (or class) like this:
import com.fasterxml.jackson.annotation.JsonProperty;
public interface CostomPojoMixin {
#JsonProperty("Id")
String getRuleId();
#JsonProperty("name")
String getRuleName();
}
This interface will be used to rename fields ruleId and ruleName during serilization.
Then when you have all this setup you can write controller method and customize ObjectMapper:
#GetMapping(value = "/test/mixin")
public String testMixin() throwsJsonProcessingException {
CostomPojo cp = new CostomPojo();
cp.setRuleId("rule");
cp.setRuleName("name");
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.addMixIn(CustomPojo.class, CostomPojoMixin.class);
String json = objectMapper.writeValueAsString(cp);
return json;
}
This endpoint should return response like this:
{"Id":"rule","name":"name"}
I am struggling with indexing jsonB column into Elasicsearch backend, using Hibernate Search 6.0.2
This is my entity:
#Data
#NoArgsConstructor
#Entity
#Table(name = "examples")
public class Example {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private UUID id;
#NotNull
#Column(name = "fields")
#Type(type = "jsonb")
private Map<String, Object> fields;
}
and this is my programmatic mapping of elasticsearch backend for Hibernate Search:
#Configuration
#RequiredArgsConstructor
public class ElasticsearchMappingConfig implements HibernateOrmSearchMappingConfigurer {
private final JsonPropertyBinder jsonPropertyBinder;
#Override
public void configure(HibernateOrmMappingConfigurationContext context) {
var mapping = context.programmaticMapping();
var exampleMapping = mapping.type(Example.class);
exampleMapping.indexed();
exampleMapping.property("fields").binder(jsonPropertyBinder);
}
}
I based my custom property binder implementation on Hibernate Search 6.0.2 documentation.
#Component
public class JsonPropertyBinder implements PropertyBinder {
#Override
public void bind(PropertyBindingContext context) {
context.dependencies().useRootOnly();
var schemaElement = context.indexSchemaElement();
var userMetadataField = schemaElement.objectField("metadata");
context.bridge(Map.class, new Bridge(userMetadataField.toReference()));
}
#RequiredArgsConstructor
private static class Bridge implements PropertyBridge<Map> {
private final IndexObjectFieldReference fieldReference;
#Override
public void write(DocumentElement target, Map bridgedElement, PropertyBridgeWriteContext context) {
var map = target.addObject(fieldReference);
((Map<String, Object>) bridgedElement).forEach(map::addValue);
}
}
}
I am aware that documentation defines multiple templates for what an Object in Map can be (like in MultiTypeUserMetadataBinder example), but I really do not know what can be inside. All I know, it is a valid json and my goal is to put it into Elasticsearch as valid json structure under "fields": {...}
In my case jsonB column may contain something like this:
{
"testString": "298",
"testNumber": 123,
"testBoolean": true,
"testNull": null,
"testArray": [
5,
4,
3
],
"testObject": {
"testString": "298",
"testNumber": 123,
"testBoolean": true,
"testNull": null,
"testArray": [
5,
4,
3
]
}
but it throws an exception:
org.hibernate.search.util.common.SearchException: HSEARCH400609: Unknown field 'metadata.testNumber'.
I have also set dynamic_mapping to true in my spring application:
...
spring.jpa.properties.hibernate.search.backend.hosts=127.0.0.3:9200
spring.jpa.properties.hibernate.search.backend.dynamic_mapping=true
...
Any other ideas how can I approach this problem? Or maybe I made an error somewhere?
I am aware that documentation defines multiple templates for what an Object in Map can be (like in MultiTypeUserMetadataBinder example), but I really do not know what can be inside. All I know, it is a valid json and my goal is to put it into Elasticsearch as valid json structure under "fields": {...}
If you don't know what the type of each field is, Hibernate Search won't be able to help much. If you really want to stuff that into your index, I'd suggest declaring a native field and pushing the JSON as-is. But then you won't be able to apply predicates to the metadata fields easily, except using native JSON.
Something like this:
#Component
public class JsonPropertyBinder implements PropertyBinder {
#Override
public void bind(PropertyBindingContext context) {
context.dependencies().useRootOnly();
var schemaElement = context.indexSchemaElement();
// CHANGE THIS
IndexFieldReference<JsonElement> userMetadataField = schemaElement.field(
"metadata",
f -> f.extension(ElasticsearchExtension.get())
.asNative().mapping("{\"type\": \"object\", \"dynamic\":true}");
)
.toReference();
context.bridge(Map.class, new Bridge(userMetadataField));
}
#RequiredArgsConstructor
private static class Bridge implements PropertyBridge<Map> {
private static final Gson GSON = new Gson();
private final IndexFieldReference<JsonElement> fieldReference;
#Override
public void write(DocumentElement target, Map bridgedElement, PropertyBridgeWriteContext context) {
// CHANGE THIS
target.addValue(fieldReference, GSON.toJsonTree(bridgedElement));
}
}
}
Alternatively, you can just declare all fields as strings. Then all features provided by Hibernate Search on string types will be available. But of course things like range predicates or sorts will lead to strange results on numeric values (2 is before 10, but "2" is after "10").
Something like this:
#Component
public class JsonPropertyBinder implements PropertyBinder {
#Override
public void bind(PropertyBindingContext context) {
context.dependencies().useRootOnly();
var schemaElement = context.indexSchemaElement();
var userMetadataField = schemaElement.objectField("metadata");
// ADD THIS
userMetadataField.fieldTemplate(
"userMetadataValueTemplate_default",
f -> f.asString().analyzer( "english" )
);
context.bridge(Map.class, new Bridge(userMetadataField.toReference()));
}
#RequiredArgsConstructor
private static class Bridge implements PropertyBridge<Map> {
private final IndexObjectFieldReference fieldReference;
#Override
public void write(DocumentElement target, Map bridgedElement, PropertyBridgeWriteContext context) {
var map = target.addObject(fieldReference);
// CHANGE THIS
((Map<String, Object>) bridgedElement).forEach(entry -> map.addValue( entry.getKey(), String.valueOf(entry.getValue())));
}
}
}
I'm looking to have multiple jackson deserializers for the same object(s) all based on a custom annotation.
Ideally I'd have a single POJO like:
public class UserInfo {
#Redacted
String ssn;
String name;
}
Under "normal" conditions I want this object to be serialized the default way:
{"ssn":"123-45-6789", "name":"Bob Smith"}
but for logging purposes (for example) I want to redact the SSN so it doesn't get saved in our logs:
{"ssn":"xxx-xx-xxxx", "name":"Bob Smith"}
I've also looked into using #JsonSerialize and come up with:
public class UserInfo {
#JsonSerialize(using = RedactedSerializer.class, as=String.class)
String firstName;
String lastName;
}
The problem with this is that it ALWAYS uses this rule. Can multiple #JsonSerializers be added and only the specified one be used within the runtime code?
I've also seen "views" but ideally I'd like to atleast show that the field was present on the request - even if I dont know the value.
The 100% safe way would be to use different DTO in different requests. But yeah, if you cant do that, use #JsonView and custom serializer, something like:
class Views {
public static class ShowSSN {}
}
private static class MyBean{
#JsonSerialize(using = MyBeanSerializer.class)
#JsonView(Views.ShowSSN.class)
String ssn;
//getter setter constructor
}
private class MyBeanSerializer extends JsonSerializer<String> {
#Override
public void serialize(String value, JsonGenerator gen,
SerializerProvider serializers) throws IOException {
Class<?> jsonView = serializers.getActiveView();
if (jsonView == Views.ShowSSN.class)
gen.writeString(value); // your custom serialization code here
else
gen.writeString("xxx-xx-xxxx");
}
}
And use it like:
public static void main(String[] args) throws JsonProcessingException {
ObjectMapper mapper = new ObjectMapper();
MyBean bean = new MyBean("123-45-6789");
System.out.println(mapper.writerWithView(Views.ShowSSN.class)
.writeValueAsString(bean));
// results in {"ssn":"123-45-6789"}
System.out.println(mapper.writeValueAsString(bean));
// results in {"ssn":"xxx-xx-xxxx"}
}
Also for example in spring it would be really easy to use
#Controller
public class MyController {
#GetMapping("/withView") // results in {"ssn":"123-45-6789"}
#JsonView(Views.ShowSSN.class)
public #ResponseBody MyBean withJsonView() {
return new MyBean("123-45-6789");
}
#GetMapping("/withoutView") // results in {"ssn":"xxx-xx-xxxx"}
public #ResponseBody MyBean withoutJsonView() {
return new MyBean("123-45-6789");
}
}
I think you could achieve that dynamically by coding not annotations,
inside your methods, you can set the proper Serializer and switch between them
(The code depends on your Jackson version)
ObjectMapper mapper = new ObjectMapper();
SimpleModule testModule = new SimpleModule("MyModule", new Version(1, 0, 0, null));
testModule.addSerializer(new RedactedSerializer()); // assuming serializer declares correct class to bind to
mapper.registerModule(testModule);
https://github.com/FasterXML/jackson-docs/wiki/JacksonHowToCustomSerializers
I have a REST API specification that talks with back-end microservices, which return the following values:
On "collections" responses (e.g. GET /users) :
{
users: [
{
... // single user object data
}
],
links: [
{
... // single HATEOAS link object
}
]
}
On "single object" responses (e.g. GET /users/{userUuid}) :
{
user: {
... // {userUuid} user object}
}
}
This approach was chosen so that single responses would be extensible (for example, maybe if GET /users/{userUuid} gets an additional query parameter down the line such at ?detailedView=true we would have additional request information).
Fundamentally, I think it is an OK approach for minimizing breaking changes between API updates. However, translating this model to code is proving very arduous.
Let's say that for single responses, I have the following API model object for a single user:
public class SingleUserResource {
private MicroserviceUserModel user;
public SingleUserResource(MicroserviceUserModel user) {
this.user = user;
}
public String getName() {
return user.getName();
}
// other getters for fields we wish to expose
}
The advantage of this method is that we can expose only the fields from the internally used models for which we have public getters, but not others. Then, for collections responses I would have the following wrapper class:
public class UsersResource extends ResourceSupport {
#JsonProperty("users")
public final List<SingleUserResource> users;
public UsersResource(List<MicroserviceUserModel> users) {
// add each user as a SingleUserResource
}
}
For single object responses, we would have the following:
public class UserResource {
#JsonProperty("user")
public final SingleUserResource user;
public UserResource(SingleUserResource user) {
this.user = user;
}
}
This yields JSON responses which are formatted as per the API specification at the top of this post. The upside of this approach is that we only expose those fields that we want to expose. The heavy downside is that I have a ton of wrapper classes flying around that perform no discernible logical task aside from being read by Jackson to yield a correctly formatted response.
My questions are the following:
How can I possibly generalize this approach? Ideally, I would like to have a single BaseSingularResponse class (and maybe a BaseCollectionsResponse extends ResourceSupport class) that all my models can extend, but seeing how Jackson seems to derive the JSON keys from the object definitions, I would have to user something like Javaassist to add fields to the base response classes at Runtime - a dirty hack that I would like to stay as far away from as humanly possible.
Is there an easier way to accomplish this? Unfortunately, I may have a variable number of top-level JSON objects in the response a year from now, so I cannot use something like Jackson's SerializationConfig.Feature.WRAP_ROOT_VALUE because that wraps everything into a single root-level object (as far as I am aware).
Is there perhaps something like #JsonProperty for class-level (as opposed to just method and field level)?
There are several possibilities.
You can use a java.util.Map:
List<UserResource> userResources = new ArrayList<>();
userResources.add(new UserResource("John"));
userResources.add(new UserResource("Jane"));
userResources.add(new UserResource("Martin"));
Map<String, List<UserResource>> usersMap = new HashMap<String, List<UserResource>>();
usersMap.put("users", userResources);
ObjectMapper mapper = new ObjectMapper();
System.out.println(mapper.writeValueAsString(usersMap));
You can use ObjectWriter to wrap the response that you can use like below:
ObjectMapper mapper = new ObjectMapper();
ObjectWriter writer = mapper.writer().withRootName(root);
result = writer.writeValueAsString(object);
Here is a proposition for generalizing this serialization.
A class to handle simple object:
public abstract class BaseSingularResponse {
private String root;
protected BaseSingularResponse(String rootName) {
this.root = rootName;
}
public String serialize() {
ObjectMapper mapper = new ObjectMapper();
ObjectWriter writer = mapper.writer().withRootName(root);
String result = null;
try {
result = writer.writeValueAsString(this);
} catch (JsonProcessingException e) {
result = e.getMessage();
}
return result;
}
}
A class to handle collection:
public abstract class BaseCollectionsResponse<T extends Collection<?>> {
private String root;
private T collection;
protected BaseCollectionsResponse(String rootName, T aCollection) {
this.root = rootName;
this.collection = aCollection;
}
public T getCollection() {
return collection;
}
public String serialize() {
ObjectMapper mapper = new ObjectMapper();
ObjectWriter writer = mapper.writer().withRootName(root);
String result = null;
try {
result = writer.writeValueAsString(collection);
} catch (JsonProcessingException e) {
result = e.getMessage();
}
return result;
}
}
And a sample application:
public class Main {
private static class UsersResource extends BaseCollectionsResponse<ArrayList<UserResource>> {
public UsersResource() {
super("users", new ArrayList<UserResource>());
}
}
private static class UserResource extends BaseSingularResponse {
private String name;
private String id = UUID.randomUUID().toString();
public UserResource(String userName) {
super("user");
this.name = userName;
}
public String getUserName() {
return this.name;
}
public String getUserId() {
return this.id;
}
}
public static void main(String[] args) throws JsonProcessingException {
UsersResource userCollection = new UsersResource();
UserResource user1 = new UserResource("John");
UserResource user2 = new UserResource("Jane");
UserResource user3 = new UserResource("Martin");
System.out.println(user1.serialize());
userCollection.getCollection().add(user1);
userCollection.getCollection().add(user2);
userCollection.getCollection().add(user3);
System.out.println(userCollection.serialize());
}
}
You can also use the Jackson annotation #JsonTypeInfo in a class level
#JsonTypeInfo(include=As.WRAPPER_OBJECT, use=JsonTypeInfo.Id.NAME)
Personally I don't mind the additional Dto classes, you only need to create them once, and there is little to no maintenance cost. And If you need to do MockMVC tests, you will most likely need the classes to deserialize your JSON responses to verify the results.
As you probably know the Spring framework handles the serialization/deserialization of objects in the HttpMessageConverter Layer, so that is the correct place to change how objects are serialized.
If you don't need to deserialize the responses, it is possible to create a generic wrapper, and a custom HttpMessageConverter (and place it before MappingJackson2HttpMessageConverter in the message converter list). Like this:
public class JSONWrapper {
public final String name;
public final Object object;
public JSONWrapper(String name, Object object) {
this.name = name;
this.object = object;
}
}
public class JSONWrapperHttpMessageConverter extends MappingJackson2HttpMessageConverter {
#Override
protected void writeInternal(Object object, Type type, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
// cast is safe because this is only called when supports return true.
JSONWrapper wrapper = (JSONWrapper) object;
Map<String, Object> map = new HashMap<>();
map.put(wrapper.name, wrapper.object);
super.writeInternal(map, type, outputMessage);
}
#Override
protected boolean supports(Class<?> clazz) {
return clazz.equals(JSONWrapper.class);
}
}
You then need to register the custom HttpMessageConverter in the spring configuration which extends WebMvcConfigurerAdapter by overriding configureMessageConverters(). Be aware that doing this disables the default auto detection of converters, so you will probably have to add the default yourself (check the Spring source code for WebMvcConfigurationSupport#addDefaultHttpMessageConverters() to see defaults. if you extend WebMvcConfigurationSupport instead WebMvcConfigurerAdapter you can call addDefaultHttpMessageConverters directly (Personally I prefere using WebMvcConfigurationSupport over WebMvcConfigurerAdapter if I need to customize anything, but there are some minor implications to doing this, which you can probably read about in other articles.
Jackson doesn't have a lot of support for dynamic/variable JSON structures, so any solution that accomplishes something like this is going to be pretty hacky as you mentioned. As far as I know and from what I've seen, the standard and most common method is using wrapper classes like you are currently. The wrapper classes do add up, but if you get creative with your inheretence you may be able to find some commonalities between classes and thus reduce the amount of wrapper classes. Otherwise you might be looking at writing a custom framework.
I guess you are looking for Custom Jackson Serializer. With simple code implementation same object can be serialized in different structures
some example:
https://stackoverflow.com/a/10835504/814304
http://www.davismol.net/2015/05/18/jackson-create-and-register-a-custom-json-serializer-with-stdserializer-and-simplemodule-classes/
I have a simple data service :
#GET
public Data getData(#QueryParam("id") Long id) {
Data data = dataService.getData(id);
return data;
}
And a matching DataSerializer that implements JsonSerializer<Data> :
The DataSerializer is registered to Jackson via :
simpleModule.addSerializer(Data.class , dataSerializer);
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(simpleModule);
It works well.
But today , I want to add another Locale parameter , and hope the DataSerializer to output correspondent content :
#GET
public Data getData(#QueryParam("id") Long id , #QueryParam("locale") Locale locale)
The 'Data' itself contains various locale variations , and I hope to get the assigned locale output.
But when I get the locale from the parameter , I don't know how to pass the locale value to the DataSerializer …
Is there anyway to achieve this ?
Except this solution :
Data data = dataService.getData(id.get() , locale);
which is not what I want.
It seems ThreadLocal is the only way to achieve this , but I feel that is ugly. Any other feasible solutions ?
Thanks.
Environments : dropwizard-0.7.0-rc2 , jackson-core:jar:2.3.1
===================== updated ==========
reply to #andrei-i :
Because my data itself already contains various locale versions.
for example :
Data helloData = dataService.get("hello");
helloData.getName(Locale.English) == "Hello";
helloData.getName(Locale.France) == "Bonjour";
helloData.getName(Locale.Germany) == "Hallo";
I want to directly pass the locale from URL to JsonSerializer , to get one version of the data presentation.
And there 'may' be other version (not just locale) , so , inheriting Data mixing Locale is not considered.
I know that this is not a new question but here is what I came up with facing the similar problem:
created custom annotation:
#Target({ ElementType.FIELD, ElementType.TYPE, ElementType.METHOD })
#Retention(RetentionPolicy.RUNTIME)
public #interface JsonLocalizable {
public String localizationKey();
}
Jackson serializer:
public class LocalizingSerializer extends StdSerializer<String> implements ContextualSerializer {
private String localizationKey;
public LocalizingSerializer() {
super(String.class);
}
public LocalizingSerializer(String key) {
super(String.class);
this.localizationKey = key;
}
#Override
public void serialize(String value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonGenerationException {
String localizedValue = //.... get the value using localizationKey
jgen.writeString(localizedValue);
}
#Override
public JsonSerializer<?> createContextual(SerializerProvider prov, BeanProperty property) throws JsonMappingException {
String key = null;
JsonLocalizable ann = null;
if (property != null) {
ann = property.getAnnotation(JsonLocalizable.class);
}
if (ann != null) {
key = ann.localizationKey();
}
//if key== null??
return new LocalizingSerializer(key);
}
}
Annotate the field you want to localize:
public class TestClass {
#JsonSerialize(using = LocalizingSerializer.class)
#JsonLocalizable(localizationKey = "my.key")
private String field;
public String getField() {
return this.field;
}
public void setField(String field) {
this.field = field;
}
}
Solution 1. In your JAX-RS implementation register your own implementation of MessageBodyWriter for JSON requests. Probably your implementation will extend Jackson. Also it might be possible that you will have to unregister Jackson. In a MessageBodyWriter you can inject a UriInfo instance using the #Context annotation, and with it you can get any request parameter.
Solution 2. Change the architecture of your Data, so that it is locale-aware. For example, create a setter setLocale() which will change the returned data, if the locale was set.