Disable Base64 encoding while Serializing on Jackson ObjectMapper - java

I am using Jackson 2.2.3. When I serialize an object to JSON, all the byte[] properties are encoded to base64 in generated json.
How do I disable that?
Here is my code.
DataHolder.java
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "", propOrder = {
"expectedData"
})
#XmlRootElement(name = "dataHolder")
public class DataHolder {
private final static long serialVersionUID = 1L;
#XmlElement(required = true, type = String.class)
#XmlJavaTypeAdapter(HexBinaryAdapter.class)
#XmlSchemaType(name = "hexBinary")
protected byte[] expectedData;
...
}
JSON output:
{
...
"dataHolder": {
"expectedData": "c29tZXRleHQ=",
}
...
}

Here I am creating custom Serializer :
public class BytesToStringSerializer extends StdSerializer<byte[]> {
public BytesToStringSerializer() {
super(byte[].class);
}
protected BytesToStringSerializer(Class<byte[]> t) {
super(t);
}
#Override
public void serialize(byte[] value, JsonGenerator gen, SerializerProvider provider) throws IOException {
String yourReadableString = new String(value, StandardCharsets.UTF_8);
gen.writeString(yourReadableString);
}
}
Your DataHolder class :
public class DataHolder {
#JsonSerialize(using = BytesToStringSerializer.class)
byte[] expectedData;
public DataHolder(byte[] expectedData) {
this.expectedData = expectedData;
}
}
And Main class for testing :
public class Main {
public static void main(String[] args) throws JsonProcessingException {
byte[] someBytes = "Hello world".getBytes(StandardCharsets.UTF_8);
DataHolder dataHolder = new DataHolder(someBytes);
ObjectMapper objectMapper = new ObjectMapper();
String output = objectMapper.writeValueAsString(dataHolder);
System.out.println(output);
}
}
The output is :
{"expectedData":"Hello world"}
Please keep in mind that you should use encoding thats is suitable for you. If your byte[] array does not represent anything readable you should keep it in base64 format.
EDIT :
To configure ObjectMapper globally register module with Serializer for it :
public class Main {
public static void main(String[] args) throws JsonProcessingException {
byte[] someBytes = "Hello world".getBytes(StandardCharsets.UTF_8);
DataHolder dataHolder = new DataHolder(someBytes);
ObjectMapper objectMapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.addSerializer(byte[].class, new BytesToStringSerializer());
objectMapper.registerModule(module);
String output = objectMapper.writeValueAsString(dataHolder);
System.out.println(output);
}
}
Remember to provide getters and setters for your data. If you dont want getters and setters configure objectMapper and set field visibility :
objectMapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY);

Related

Jackson serializes java.time.Duration as a string in one part of my object graph and as a double in a different part

How can I force Jackson to consitently serialize both durations as a string?
record MyObject(#JsonProperty("duration") Duration duration) {
}
#Test
public void testJackson() throws JsonProcessingException {
var map = Map.of(Duration.ofHours(1), new MyObject(Duration.ofHours(1)));
ObjectMapper om = new ObjectMapper();
om.registerModule(new JavaTimeModule());
var json = om.writerWithDefaultPrettyPrinter().writeValueAsString(map);
System.out.println(json);
}
Results:
{
"PT1H" : {
"duration" : 3600.000000000
}
}
Tried extending DurationSerializer and annotating MyObject with #JsonSerialize(using = MyDurationSerializer.class, but this didn't work
public class MyDurationSerializer extends DurationSerializer {
#Override
protected boolean useTimestamp(SerializerProvider provider) {
return false;
}
}

Ignore properties marked with specific annotation

I have ObjectMapper instance:
ObjectMapper mapper = new ObjectMapper();
In runtime want to serialize instance of class. What is the class the program doesn't known. It's object instance of parameterized type T.
How to ignore all properties (fields and getters) which marked specified annotation (javax.persistence.Id) ?
Example:
public static class PojoTest {
#Id
public String idTest;
public String id;
}
public void serialize(Object object) {
ObjectMapper objectMapper = new ObjectMapper();
// TODO ignore property mark #Id annotation
Map<Object, Object> map = objectMapper.convertValue(object, Map.class);
assertFalse(map.containsKey("idTest"));
}
public void test() {
PojoTest pojoTest = new PojoTest();
pojoTest.id = "foo";
pojoTest.idTest = "bar";
serialize(pojoTest);
}
You can implement a new com.fasterxml.jackson.databind.AnnotationIntrospector class where you can extend hasIgnoreMarker method:
static class IdIgnoreAnnotationIntrospector extends AnnotationIntrospector {
#Override
public Version version() {
return new Version(1,0,0,"Ignore #Id", "group.id", "artifact.id");
}
#Override
public boolean hasIgnoreMarker(AnnotatedMember m) {
return hasIdAnnotation(m);
}
boolean hasIdAnnotation(AnnotatedMember member) {
return member.getAnnotation(Id.class) != null;
}
}
Now you need to register this introspector:
ObjectMapper mapper = new ObjectMapper();
mapper.setAnnotationIntrospector(AnnotationIntrospector.pair(new JacksonAnnotationIntrospector(), new IdIgnoreAnnotationIntrospector()));
Now you can ignore all fields marked with #Id annotation.

Jackson serialize Object to JSON to base64 (without endless loop)

Is there a simple way to serialize an object using Jackson to base64 encoded JSON? (object -> JSON -> base64)
I tried using a custom StdSerializer, but this (of course) results in a endless loop:
class MySerializer extends StdSerializer<Foo> {
public void serialize(Foo value, JsonGenerator gen, SerializerProvider provider) {
StringWriter stringWriter = new StringWriter();
JsonGenerator newGen = gen.getCodec().getFactory().createGenerator(stringWriter);
gen.getCodec().getFactory().getCodec().writeValue(newGen, value);
String json = stringWriter.toString();
String base64 = new String(Base64.getEncoder().encode(json.getBytes()));
gen.writeString(base64);
}
}
A workaround is to copy all fields to another class and use that class for the intermediate representation:
class TmpFoo {
public String field1;
public int field2;
// ...
}
class MySerializer extends StdSerializer<Foo> {
public void serialize(Foo value, JsonGenerator gen, SerializerProvider provider) {
TmpFoo tmp = new TmpFoo();
tmp.field1 = value.field1;
tmp.field2 = value.field2;
// etc.
StringWriter stringWriter = new StringWriter();
JsonGenerator newGen = gen.getCodec().getFactory().createGenerator(stringWriter);
gen.getCodec().getFactory().getCodec().writeValue(newGen, tmp); // here "tmp" instead of "value"
String json = stringWriter.toString();
String base64 = new String(Base64.getEncoder().encode(json.getBytes()));
gen.writeString(base64);
}
}
Creating a new ObjectMapper is not desired, because I need all registered modules and serializers of the default ObjectMapper.
I was hoping for some easier way of achieving this.
EDIT: Example
Step 1: Java Object
class Foo {
String field1 = "foo";
int field2 = 42;
}
Step 2: JSON
{"field1":"foo","field2":42}
Step 3: Base64
eyJmaWVsZDEiOiJmb28iLCJmaWVsZDIiOjQyfQ==
According to this site, there is a workaround to avoid this recursion problem:
When we define a custom serializer, Jackson internally overrides the
original BeanSerializer instance [...] our SerializerProvider finds
the customized serializer every time, instead of the default one, and
this causes an infinite loop.
A possible workaround is using BeanSerializerModifier to store the
default serializer for the type Folder before Jackson internally
overrides it.
If I understood the workaround correctly, your Serializer should look like this:
class FooSerializer extends StdSerializer<Foo> {
private final JsonSerializer<Object> defaultSerializer;
public FooSerializer(JsonSerializer<Object> defaultSerializer) {
super(Foo.class);
this.defaultSerializer = defaultSerializer;
}
#Override
public void serialize(Foo value, JsonGenerator gen, SerializerProvider provider) throws IOException {
StringWriter stringWriter = new StringWriter();
JsonGenerator tempGen = provider.getGenerator().getCodec().getFactory().createGenerator(stringWriter);
defaultSerializer.serialize(value, tempGen, provider);
tempGen.flush();
String json = stringWriter.toString();
String base64 = new String(Base64.getEncoder().encode(json.getBytes()));
gen.writeString(base64);
}
}
In addition to the serializer, a modifier is needed:
public class FooBeanSerializerModifier extends BeanSerializerModifier {
#Override
public JsonSerializer<?> modifySerializer(
SerializationConfig config, BeanDescription beanDesc, JsonSerializer<?> serializer) {
if (beanDesc.getBeanClass().equals(Foo.class)) {
return new FooSerializer((JsonSerializer<Object>) serializer);
}
return serializer;
}
}
Example module:
ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.setSerializerModifier(new FooBeanSerializerModifier());
mapper.registerModule(module);
EDIT:
I've added flush() to flush the JsonGenerator tempGen.
Also, I've created a minimal test enviroment with JUnit, which verifies your Example with Foo: The github repo can be found here.
EDIT: Alternative 2
Another (simple) option is using a wrapper class with generics:
public class Base64Wrapper<T> {
private final T wrapped;
private Base64Wrapper(T wrapped) {
this.wrapped = wrapped;
}
public T getWrapped() {
return this.wrapped;
}
public static <T> Base64Wrapper<T> of(T wrapped) {
return new Base64Wrapper<>(wrapped);
}
}
public class Base64WrapperSerializer extends StdSerializer<Base64Wrapper> {
public Base64WrapperSerializer() {
super(Base64Wrapper.class);
}
#Override
public void serialize(Base64Wrapper value, JsonGenerator gen, SerializerProvider provider) throws IOException {
StringWriter stringWriter = new StringWriter();
JsonGenerator tempGen = provider.getGenerator().getCodec().getFactory().createGenerator(stringWriter);
provider.defaultSerializeValue(value.getWrapped(), tempGen);
tempGen.flush();
String json = stringWriter.toString();
String base64 = new String(Base64.getEncoder().encode(json.getBytes()));
gen.writeString(base64);
}
}
An example usecase would be:
final ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.addSerializer(new Base64WrapperSerializer());
mapper.registerModule(module);
final Foo foo = new Foo();
final Base64Wrapper<Foo> base64Wrapper = Base64Wrapper.of(foo);
final String base64Json = mapper.writeValueAsString(base64Wrapper);
This example can be found in this GitHub (branch: wrapper) repo, verifing you BASE64 String from your foo example with JUnit testing.
Instead of creating new object you may convert existing one into map. Like in the example below
import static java.nio.charset.StandardCharsets.UTF_8;
public class FooSerializer extends StdSerializer<Foo> {
public FooSerializer() {
super(Foo.class);
}
#Override
public void serialize(Foo foo, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) {
try {
ObjectMapper mapper = (ObjectMapper) jsonGenerator.getCodec();
var map = toMap(foo); // if you need class info for deserialization than use toMapWithClassInfo
String json = mapper.writeValueAsString(map);
jsonGenerator.writeString(Base64.getEncoder().encodeToString(json.getBytes(UTF_8)));
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public static Map<String, Object> toMap(Object o) throws Exception {
Map<String, Object> result = new HashMap<>();
Field[] declaredFields = o.getClass().getDeclaredFields();
for (Field field : declaredFields) {
field.setAccessible(true);
result.put(field.getName(), field.get(o));
}
return result;
}
public static Map<String, Object> toMapWithClassInfo(Object obj) throws Exception {
Map<String, Object> result = new HashMap<>();
BeanInfo info = Introspector.getBeanInfo(obj.getClass());
for (PropertyDescriptor pd : info.getPropertyDescriptors()) {
Method reader = pd.getReadMethod();
if (reader != null)
result.put(pd.getName(), reader.invoke(obj));
}
return result;
}
}
I'm providing 2 ways of converting into map: with and without class info. Choose the one, applicable to your problem.
To serialize object jackson search #JsonValue method. You can add encodedJsonString method annotated by #JsonValue in Foo class.
Try with this:
#Getter
#Setter
public class Foo implements Serializable {
private static final long serialVersionUID = 1L;
public String field1;
public int field2;
#JsonValue
public String toEncodedJsonString() {
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
new ObjectOutputStream(baos).writeObject(this);
return org.apache.commons.codec.binary.Base64.encodeBase64String(baos.toByteArray());
}catch (Exception ex){
}
return null;
}
}

Jackson encode all properties to base64

I have more than one class that I want to serialize using Jackson to generate Json, for example
public class A{
int id;
String name;
Object database;
... getter and setter
}
I need to encode all value of the json to base64, so I configure the object mapper like this
public class Base64Serializer<T> extends StdSerializer<T> {
private static final long serialVersionUID = 1L;
protected Base64Serializer(Class<?> t, boolean f) {
super(t, f);
}
#Override
public void serialize(T value, JsonGenerator jsonGenerator, SerializerProvider arg2) throws IOException {
String ecnodedOutput = Base64.getEncoder().encodeToString(((String) value).getBytes());
jsonGenerator.writeString(ecnodedOutput);
}
}
//Using the base64 Serializer to configure Object mapper
SimpleModule module = new SimpleModule();
module.addSerializer(new Base64Serializer(String.class, false));
objectMapper.registerModule(module);
objectMapper.writerWithDefaultPrettyPrinter().writeValue(outputStream,intanceOfClassA);
The problem is it's only encode the String type as the serializer only accept one type, is there any method to encode all field values, (long, int, object, etc..) I mean to encode all value of the json field regarding it type of string or int??
You could use following:
Serializer
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.util.Base64;
public class Base64Serializer<T extends Serializable> extends StdSerializer<T> {
private static final long serialVersionUID = 1L;
protected Base64Serializer(Class<?> t, boolean f) {
super(t, f);
}
#Override
public void serialize(T value, JsonGenerator jsonGenerator, SerializerProvider arg2) throws IOException {
String ecnodedOutput = Base64.getEncoder().encodeToString(serialize(value));
jsonGenerator.writeString(ecnodedOutput);
}
public static byte[] serialize(Object obj) throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
ObjectOutputStream os = new ObjectOutputStream(out);
os.writeObject(obj);
return out.toByteArray();
}
}
Registration and test:
public class SerializerTest {
public static void main(String[] args) throws JsonProcessingException {
ObjectMapper objectMapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
addSerializers(module, Serializable.class, int.class, double.class, float.class, char.class, byte.class, short.class);
objectMapper.registerModule(module);
System.out.println(objectMapper.writeValueAsString(new A(10, "test", Arrays.asList(10000L, "TTTT2"))));
}
private static void addSerializers(SimpleModule module, Class... classes) {
Arrays.stream(classes).forEach(c -> module.addSerializer(new Base64Serializer(c, false)));
}
}
Output:
{
"id": "rO0ABXNyABFqYXZhLmxhbmcuSW50ZWdlchLioKT3gYc4AgABSQAFdmFsdWV4cgAQamF2YS5sYW5nLk51bWJlcoaslR0LlOCLAgAAeHAAAAAK",
"name": "rO0ABXQABHRlc3Q=",
"database": "rO0ABXNyABpqYXZhLnV0aWwuQXJyYXlzJEFycmF5TGlzdNmkPL7NiAbSAgABWwABYXQAE1tMamF2YS9sYW5nL09iamVjdDt4cHVyABdbTGphdmEuaW8uU2VyaWFsaXphYmxlO67QCaxT1+1JAgAAeHAAAAACc3IADmphdmEubGFuZy5Mb25nO4vkkMyPI98CAAFKAAV2YWx1ZXhyABBqYXZhLmxhbmcuTnVtYmVyhqyVHQuU4IsCAAB4cAAAAAAAACcQdAAFVFRUVDI="
}
Just in case if someone is looking for converting any kind of object to Base64 string using something like Jackson or Gson. You could do something like this:
Convert your object to string using Jackson/Gson:
str = objectMapper.writeValueAsString(obj)
OR
str = gson.toJson(obj)
Now you get bytes from your string using
base64str = str.getBytes()
Use java's Base64 class to convert bytes to Base64 string:
Base64.getEncoder().encodeToString(base64str)

ObjectMapper writeValueAsString changes bytebuffer pos

I am trying to serialize a complex object to string that somewhere contains bytebuffer inside by using ObjectMapper for logging the response.
This changes the cursor position inside the bytebuffer and simply corrupts the response.
Code snippet that i am using:
import org.codehaus.jackson.map.ObjectMapper;
private static final ObjectMapper MAPPER = new ObjectMapper();
public static String serializeToString(final Object obj) {
Preconditions.checkArgument(obj != null, "Object to be serialized is null");
try {
final String str = MAPPER.writeValueAsString(obj);
if (Strings.isNullOrEmpty(str)) {
log.warn("Serialized to null/empty string");
}
return str;
} catch (final JsonGenerationException e) {
throw new IllegalArgumentException("Json generation exception occured in de-serializing obj", e);
} catch (final JsonMappingException e) {
throw new IllegalArgumentException("Json mapping exception occured in de-serializing obj", e);
} catch (final IOException e) {
throw new IllegalArgumentException("IO exception occured in de-serializing obj", e);
}
}
I passed above method a complex object having bytebuffer inside.
I printed bytebuffer before and after calling above method.
public static void main(final String[] args) throws SecurityException, NoSuchMethodException {
final String x =
"Random data i am using for this test for byte buffer. Random data i am using for this test for byte buffer";
final byte[] byteArr = x.getBytes();
final ByteBuffer bb = ByteBuffer.wrap(byteArr);
System.out.println("before bytebuffer :" + bb);
String stringData = SerializerUtil.serializeToString(bb); // In real i am passing a complex structure having
// bytebuffer inside
System.out.println(stringData);
System.out.println("after bytebuffer :" + bb);
}
Output:
before bytebuffer :java.nio.HeapByteBuffer[pos=0 lim=106 cap=106]
{"short":21089,"char":"\u6e64","int":1869422692,"long":7022344510808023405,"float":2.0790493E-19,"double":6.687717052371733E223,"direct":false,"readOnly":false}
after bytebuffer :java.nio.HeapByteBuffer[pos=28 lim=106 cap=106]
This change in (pos=0 to pos=28)position simply corrupts the response sent. Do we have any way to convert this complex object to string without affecting the byteBuffer?
Any help much appreciated.
Obviously, you don't want to serialize the ByteBuffer property as another structured class, but just the content, as a string. One way to do that is to use a #JsonProperty annotation on a method to tell the mapper to use that method instead of trying to serialize the field directly. Assuming you have a bean like this:
class Stuff {
private ByteBuffer data;
public Stuff() {
}
public Stuff(ByteBuffer data) {
super();
this.data = data;
}
public ByteBuffer getData() {
return data;
}
public void setData(ByteBuffer data) {
this.data = data;
}
#JsonProperty(value = "data")
public String convertData() {
return new String(data.array());
}
#JsonProperty("data")
public void convertData(String s) {
data = ByteBuffer.wrap(s.getBytes());
}
}
The mapper will now use the convertData methods for serializeing and deserializing the ByteBuffer data property, and you can still use normal java bean property methods.
Update:
Since the serialized class cannot be changed, here is an alternative method using som advanced JACKSON stuff. First, create custom serializer and deserializer:
static class ByteBufferSerializer extends JsonSerializer<ByteBuffer> {
#Override
public void serialize(ByteBuffer value, JsonGenerator jgen,
SerializerProvider provider) throws IOException,
JsonProcessingException {
jgen.writeString(new String(value.array()));
}
}
static class ByteBufferDeserializer extends JsonDeserializer<ByteBuffer> {
#Override
public ByteBuffer deserialize(JsonParser jp,
DeserializationContext context) throws IOException,
JsonProcessingException {
return ByteBuffer.wrap(jp.getText().getBytes());
}
}
Then, create a Mixin interface to provide the annotations for the properties that we cannot provide in the real target class:
static interface Mixin {
#JsonSerialize(using = ByteBufferSerializer.class, contentAs = String.class)
ByteBuffer getData();
#JsonDeserialize(using = ByteBufferDeserializer.class, contentAs = String.class)
void setData(ByteBuffer data);
}
Further, create a Module used to configure the object mapper, and add the mixin interface:
static class MyModule extends SimpleModule {
public MyModule() {
super("ByteBuffer wrangling");
}
#Override
public void setupModule(SetupContext context) {
context.setMixInAnnotations(Stuff.class, Mixin.class);
}
}
And, finally, register the module with the mapper:
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new MyModule());
VoilĂ , piece of cake! :-)

Categories

Resources