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());
}
}
Related
Here is an example DTO
#Getter
#Setter
public class TestDto {
private Long id;
private String name;
private String sex;
}
Say I have this object stored on the server:
{"id":1, "name": "alex", "sex": "M"}
How can I send a request that only updates the "name" portion of the object?
Perhaps I could send this:
{"id":1, "name":"adam"}
Such that the object will change to this:
{"id":1, "name": "adam", "sex": "M"}
I also need the ability to set a field to null
(i.e. clear the contents of a field).
In this case I would like to send
{"id":1, "name":"adam", "sex":null}
To have the stored DTO change to
{"id":1, "name": "adam", "sex":null}
How can I do this using java, spring boot, etc.?
I know the way to use:
#PutMapping
public TestDto update(Map<String, Object>map){ ... }
but I also need to some validation such that if I pass
{"id":"1AA" ... } I get a serialization exception.
Ps.Find first step of this magic ->
1.Before path TestDto throu Rest - need to clear Type like this
Object body = testDto;
if will help you to get an Object with field what you want on server and then you'll be able to detect list of fieds to update
Instead of attempting to detect absent vs null value,
consider defining an update object that includes a list of fields to be updated.
Such an object might look like this:
#Getter
#Setter
public class UpdateObject
{
private long id; // ID of the object to be updated.
private TestDto updates; // an object that contains the new values for the fields.
private List<String> updateFields; // a list of fields to be updated.
}
Here is some Json
{
"id": 1,
"updates":
{
"name": "blem",
"sex": null
},
"updateFields": ["name", "sex"]
}
if i understood right you just send request to the server with different fields. With #ModelAttribute annotation you can send your body in json format.
if you send only one/two field or how you want {"id":1, "name":"adam"}, due to spring data jpa you can update your model in db. (in this case your field sex will be null and you need to create some manipulation for checking it kind of Mapstruct - convert your dto in other model plus checking null/not null fields).
Better create default value for sex field if you want to saving not M and not FM values. null bad practice, in the future it will be bad joke for you.
#Getter
public Enum Sex {
private MALE,
private FEMALE,
private DEFAULT
}
Ok guys finally fount how to do this
1.Client side - > path your testDto as Object, not as TestDto.class
Object payLoad = testDto;
template.postForObject("url", payload);
2.Server side - >
#RestController
#RequestMapping("/test")
public class TestController {
private final Map<Long, TestDto> cash = new HashMap<>();
private final ObjectMapper mapper = new ObjectMapper();
#PostMapping
public TestDto create(#RequestBody TestDto dto) {
return cash.computeIfAbsent(dto.getId(), v -> dto);
}
#PutMapping("/{id}")
public TestDto update(#PathVariable Long id, #RequestBody String json) throws JsonProcessingException {
val oldValue = cash.get(id);
mapper.readerForUpdating(oldValue).readValue(json);
return cash.put(oldValue.getId(), oldValue);
}
}
this hint let you update only field that client really changed
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.
Consider json input:
{
companies: [
{
"id": 1,
"name": "name1"
},
{
"id": 1,
"name": "name1"
}
],
nextPage: 2
}
How deserialize this into class:
public class MyClass {
List<String> companies;
Integer nextPage;
}
Where List<String> companies; consists of strings:
{"id": 1,"name": "name1"}
{"id": 1,"name": "name1"}
#JsonRawValue doesn't work for List<String> companies;
Is there a way to configure Jackson serialization to keep companies array with raw json string with annotations only? (E.g. without writing custom deserializator)
There is no annotation-only solution for your problem. Somehow you have to convert JSON Object to java.lang.String and you need to specify that conversion.
You can:
Write custom deserializer which is probably most obvious solution but forbidden in question.
Register custom com.fasterxml.jackson.databind.deser.DeserializationProblemHandler and handle com.fasterxml.jackson.databind.exc.MismatchedInputException situation in more sophisticated way.
Implement com.fasterxml.jackson.databind.util.Converter interface and convert JsonNode to String. It is semi-annotational way to solve a problem but we do not implement the worst part - deserialisation.
Let's go to point 2. right away.
2. DeserializationProblemHandler
Solution is pretty simple:
ObjectMapper mapper = new ObjectMapper();
mapper.addHandler(new DeserializationProblemHandler() {
#Override
public Object handleUnexpectedToken(DeserializationContext ctxt, JavaType targetType, JsonToken t, JsonParser p, String failureMsg) throws IOException {
if (targetType.getRawClass() == String.class) {
// read as tree and convert to String
return p.readValueAsTree().toString();
}
return super.handleUnexpectedToken(ctxt, targetType, t, p, failureMsg);
}
});
Read a whole piece of JSON as TreeNode and convert it to String using toString method. Helpfully, toString generates valid JSON. Downside, this solution has a global scope for given ObjectMapper instance.
3. Custom Converter
This solution requires to implement com.fasterxml.jackson.databind.util.Converter interface which converts com.fasterxml.jackson.databind.JsonNode to String:
class JsonNode2StringConverter implements Converter<JsonNode, String> {
#Override
public String convert(JsonNode value) {
return value.toString();
}
#Override
public JavaType getInputType(TypeFactory typeFactory) {
return typeFactory.constructType(new TypeReference<JsonNode>() {
});
}
#Override
public JavaType getOutputType(TypeFactory typeFactory) {
return typeFactory.constructType(new TypeReference<String>() {
});
}
}
and now, you can use annotation like below:
#JsonDeserialize(contentConverter = JsonNode2StringConverter.class)
private List<String> companies;
Solutions 2. and 3. solve this problem almost in the same way - read node and convert it back to JSON, but uses different approaches.
If, you want to avoid deserialising and serialising process you can take a look on solution provided in this article: Deserializing JSON property as String with Jackson and take a look at:
How to serialize JSON with array field to object with String field?
How to get a part of JSON as a plain text using Jackson
How to extract part of the original text from JSON with Jackson?
I am trying to de-serialize this JSON object using Jackson 2.8 as part of Retrofit response. Here is the JSON response I get from the server.
{
"id":"8938209912"
"version":"1.1"
"cars":{
"mercedes":[
{
"property":"color"
},
{
"property":"price"
},
{
"property":"location"
}
],
"tesla":[
{
"property":"environment"
}
]
}
}
Based on the query, the cars above may have one or more models returned. I cannot create a class each for each model as these get created/removed arbitrarily. For each model of the car (say tesla), there may be one or more property key-value pairs.
I am new to Jackson. I have been looking at several examples and looks like a custom #JsonDeserialize is the best way to go. So, I created Root class and Cars class like this:
// In file Root.java
public class Root {
#JsonProperty("id")
private String id = null;
#JsonProperty("version")
private String version = null;
#JsonProperty("cars")
private Cars cars = null;
}
// In file Cars.java
public class Cars {
public Cars(){}
#JsonDeserialize(using = CarDeserializer.class)
private Map<String, List<Property>> properties;
public Map<String, List<Property>> getProperties() {
return properties;
}
public void setProperties(Map<String, List<Property>> properties) {
this.properties = properties;
}
}
// Property.java
public class Property {
#JsonProperty("property")
private String property;
}
My de-serializer is below. However, even though the empty constructor gets called, the parse method itself is not called at all!
// CarDeserializer.class
public class RelationshipDeserializer extends StdDeserializer<Map<String, List<Action>>>{
protected RelationshipDeserializer(){
super(Class.class);
}
#Override
public Map<String, List<Action>> deserialize(JsonParser parser, DeserializationContext ctx)
throws IOException, JsonProcessingException
{
// This method never gets invoked.
}
}
My questions:
Is this the right approach in the first place?
Why do you think the execution never gets to the deserialize()? (I checked, the cars object is present in JSON.
Are there better approaches to parse this JSON using Jackson?
The "properties" deserializer is never called because that does not match anything in that JSON. The field name in the JSON is "property" and it does not match Map<String, List<Property>>. It looks like it would be closer to List<Property>
Do you control the in coming JSON? It would be better for the car name/type to be in its own field rather than the name of the object. Then you can use a generic object. What you have now is going to break. Any time they add a new name/type and you do not have a matching object for it.
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.