I have s Spring REST API that usually just outputs JSON.
Now I want to also export CSVs for some endpoints.
Jackson has a library for that
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-csv</artifactId>
<version>2.8.5</version>
</dependency>
Which I can utilize using Spring's HttpMessageConverter.
However I need to exclude some fields in the CSV that are present in the JSON thus I cannot use #JsonIgnore annotation. Is there a way to use a different ignore property/annotation for CSV?
As an alternative i considered using a custom interface to extract the values to serialize the values before hand. List<CSVableDTO> -> List<Map<String,?>> -> CSV. But I would like to avoid that because it involves extra instance creation and extra coding vs just using a new annotation.
Java-Class-Example:
public class MyDTO {
#CSVIgnore
public int id;
public String name;
public String property;
#CSVIgnore
public String ref;
}
JSON-Example:
[
{
"id": 42,
"name": "Example",
"property": "FooBar",
"ref": "42:Not:FooBar:1337"
}
]
Expected-CSV-Result:
"name";"property"
"Example";"FooBar"
Please, consider usage of JsonView mechanism. You will have something like that:
public class Foo {
public interface JsonOnly{}
public interface CsvView{}
#JsonView(JsonOnly.class)
private Integer secretNotForCsv;
// ...
}
#RestController
public class FooController {
#RequestMapping(...)
#JsonView(Foo.JsonOnly.class)
public Foo getJson() {
// ...
}
#RequestMapping(...)
#JsonView(Foo.CsvView.class)
public Foo getCsv() {
// ...
}
}
This is only a very rough sketch, but it should give you an idea.
Related
I use interface-based projections using named-native-query. My UserDTO looks like this:
public interface UserDTO {
#Value("#{target.USER_ID}")
String getId();
#Value("#{target.USER_NAME}")
String getName();
#Value("#{target.REGISTRATION_REGION}")
String getRegistrationRegion();
}
After that I marshall the list of DTOs to Json, and field names which I see there are in camel-case:
{"USERS": [
{
"id": "e00000099232200",
"name": 1616674065,
"registrationRegion": 1617344002
}]}
But I need them in DB style - Upper-case and with underscores like:
{"USERS": [
{
"ID": "e00000099232200",
"NAME": 1616674065,
"REGISTRATION_REGION": 1617344002
}]}
The straightforward way is naming my DTOs methods like getNAME or getREGISTRATION_REGION or iterating over Json fields and make them upper-case there. But is there a more elegant way to set the display name? Something like this:
#Display("REGISTRATION_REGION")
String getRegistrationRegion();
If you're using Jackson you can annotate your interface with:
#JsonNaming(PropertyNamingStrategy.UpperCamelCaseStrategy.class)
Or if you want this behaviour globally across all usages of your mapper, configure it as follows:
ObjectMapper mapper = new ObjectMapper();
mapper.setPropertyNamingStrategy(new PropertyNamingStrategy.UpperCamelCaseStrategy());
EDIT:
You can implement your own CaseStrategy, for your case it will be:
class UpperSnakeCase extends PropertyNamingStrategy.SnakeCaseStrategy{
#Override
public String translate(String input) {
String snakeCased = super.translate(input);
if (snakeCased == null) {
return null;
}
return snakeCased.toUpperCase();
}
}
I have the following controller code:
public abstract class BaseController<TClientModel extents BaseClientModel> {
#Operation
#GetMapping
Page<TClientModel> get()
}
#Data
public abstract class BaseClientModel {
int id;
}
#RestController
public class SpecificController extends BaseController<SpecificClientModel> {}
#Data
public class SpecificClientModel extends BaseClientModel {
String name;
}
Problem:
When open-api markup is generated for SpecificController in Swagger, client model in the response is BaseClientModel, not SpecificClientModel and only has id field, and not id+name.
Actual:
{
"id": 0,
}
Expected:
{
"id": 0,
"name": "string",
}
Given I have 40+ specific controllers, is there any way I can make springdoc open-api generate correct markup based on specific generic parameters?
The support is now part of v1.2.33 of springdoc-openapi.
For example, if you are using spring-mvc, you can declare:
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-ui</artifactId>
<version>1.2.33</version>
</dependency>
my backend must offer two different APIs - different access to the same models, respectively, same implementation and same mappings to the database. Models are send as JSONs and they are consumed by the backend in the same way.
But different JSON representations are necessary on each API.
F.e. I'd like to name some fields differently (w/ #JsonProperty f.e.) or want to omit some.
As mentioned, they should be consumed by the controllers in the same way they are produced.
Since only the representation differs: is there a simple and DRY compliant way to accomplish this?
Example to this:
Calling
ProductsController.java
sym/products/1
should return
{
"id": 1,
"title": "stuff",
"label": "junk"
}
and calling
ProductsController.java
frontend/products/1
should return
{
"id": 1,
"label": "junk",
"description": "oxmox",
"even-more": "text"
}
Thanks a lot!
Tim
Separate DTOs may be the best solution.
An alternate (assuming you are using Jackson) is to have one DTO with the all the different fields, and then use MixIns to control how the DTO is serialized.
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.databind.ObjectMapper;
public class Main {
public static void main(String[] args) throws Exception {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.addMixIn(SomeDTOWithLabel.class, IgnoreLabelMixin.class);
SomeDTOWithLabel dto = new SomeDTOWithLabel();
dto.setLabel("Hello World");
dto.setOtherProperty("Other property");
String json = objectMapper.writeValueAsString(dto);
System.out.println("json = " + json);
}
public static class SomeDTOWithLabel {
private String label;
private String otherProperty;
public String getOtherProperty() {
return otherProperty;
}
public void setOtherProperty(String otherProperty) {
this.otherProperty = otherProperty;
}
public String getLabel() {
return label;
}
public void setLabel(String label) {
this.label = label;
}
}
public abstract class IgnoreLabelMixin {
#JsonIgnore
public abstract String getLabel();
}
}
For instance we have DTOs with deprecated properties that old clients may still depend on, but we don't want to send them to newer client, so we use MixIns to supress them.
If this is simply a case of returning a lightweight payload depending on which path you call, you can configure your json serializer (ObjectMapper) to omit empty fields. Then in your service only select and populate the subset of fields you wish to return.
objectMapper.setSerializationInclusion(Include.NON_NULL); // omits null fields
However, if you wish to return differently named fields, use a different API model.
I am new to Jackson and I am having some problems determining the best way to deal with processing JSON files that are dynamic in nature. I know I could solve the issue with the streaming or tree API, but this would involve a lot of code which will not be easily maintained. For example, take the following two json files:
{
something: "somethingValue"
somethingelse: "anotherValue"
url: "http://something.com"
}
and
{
something: "somethingValue"
somethingelse: "anotherValue"
url: {
service1: [
"http://something.com",
"https://something.com" ],
service2: [
"http://something2.com",
"https://something2.com" ],
}
}
the default behaviour of the first json object after being parsed, should add the URL to both service1 and service2 url lists in the subclass "URL". where the second allow specifying very specific urls to each. The data object for url class I was planning on use is as follows:
public class url {
// ideally, I would use the java.net.URL instead of String
public List<String> service1;
public List<String> service2;
// also includes getter/setters using a fluent style
...
}
There would also be some other parent class which would have a parameter for URL and other first level json parameters.
What is the best way to handle this in jackson?
The second one is not valid JSON, this is :
{
"something": "somethingValue",
"somethingelse": "anotherValue",
"url": {
"service1" : [
"http://something.com",
"https://something.com" ],
"service2" : [
"http://something2.com",
"https://something2.com" ]
}
}
You can create it/consume it with class A which looks like following
class A{
String something;
String somethingElse;
B url;
}
class B{
Str service1;
List<String> service2;
}
To achieve anything dynamically no matter what, you have to put it in Lists, therefore instead of solution above, you can do this
class A{
String something;
String somethingElse;
B url;
}
class B{
List<C> services;
}
class C{
List<String> service;
}
I ended up mixing JsonNode to get this working.
public class Foo {
#JsonProperty("something")
private String something;
#JsonProperty("somethingelse")
private String somethingelse;
#JsonProperty("url")
JsonNode url;
// getters setters
public static Foo parse(String jsonString) {
ObjectMapper mapper = new ObjectMapper();
Foo foo = mapper.readValue(jsonString, Foo.class);
return foo;
}
public static boolean validate(Foo foo) {
JsonNode url = foo.path("url");
if (url.isTextual()) {
// this is the first case {"url": "http://something.com"}
System.out.println(url.getTextValue());
} else {
// This is the second case
}
}
}
Answer:
After struggling with Jackson to do what I want in a simple and elegant way, I ended up switching to Gson library for JSON parsing. it allowed me to create a custom deserializer for my class that was extremely easy.
An example of something similar that I did can be found here:
http://www.baeldung.com/gson-deserialization-guide
I appreciate the help and guidance with Jackson, however it just made me realize that Jackson was just not going to meet my needs.
-Stewart
Using Jackson data binding, what's the neatest way to skip a bad chunk of data, without rejecting the whole parse?
Take these classes (I'm using public fields just to keep the code short):
public class ClassWhichCouldFailConstruction {
public ClassWhichCouldFailConstruction(String s) {
if(s.charAt(0) > 'L') {
throw new BadParameterException();
}
// else init code here.
}
}
public class User {
public String name;
public ClassWhichCouldFailConstruction failable;
}
public class AppInfo {
public List<User> users;
}
... and this code to parse it:
AppInfo appinfo = (List<User>) objectMapper.readValues(jsonStream, AppInfo.class);
... and this JSON:
{ "users": [
{ "name": "John", "failable": "Example" },
{ "name": "Jane", "failable": "No good" }
]
}
By default ClassWhichCouldFailConstruction("No good") will throw an exception which will bubble up to the caller of objectMapper.readValues().
How can I make it return a AppInfo object containing a users list that is one item long (the valid item)?
And can I run a routine to deal with (e.g. to log) the skipped entry?
I know I can achieve this with a custom deserializer:
public class User {
public String name;
#JsonDeserialize (using = MyCustomDeserializer.class)
public ClassWhichCouldFailConstruction failable;
}
... in which MyCustomDeserializer consumes the content in incremental mode. I'm looking for an option which takes advantage of data binding. Consider that ClassWhichCouldFailConstruction might be something a whole lot more complicated, so writing a custom parser would be laborious.
Use Bean Validation API instead of throwing exception from constructor -- aspects of JSON parsing and data-binding (that Jackson does) can be separated from validation logic. This is where Bean Validator helps: you can declaratively define rules and constraints.