How to get Jackson to use its JsonSerializable Interface? - java

Jackson docs say that a class that implements their JsonSerializable interface will be serialized by calling the Interface's serialize() method.
I tried this in a project that uses Jackson 2.8.4 under Jersey 2.25.
It continues to use Jackson's BeanSerializer to do default serialization based on public getters, instead of using the SerializableSerializer and the serialize() method.
Code looks like this fairly minimal example...
public class Minimal extends JsonSerializable.Base {
private String title;
private ZonedDateTime theTime;
public String getTitle() { return title; }
void setTitle(String title) { this.title = title; }
public ZonedDateTime getTheTime() { return theTime; }
void setTheTime(ZonedDateTime theTime) { this.theTime = theTime; }
#Override
public void serialize(JsonGenerator gen, SerializerProvider serializers) throws IOException {
gen.writeStartObject();
gen.writeFieldName("title");
gen.writeString(this.title);
// Other serialization...
gen.writeEndObject();
}
#Override
public void serializeWithType(JsonGenerator gen, SerializerProvider serializers, TypeSerializer typeSer) throws IOException {
throw new UnsupportedOperationException("Not supported.");
}
}
I also tried other ideas to get it using the right serializer...maybe foolish ideas like...
public class Minimal implements JsonSerializable {
and
#JsonSerialize(using = SerializableSerializer.class)
public class Minimal extends JsonSerializable.Base {
and
#JsonSerialize(as = JsonSerializable.Base.class)
public class Minimal extends JsonSerializable.Base {
What am I missing to get this Jackson feature working?
I verified in Jackson's latest source code that the feature still exists. I suppose I can trace it through the 2.8.4 source in the debugger, but maybe someone knows the answer.

Apparently the answer to
"What am I missing?"
is Nothing.
After writing the question, I rebuilt everything again, restarted Tomcat, and redeployed and tested again and got my expected output.
So I will chalk this up to bad build, bad deploy, confused Classloader, something like that. I am leaving the question, since it provides an example that might help someone.

Related

Is there a way to serialize this class?

#JsonSerialize(using = TestDefSerializer.class)
public class TestDef{
private List<TestStep> steps = new LinkedList<>();
private String name;
} //Getter and Setters are defined
I can't seem to figure out a way after this
public class TestDefSerializer extends StdSerializer<TestDef> {
public TestDefSerializer(Class<TestDef> t) {
super(t);
}
public TestDefSerializer(){
this(TestDef.class);
}
#Override
public void serialize(TestDef testDefinition, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
jsonGenerator.writeStartObject();
// What should go here in order to serialize List<TestStep> ???
}
}
TestStep has a couple of fields namely responseDef (which has path , method) , requestDef (mathcing , status) etc. I wish to skip a couple of fields in responseDef and requestDef
There are chances you don't even need to implement a
TestDefSerializer at all , because Jackson is probably
smart enough to pick up enough information from the getters
of your TestDef class.
Just omit the line
#JsonSerialize(using = TestDefSerializer.class)
on your TestDef class and check if this will already
produce the JSON output you want.
But anyway, here is how to proceed if you want to
implement your own TestDefSerializer.
Look up the the API docu of JsonGenerator.
It describes all the write... methods available
for writing the JSON pieces.
For example, there is method writeStartArray() for writing a [,
and writeEndArray() for writing a ].
So in your TestDefSerializer class you may end up
with a serialize method looking like this:
#Override
public void serialize(TestDef testDefinition, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
jsonGenerator.writeStartObject(); // write {
jsonGenerator.writeFieldName("name"); // write "name":
jsonGenerator.writeString(testDefinition.getName());
jsonGenerator.writeFieldName("steps"); // write "steps":
jsonGenerator.writeStartArray(); // write [
for (TestStep testStep : testDefinition.getSteps()) {
jsonGenerator.writeObject(testStep); // this will invoke the serializer for TestStep
}
jsonGenerator.writeEndArray(); // write ]
jsonGenerator.writeEndObject(); // write }
}

How to generate swagger 2 models for Stripe Java SDK?

I'm using Spring cloud + Swagger to build a SaaS system, which needs Stripe SDK to take payment.
The problem is that Stripe's SDK uses Gson annotations to shape the JSON serialized models. For example:
package com.stripe.model;
public class Dispute extends ApiResource implements MetadataStore<Dispute>, BalanceTransactionSource {
#SerializedName("balance_transactions")
List<BalanceTransaction> balanceTransactions;
}
I declare a REST api to return a Dispute:
class StripeController {
#GetMapping("/disputes")
Dispute getDispute(#RequestParam String id) {
Dispute dispute = stripeService.getDispute(id);
}
}
I hope this REST api prints {"balance_transactions":"..."} rather than {"balanceTransactions":"..."}.
At first, I write a StripeObjectSerializer to let Jackson serialize it as expected in response:
public class StripeObjectSerializer extends JsonSerializer<StripeObject> {
#Override
public void serialize(StripeObject stripeObject, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException, JsonProcessingException {
jsonGenerator.writeRawValue(stripeObject.toJson());
}
}
It basically works!
However, Swagger cannot recognize StripeObjectSerializer, it creates models like {"balanceTransactions":"..."} with so many extra useless properties, such as lastResponse.
What should I do?

Serialization [Workaround] for ResourceBundle

So my problem is the following:
For an application that I need to write I have to implement the ability to store some DTOs to disk to be reused later on (in JSON format). Just to give you a broad frame of reference: The DTOs contain process/data models and also their graphical representation.
To obtain the desired JSON files I currently use Jackson. This works out fine for the largest part, however, in one object that needs to be saved I use a ResourceBundle (to localize the program for different languages). And this is exactly where the problem comes in, as Jackson seems to be unable to serialize ResourceBundle objects (know that both from trying it, but also the research I have done so far basically told me the same).
So I would like to ask you whether you might have an idea how to make it work, or whether you might have found some fancy workaround.
For further illustration I will append some sample code which is not from the project in question, since I do this for someone else and I am not sure whether he would appreciate the release of his code.
public class SomeClass {
private String name;
private ResourceBundle bundle;
public SomeClass(String name, ResourceBundle bundle) {
this.name = name;
this.bundle = bundle;
}
public String getName() {
return this.name;
}
public ResourceBundle getBundle() {
return this.bundle;
}
public void setName(String name) {
this.name = name;
}
public void setBundle(ResourceBundle bundle) {
this.bundle = bundle;
}
/*
Here one could imagine some additional functionality making use of the
given ResourceBundle (something that has to be printed depending on the
used language etc.).
*/
}
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.File;
import java.io.IOException;
import java.util.ResourceBundle;
public class Saver {
public static void main(String[] args) {
Saver saver = new Saver();
saver.run();
}
public void run() {
ObjectMapper om = new ObjectMapper();
ResourceBundle rb = ResourceBundle.getBundle("test");
SomeClass sc = new SomeClass("SomeClass", rb);
try {
om.writeValue(new File("test.json"), sc);
} catch (IOException e) {
e.printStackTrace();
}
}
}
The resulting Stack Trace looks as follows:
com.fasterxml.jackson.databind.JsonMappingException: No serializer found for class sun.util.ResourceBundleEnumeration and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS) ) (through reference chain: SomeClass["bundle"]->java.util.PropertyResourceBundle["keys"])
at com.fasterxml.jackson.databind.JsonMappingException.from(JsonMappingException.java:230)
at com.fasterxml.jackson.databind.ser.impl.UnknownSerializer.failForEmpty(UnknownSerializer.java:68)
at com.fasterxml.jackson.databind.ser.impl.UnknownSerializer.serialize(UnknownSerializer.java:32)
at com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:672)
at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:678)
at com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:157)
at com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:672)
at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:678)
at com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:157)
at com.fasterxml.jackson.databind.ser.DefaultSerializerProvider.serializeValue(DefaultSerializerProvider.java:130)
at com.fasterxml.jackson.databind.ObjectMapper._configAndWriteValue(ObjectMapper.java:3613)
at com.fasterxml.jackson.databind.ObjectMapper.writeValue(ObjectMapper.java:2929)
at Saver.run(Saver.java:22)
at Saver.main(Saver.java:14)
You should check your JSON Java class, if it contains a recursive cycle that may blow up your application. If this is the case add #JsonIgnore to the relevant attribute to break the cycle.

How come JsonGeneratorDelegate in Jackson doesn't implement setPrettyPrinter()?

this is really confusing! if you use a JsonGeneratorDelegate as-is it doesn't transmit calls to setPrettyPrinter() to the delegate
Probably just an oversight -- feel free to file an issue to get this corrected for future versions. Delegate is supposed to delegate all calls by default.
So what is your real question? You can always define your own enhanced JsonGeneratorDelegate, like this:
public class PrettyPrintJsonGeneratorDelegate extends JsonGeneratorDelegate {
public PrettyPrintJsonGeneratorDelegate (final JsonGenerator delegate) {
super (delegate);
}
#Override
public JsonGenerator setPrettyPrinter(final PrettyPrinter pp) {
delegate.setPrettyPrinter (pp);
return this;
}
}

Serialization third-party classes with Simple XML (org.simpleframework.xml)

I have decided to use Simple XML serialization and was stucked with basic problem. I am trying to serialize java.util.UUID class instance as final field in this small class:
#Root
public class Identity {
#Attribute
private final UUID id;
public Identity(#Attribute UUID id) {
this.id = id;
}
}
Tutorial shows how to serialize third-party objects by registering converters like this:
Registry registry = new Registry();
registry.bind(UUID.class, UUIDConverter.class);
Strategy strategy = new RegistryStrategy(registry);
Serializer serializer = new Persister(strategy);
serializer.write( object, stream );
appropriate converter for UUID is pretty simple:
public class UUIDConverter implements Converter<UUID> {
#Override
public UUID read(InputNode node) throws Exception {
return new UUID.fromString(node.getValue());
}
#Override
public void write(OutputNode node, UUID value) throws Exception {
node.setValue(value.toString());
}
}
But this simple code just didn't work for me, during serialization objects with UUID fields was thrown exception Transform of class java.util.UUID not supported.
I have tried something something similar with custom Matcher (which was not in tutorial) that works for me:
Serializer serializer = new Persister(new MyMatcher());
serializer.write( object, stream );
and Matcher class looks like this:
public static class MyMatcher implements Matcher {
#Override
#SuppressWarnings("unchecked")
public Transform match(Class type) throws Exception {
if (type.equals(UUID.class))
return new UUIDTransform();
return null;
}
}
public class UUIDTransform implements Transform<UUID> {
#Override
public UUID read(String value) throws Exception {
return UUID.fromString(value);
}
#Override
public String write(UUID value) throws Exception {
return value.toString();
}
}
Questions:
Is custom Matcher always recommended practice for streaming third-party classes?
In which case I can use Converter?
Are there any better tutorials/examples for Simple XML out there?
Thank you.
I have to answer by myself again :-)
Advice from Niall Gallagher, project leader of Simple XML, from support-list:
"You could use either a Converter or a Transform. I would say
for a UUID a Transform with a Matcher would be the easiest option."
So, I use Transform<T>/Matcher and satisfied with it. This does not alter the fact that the Converter<T> does not work for me :-)
I think i have the answer to this.
Strategy strategy = new AnnotationStrategy();
Serializer serializer = new Persister(strategy);
should register the converter and solve the problem.
I know this is a bit aold but my chance i came to the same exception.
The actual issue is the use of the #Attribute annotation. If instead of #Attribute
you put #Element the exception does not appear and the converter is used for the serialization.
I guess it will then depend on which annotation you used that you should create a Converter or use the Marker-Transform solution. Although i do not know if this is the intendent behaviour.

Categories