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.
Related
I have:
{
"id": "2021-04-03T15-SV_Waldhof_Mannheim--Zwickau",
"something": {
"id": "12",
"value": 1.5
}
}
I want get value: 1.15, and store it in my variable.
How can i do it with #JsonPropety?
#JsonProperty("something[value}") //how to do it correctly?
private float value;
How i parse JSON:
restTemplate.exchange(MY_GET_REQUEST, HttpMethod.GET, entity, new ParameterizedTypeReference<List<MyEntity>>(){})
I will be grateful for any help, if you know identical topics - just send link
UPDATED
something.value does not work
The same problem with unpacking, such as:
#JsonProperty("something")
public void setLng(Map<String, Float> coordinates) {
this.value= (Float.parseFloat(coordinates.get("value")));
}
Also does not work
You have 2 options:
Use custom deserializer for your response. In this case you able to populate any target DTO in any way. Here you could find example of custom deserializer
Use the same structure for your DTO as in response (with sub object) and add additional method in root DTO to access this value. But in this case it could produce side effects on serialization (for example, additional field in root DTO)
UPDATE
Such configuration is working for me
public static class Obj {
#JsonProperty("id")
String id;
Float value;
#JsonProperty("something")
public void value(Map<String, Object> obj) {
this.value = Float.parseFloat(obj.get("value").toString());
}
}
I am creating a client for the following format of JSON -
{
"results": [
{
"Product": "K265113",
"Language": "EN",
"LongText": "FIXTURE,INTERIOR,WALL"
}
]
}
The JSON always contains "results" field which is an array of a single element (it will always be a single element in this array). I just need LongText field from the JSON and nothing else. I am using Spring RESTTemplate.
I know that it works if I create two DTOs like -
public class ParentDTO
{
private List<ChildDTO> results;
public List<ChildDTO> getResults()
{
return results;
}
public void setResults(List<ChildDTO> results)
{
this.results = results;
}
}
public class ChildDTO
{
private String longText;
public String getLongText()
{
return longText;
}
#JsonProperty("LongText")
public void setLongText(String longText)
{
this.longText = longText;
}
}
But is there any way to read longText by creating a single DTO as the parent DTO is not having any useful field as I know there will always but just one element in the results array.
The reason you need only single DTO could be that you want only single class to perform this task. You can achieve that using ChildDTO as inner class which will make it more readable and maintainable.
The other way is to not parse the spring template response into DTOs instead use JSONNode class of Jackson databind API.
JsonNode root = objectMapper.readTree(response.getBody());
You can find more information at
https://fasterxml.github.io/jackson-databind/javadoc/2.8/com/fasterxml/jackson/databind/JsonNode.html
You can traverse down the tree and could retrieve the value of the attribute directly without any DTOs.
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.
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.
Here's where I'm at. I've an MVC controller method that accepts JSON content. Because I need to validate it using JSON Schema, my controller maps the request body as a Jackson JsonNode.
Upon successful validation, I need to persist the data in Spring Couchbase repository. Consider the following snippet:
public class Foo
{
#Id
private String _id;
#Version
private Long _rev;
#Field
private JsonNode nodeData;
// .. Other data and members.
}
//
// Repository
//
#Repository
public interface FooRepository extends CrudRepository<Foo, String> {
}
When I store these elements into the Couch repository, what I'd like to see is something like this:
{
"_class": "Foo",
"field1": "field 1 data",
"nodeData" : {
"Some" : "additional data",
"from" : "JsonNode"
}
}
instead, what I see in the repository is something like this:
{
"_class": "Foo",
"field1": "field 1 data",
"nodeData" : {
"_children": {
"Some": {
"_value": "additional data",
"_class": "com.fasterxml.jackson.databind.node.TextNode"
},
"From": {
"_value": "jsonNode",
"_class": "com.fasterxml.jackson.databind.node.TextNode"
},
"_nodeFactory": {
"_cfgBigDecimalExact": false
}
}
}
Each stored property of the JsonNode is decorated with class information, and other meta-data, which is not desirable.
My question - is there a preferred way to get the CrudRepository to behave in the manner that I wish?
It doesn't work that way because serialization and de-serialization conventions are already established. You can override these conventions with custom serialization & de-serialization in Jackson-- but that might go beyond the "crude" approach you are looking for.
I see you want a one shoe fits all approach to data modeling.
Might i recommend storing a Map
#Field
private Map<String, String> data;
This map is private so its perfect.
You can then have two methods
one method puts to the map like so
ObjectMapper mapper = new ObjectMapper()
public void setFeild(String name, Object value) {
ObjectNode node new ObjectNode(JsonNodeFactory.instance);
node.put("clazz", value.getClass().getName());
if (value instance of String) {
node.put("value", value)
} else {
node.put("value", mapper.writeValueAsString(data));
}
data.put(name, node.toString());
}
the other gets from the map like so
public Object getField(String name) {
if (data.contains(name)) {
JsonNode node = mapper.readValue(data.get(name), JsonNode.class);
Class clazz = Class.forName(node.get("class").textValue());
if (clazz.equals(String.class) {
return node.get("value").textValue();
} else {
return (Object) mapper.readValue(node.get("value"), clazz);
}
}
}
You should update this implementation to handle Date, Integer, Boolean, Double ... etc the same way i am handling String-- POJOs are what you serialize/de-serialize to/from json.
I hope this makes sense.