SerializationFeature.WRAP_ROOT_VALUE as annotation in jackson json - java

Is there a way to have the configuration of SerializationFeature.WRAP_ROOT_VALUE as an annotation on the root element instead using ObjectMapper?
For example I have:
#JsonRootName(value = "user")
public class UserWithRoot {
public int id;
public String name;
}
Using ObjectMapper:
#Test
public void whenSerializingUsingJsonRootName_thenCorrect()
throws JsonProcessingException {
UserWithRoot user = new User(1, "John");
ObjectMapper mapper = new ObjectMapper();
mapper.enable(SerializationFeature.WRAP_ROOT_VALUE);
String result = mapper.writeValueAsString(user);
assertThat(result, containsString("John"));
assertThat(result, containsString("user"));
}
Result:
{
"user":{
"id":1,
"name":"John"
}
}
Is there a way to have this SerializationFeature as an annotation and not as an configuration on the objectMapper?
Using dependency:
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.7.2</version>
</dependency>

import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.JsonTypeName;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
public class Test2 {
public static void main(String[] args) throws JsonProcessingException {
UserWithRoot user = new UserWithRoot(1, "John");
ObjectMapper objectMapper = new ObjectMapper();
String userJson = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(user);
System.out.println(userJson);
}
#JsonTypeName(value = "user")
#JsonTypeInfo(include = JsonTypeInfo.As.WRAPPER_OBJECT, use = JsonTypeInfo.Id.NAME)
private static class UserWithRoot {
public int id;
public String name;
}
}
#JsonTypeName and #JsonTypeInfo together make it possible.
Result:
{
"user" : {
"id" : 1,
"name" : "John"
}
}

I think this has been requested as:
https://github.com/FasterXML/jackson-databind/issues/1022
so if anyone wants a challenge & a chance to make many users happy (it is something that'd be nice to have for sure), it's up for grabs :)
Aside from that one small thing worth noting is that you can use ObjectWriter to enable/disable SerializationFeatures.
String json = objectMapper.writer()
.with(SerializationFeature.WRAP_ROOT_VALUE)
.writeValueAsString(value);
in case you need to sometimes use this, other times not (ObjectMapper settings should not be changed after initial construction and configuration).

You can use the it in the below way, so this property will be applied to throughout the objectMapper usage.
static {
objectMapper.configure(SerializationFeature.WRAP_ROOT_VALUE, true);
}

Related

Flat JSON in Jackson for class/record/value object with one field

I have a Java record with one field only:
public record AggregateId(UUID id) {}
And a class with the AggregateId field (other fields removed for readability)
public class Aggregate {
public final AggregateId aggregateId;
#JsonCreator
public Aggregate(
#JsonProperty("aggregateId") AggregateId aggregateId
) {
this.aggregateId = aggregateId;
}
}
The implementation above serialize and deserialize JSON with given example:
ObjectMapper objectMapper = new ObjectMapper();
String content = """
{
"aggregateId": {
"id": "3f61aede-83dd-4049-a6ff-337887b6b807"
}
}
""";
Aggregate aggregate = objectMapper.readValue(content, Aggregate.class);
System.out.println(objectMapper.writeValueAsString(aggregate));
How could I change Jackson config to replace JSON by that:
{
"aggregateId": "3f61aede-83dd-4049-a6ff-337887b6b807"
}
without giving up a separate class for AggregateId and access through fields, without getters?
I tried #JsonUnwrapper annotation, but this caused throws
Exception in thread "X" com.fasterxml.jackson.databind.exc.InvalidDefinitionException:
Invalid type definition for type `X`:
Cannot define Creator parameter as `#JsonUnwrapped`: combination not yet supported at [Source: (String)"{
"aggregateId": "3f61aede-83dd-4049-a6ff-337887b6b807"
}"
or
Exception in thread "X" com.fasterxml.jackson.databind.exc.InvalidDefinitionException:
Cannot define Creator property "aggregateId" as `#JsonUnwrapped`:
combination not yet supported at [Source: (String)"{
"aggregateId": "3f61aede-83dd-4049-a6ff-337887b6b807"
}"
Jackson version: 2.13.1
dependencies {
compile "com.fasterxml.jackson.core:jackson-annotations:2.13.1"
compile "com.fasterxml.jackson.core:jackson-databind:2.13.1"
}
Of course, it's possible with a custom serializer/deserializer, but I'm looking for an easier solution because I have many different classes with a similar issue.
The combination of #JsonUnwrapped and #JsonCreator is not supported yet, so we can generate a solution like this:
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonUnwrapped;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import java.util.UUID;
public class AggregateTest {
static record AggregateId(#JsonProperty("aggregateId") UUID id) {}
static class Aggregate {
#JsonUnwrapped
#JsonProperty(access = JsonProperty.Access.READ_ONLY)
public final AggregateId _aggregateId;
public final String otherField;
#JsonCreator
public Aggregate(#JsonProperty("aggregateId") UUID aggregateId,
#JsonProperty("otherField") String otherField) {
this._aggregateId = new AggregateId(aggregateId);
this.otherField = otherField;
}
}
public static void main(String[] args) throws JsonProcessingException {
String rawJson =
"{\"aggregateId\": \"1f61aede-83dd-4049-a6ff-337887b6b807\"," +
"\"otherField\": \"İsmail Y.\"}";
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
Aggregate aggregate = objectMapper
.readValue(rawJson, Aggregate.class);
System.out.println(objectMapper
.writeValueAsString(aggregate));
}
}
Here we briefly get rid of the #JsonUnwrapped field.
We get the UUID with the name aggregateId and create an AggregateId record.
Detailed explanations about it:
https://github.com/FasterXML/jackson-databind/issues/1467
https://github.com/FasterXML/jackson-databind/issues/1497

Jackson serialization of immutable value class with optional fields

I am using Immutables library (https://immutables.github.io).
My class looks as follows:
package com.abc.myservice.data.models;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import org.immutables.value.Value;
import java.util.Map;
import java.util.Optional;
#Value.Immutable
#JsonSerialize(as = ImmutableMyEntityModel.class)
#JsonDeserialize(as = ImmutableMyEntityModel.class)
public interface MyEntityModel {
String myEntityId();
String status();
Optional<Integer> count();
Optional<Integer> version();
Optional<Map<String, String>> attributes();
}
I build the immutable class object with:
ImmutableMyEntityModel.builder()
.myEntityId("some-id")
.status("some-status")
.count(Optional.of(10))
.build()
And my output is:
{
"MyEntityId": "some-id",
"status": "some-status",
"count": {
"present": true
},
"version": {
"present": false
},
"attributes": {
"present": false
}
}
Instead what I would like to see is:
{
"MyEntityId": "some-id",
"status": "some-status",
"count": 10
}
How can I make it work like that?
Use the jackson-datatype-jdk8 module so that Jackson properly understands the java.util.Optional type - a pretty good explanation is in this article.
Add jackson-datatype-jdk8 library to your project/classpath, which contains a Jackson module that allows Jackson to properly understand Optionals.
When creating an ObjectMapper, register the Jdk8Module:
ObjectMapper om = new ObjectMapper();
om.registerModule(new Jdk8Module());
Optionally, add #JsonIgnoreProperties(ignoreUnknown = true) to properties or the class itself to avoid serializing Optional.empty() to null values and instead ignore the property completely.
Full example:
public class JacksonOptionalTest
{
public static void main(String... args)
throws Exception
{
ObjectMapper om = new ObjectMapper();
om.registerModule(new Jdk8Module());
Thing thing = new Thing();
thing.name = "John Smith";
thing.count = Optional.of(12);
String s = om.writeValueAsString(thing);
System.out.println(s);
}
#JsonInclude(Include.NON_ABSENT)
#JsonIgnoreProperties(ignoreUnknown = true)
public static class Thing
{
public String name;
public Optional<Integer> count = Optional.empty();
public Optional<Integer> version = Optional.empty();
}
}
The output of this is {"name":"John Smith","count":12}.

JaxbDto Serialization and deserialization

I need to receive some message with SOAP so I've generated a few classes by xsd-scheme and maven-jaxb2-plugin like this:
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "Claim", propOrder = {
"field",
})
public class ClaimType {
#XmlElement(required = true, type = Integer.class, nillable = false)
protected Integer field;
public Integer getField() {
return bpType;
}
public void setField(Integer value) {
this.field= value;
}
}
After receiving message I need to send these to the next one microservice in wrap of HashMap.
I supposed to use ObjectMapper to convert:
//JAXB DTO --> JSON
ObjectMapper objectMapper = new ObjectMapper();
String jsonContent = objectMapper.writeValueAsString(claimType);
map.put("json", jsonContent);
//JSON --> JAXB DTO
ObjectMapper objectMapper = new ObjectMapper();
String json = map.get("json");
ClaimType claimType = objectMapper.readValue(json, ClaimType.class);
But the generated classes are haven't any constructors so I got the exception like "
No creator like default constructor are exists".
What is the best preactice to work with Jaxb Dto? Can I do smth to successful convert these json to object? Thanks in advance!
I've solved my problem by using ObjectMapper MixIn:
import javax.xml.bind.JAXBElement;
import javax.xml.namespace.QName;
#JsonIgnoreProperties(value = {"globalScope", "typeSubstituted", "nil"})
public abstract class JAXBElementMixIn<T> {
#JsonCreator
public JAXBElementMixIn(#JsonProperty("name") QName name,
#JsonProperty("declaredType") Class<T> declaredType,
#JsonProperty("scope") Class scope,
#JsonProperty("value") T value) {
}
}
And the convertation:
import com.fasterxml.jackson.databind.ObjectMapper;
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.addMixIn(JAXBElement.class, JAXBElementMixIn.class);
solution link

Jackson deserialize GeoJson Point in Spring Boot

I have a #Entity model that has a property of type com.vividsolutions.jts.geom.Point. When I try to render this model in a #RestController I get a recursion exception.
(StackOverflowError); nested exception is
com.fasterxml.jackson.databind.JsonMappingException: Infinite
recursion (StackOverflowError) (through reference chain:
com.vividsolutions.jts.geom.Point[\"envelope\"]-
>com.vividsolutions.jts.geom.Point[\"envelope\"]....
The entity looks like this (shortened for brevity):
#Entity
#Data
public class MyEntity{
// ...
#Column(columnDefinition = "geometry")
private Point location;
// ...
}
After some research I found out that this is because Jackson cannot deserialize GeoJson by default. Adding this library should solve the issue: https://github.com/bedatadriven/jackson-datatype-jts.
I am now not sure how to include this module in the object mapper in spring boot. As per documentation in boot, I tried adding it to the #Configuration in the following two ways:
#Bean
public Jackson2ObjectMapperBuilder jacksonBuilder() {
Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
builder.modulesToInstall(new JtsModule());
return builder;
}
and
#Bean
public JtsModule jtsModule(){
return new JtsModule();
}
Both didn't remove the exception. Sry if this is a duplicate, but all I was able to find SO were customising the ObjectMapper which in my understanding of the documentation is no the "spring boot way".
As a workaround I am #JsonIgnoreing the Point and have custom getters and setters for a non existent coordinated object,... but it's not the way I'd like to keep it.
As of 2020 most of the JTS libraries are outdated and no longer work. I found one fork on Maven Central that was updated recently and it worked flawlessly with jackson-core:2.10.0 and jts-core:1.16.1:
implementation 'org.n52.jackson:jackson-datatype-jts:1.2.4'
Sample usage:
#Test
void testJson() throws IOException {
var objectMapper = new ObjectMapper();
objectMapper.registerModule(new JtsModule());
GeometryFactory gf = new GeometryFactory();
Point point = gf.createPoint(new Coordinate(1.2345678, 2.3456789));
String geojson = objectMapper.writeValueAsString(point);
InputStream targetStream = new ByteArrayInputStream(geojson.getBytes());
Point point2 = objectMapper.readValue(targetStream, Point.class);
assertEquals(point, point2);
}
You don't need to use any annotations on class fields or register new Spring Beans, just register the JTS module with Jackson.
Maybe you should tag your geometric attribute with #JsonSerialize and #JsonDeserialize. Like this:
import com.bedatadriven.jackson.datatype.jts.serialization.GeometryDeserializer;
import com.bedatadriven.jackson.datatype.jts.serialization.GeometrySerializer;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.vividsolutions.jts.geom.Geometry;
import fr.info.groloc.entity.json.GreffeDeserializer;
import javax.persistence.Entity;
#Entity
public class Table
{
#JsonSerialize(using = GeometrySerializer.class)
#JsonDeserialize(contentUsing = GeometryDeserializer.class)
private Geometry coord;
// ...
}
If you are using Spring-Boot you only need for:
import com.bedatadriven.jackson.datatype.jts.JtsModule;
// ...
#Bean
public JtsModule jtsModule()
{
return new JtsModule();
}
As Dave said you need to add this dependency to your pom.xml:
<dependency>
<groupId>com.bedatadriven</groupId>
<artifactId>jackson-datatype-jts</artifactId>
<version>2.4</version>
</dependency>
The above workaround using JTSModule results in an internal SpringBoot error for me. I was able to solve this issue by making sure the getter methods of my Entity are returning String types.
#Entity
public class MyClassWithGeom {
#Id
#GeneratedValue
private Long id;
private Point centre;
private Polygon boundary;
private MyClassWithGeom() {}
public MyClassWithGeom(String centreLat, String centreLng, Double[]... boundaryCoords) {
String wkt = "POINT (" + centreLat + " " + centreLng + ")";
StringBuilder builder = new StringBuilder("POLYGON (( ");
for(int i=0;i<boundaryCoords.length;i++) {
Double[] coord = boundaryCoords[i];
if (i < boundaryCoords.length - 1)
builder = builder.append(coord[0]).append(" ").append(coord[1]).append(", ");
else
builder = builder.append(coord[0]).append(" ").append(coord[1]).append(" ))");
}
try {
this.centre = (Point) this.wktToGeometry(wkt);
logger.info(this.centre.toString());
this.boundary = (Polygon) this.wktToGeometry(builder.toString());
logger.info(this.boundary.toString());
}
catch (ParseException pe) {
logger.error(pe.getMessage());
logger.error("Invalid WKT: " + wkt);
}
}
public Geometry wktToGeometry(String wellKnownText) throws ParseException {
return new WKTReader().read(wellKnownText);
}
public String getCentre() { return centre.toString(); }
public String getName() { return name; }
public String getBoundary() { return boundary.toString(); }
}
When I'm dealing with spring boot spatial data types in spring boot, com.vividsolutions.jts.geom.Point raised a lot of issues for me. Currently, I'm using Point of type
org.locationtech.jts.geom.Point
which works like a charm
Please try to make changes as below and try again..
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
</dependency>
Change the model like below.
#Entity
#Data
public class MyEntity{
// ...
#Column(columnDefinition = "geometry")
#JsonDeserialize(as = Point.class)
private Point location;
// ...
}
In Case aboveconfiguration does not work with your JacksonSerializer class, please try below once.
public class JacksonSerializer {
private JacksonSerializer(){
}
private static final ObjectMapper objectMapper = new ObjectMapper();
private static boolean isInit = false;
private static void init() {
if (isInit == false) {
objectMapper.setDefaultPropertyInclusion(Include.NON_EMPTY);
objectMapper.disable(SerializationFeature.WRITE_ENUMS_USING_TO_STRING);
objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
objectMapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
objectMapper.registerModule(new JavaTimeModule());
objectMapper.setDateFormat(new ISO8601DateFormat());
objectMapper.setAnnotationIntrospector(new JsonIgnoreIntrospector());
isInit = true;
}
}

JodaTime LocalTime to JSON - Actual Stack Overflow

I am trying to serialize Java objects to JSON. One of my Java objects has a JodaTime LocalTime object as one of its fields.
A fair number of my Java objects also have various fields that are Collections that could be empty. I want to prevent the serialization of JSON that looks like this:
{id: 2348904, listOfThings: [], listOfStuff: [], nowASet: []}
In this scenario where those three Collections are empty, I would rather see this JSON:
{id: 2348904}
The correct way to do such a thing is to configure the ObjectMapper with the following line of code:
objectMapper.setSerializationInclusion(Include.NON_EMPTY);
This works just fine...until I hit that Java object with the LocalTime inside of it. That's when I get an actual java.lang.StackOverflowError.
It seems to be ping-ponging between JodaDateSerializerBase.isEmpty() and JsonSerializer.isEmpty(). I'm not sure how, though, because they don't call each other.
I managed to make a SSSSSSCCCCEEEE, or whatever the hell the acronym is, as follows:
package whatever.you.like;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.joda.JodaModule;
import org.joda.time.LocalTime;
import org.junit.Test;
public class TestClass {
public class JodaMapper extends ObjectMapper {
private static final long serialVersionUID = 34785437895L;
public JodaMapper() {
registerModule(new JodaModule());
}
public boolean getWriteDatesAsTimestamps() {
return getSerializationConfig().isEnabled(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
}
public void setWriteDatesAsTimestamps(boolean state) {
configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, state);
}
}
private class Thing {
private LocalTime localTime;
public Thing() {}
public void setLocalTime(LocalTime localTime) {
this.localTime = localTime;
}
public LocalTime getLocalTime() {
return localTime;
}
}
#Test
public void extendObjectMapperTest() throws JsonProcessingException {
JodaMapper objectMapper = new JodaMapper();
objectMapper.setWriteDatesAsTimestamps(false);
objectMapper.setSerializationInclusion(Include.NON_EMPTY);
Thing thing = new Thing();
LocalTime localTime = new LocalTime(12389340L);
thing.setLocalTime(localTime);
System.out.println("Never manages to print this out: " + objectMapper.writeValueAsString(thing));
}
#Test
public void configureObjectMapperTest() throws JsonProcessingException {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new JodaModule());
objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
objectMapper.setSerializationInclusion(Include.NON_EMPTY);
Thing thing = new Thing();
LocalTime localTime = new LocalTime(12389340L);
thing.setLocalTime(localTime);
System.out.println("Never manages to print this out: " + objectMapper.writeValueAsString(thing));
}
}
I tried both extending the ObjectMapper and configuring the ObjectMapper, and I get the same error each time.
Dependencies:
JodaTime 2.6
FasterXML's Jackson 2.5.0
FasterXML's Jackson-DataType-Joda 2.5.0
Interestingly, you can find in that GitHub a unit test ("testLocalDateSer()") that claims to succeed using the Include.NON_EMPTY qualifier. I fail to see how it could possibly function.
Upgrade to
FasterXML's Jackson 2.5.3
FasterXML's Jackson-DataType-Joda 2.5.3.
This works.
#Test
public void configureObjectMapperTest() throws JsonProcessingException {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new JodaModule());
// objectMapper.configure(SerializationFeature.WRITE_DURATIONS_AS_TIMESTAMPS, false);
objectMapper.setSerializationInclusion(Include.NON_EMPTY);
Thing thing = new Thing();
LocalTime localTime = new LocalTime(12389340L);
thing.setLocalTime(localTime);
System.out.println("Never manages to print this out: " + objectMapper.writeValueAsString(thing));
}

Categories

Resources