JsonGenerator generator =
new JsonFactory().createJsonGenerator(new JSONWriter(response));
generator.configure(JsonGenerator.Feature.WRITE_NUMBERS_AS_STRINGS, true);
I used JsonGenerator.Feature.WRITE_NUMBERS_AS_STRINGS to write numbers as string in json. But, I couldn't find similar feature to write boolean value as string.
I couldn't find similar feature for boolean, also. So, I propose to write new serializer and deserializer for boolean fields.
See my example:
public class JacksonProgram {
public static void main(String[] args) throws IOException {
Foo foo = new Foo();
foo.setB(true);
foo.setS("Test");
foo.setI(39);
ObjectMapper objectMapper = new ObjectMapper();
JsonFactory jsonFactory = new JsonFactory();
StringWriter stringWriter = new StringWriter();
JsonGenerator jsonGenerator = jsonFactory.createGenerator(stringWriter);
jsonGenerator.enable(JsonGenerator.Feature.WRITE_NUMBERS_AS_STRINGS);
objectMapper.writeValue(jsonGenerator, foo);
System.out.println(stringWriter);
JsonParser jsonParser = jsonFactory.createJsonParser(stringWriter.toString());
Foo value = objectMapper.readValue(jsonParser, Foo.class);
System.out.println(value);
}
}
class BooleanSerializer extends JsonSerializer<Boolean> {
#Override
public void serialize(Boolean value, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException, JsonProcessingException {
jsonGenerator.writeString(value.toString());
}
}
class BooleanDeserializer extends JsonDeserializer<Boolean> {
public Boolean deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException {
return Boolean.valueOf(jsonParser.getValueAsString());
}
}
class Foo {
#JsonSerialize(using = BooleanSerializer.class)
#JsonDeserialize(using = BooleanDeserializer.class)
private boolean b;
private String s;
private int i;
public boolean isB() {
return b;
}
public void setB(boolean b) {
this.b = b;
}
public String getS() {
return s;
}
public void setS(String s) {
this.s = s;
}
public int getI() {
return i;
}
public void setI(int i) {
this.i = i;
}
#Override
public String toString() {
return "Foo [b=" + b + ", s=" + s + ", i=" + i + "]";
}
}
Output:
{"b":"true","s":"Test","i":"39"}
Foo [b=true, s=Test, i=39]
EDIT
I think, you should add SimpleModule configuration to ObjectMapper:
SimpleModule simpleModule = new SimpleModule("BooleanModule");
simpleModule.addSerializer(Boolean.class, new BooleanSerializer());
simpleModule.addDeserializer(Boolean.class, new BooleanDeserializer());
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(simpleModule);
Now, you should be able to serialize boolean/Object List-s and Map-s.
All I know of a working json string looks something like this:
string json = "
{
"somestring": "some string value"
"someboolean": true
}
";
Use the multiline syntax specific to the language you use.
Related
I'm looking for a way to (de-)serialize a List of items without using Annotations in Jackson. Is this possible? What I'm doing up to now is trying to replace the <item>-tag with a tag telling about the item's class, but no avail. And even if this worked, I'm not sure whether Jackson would offer a way to process this tag information.
To give a better of what I'm aiming at, here's a sample:
public class JacksonTest {
private static class ListElement {
private boolean value;
// getters, setters, constructors omitted
}
#Test
public void testDeSerialization() throws Exception {
final List<ListElement> existing = Arrays.asList(new ListElement(true));
final ObjectMapper mapper = new XmlMapper();
final JavaType listJavaType = mapper.getTypeFactory().constructCollectionType(List.class, ListElement.class);
final String listString = mapper.writerFor(listJavaType).writeValueAsString(existing);
System.out.println(listString);
// "<List><item><value>true</value></item></List>"
}
}
So, the result is <List><item><value>true</value></item></List>, while I want the <item>-tag to be replaced with the (qualified) class name or offering a type-attribute.
Of course, even this would not help if there's no way in Jackson to process this class name.
Do I have reached a dead end here or is there a way to go?
You can define your own JsonSerializer (also used for XML) and add it to a JacksonXmlModule.
ToXmlGenerator has a setNextName function that allows you to override the default item name
private class MyListSerializer extends JsonSerializer<List> {
#Override
public void serialize(List list, JsonGenerator jsonGenerator, SerializerProvider serializerProvider)
throws IOException {
for (Object obj : list) {
if (jsonGenerator instanceof ToXmlGenerator) {
ToXmlGenerator xmlGenerator = (ToXmlGenerator) jsonGenerator;
String className = obj.getClass().getSimpleName();
xmlGenerator.setNextName(new QName(className));
}
jsonGenerator.writeObject(obj);
// this is overridden at the next iteration
// and ignored at the last
jsonGenerator.writeFieldName("dummy");
}
}
#Override
public Class<List> handledType() {
return List.class;
}
}
#Test
public void testDeSerialization() throws Exception {
final List<ListElement> existing = Arrays.asList(new ListElement(true));
JacksonXmlModule module = new JacksonXmlModule();
module.addSerializer(new MyListSerializer());
final ObjectMapper mapper = new XmlMapper(module);
final JavaType listJavaType = mapper.getTypeFactory().constructCollectionType(List.class, ListElement.class);
final ObjectWriter writer = mapper.writerFor(listJavaType);
final String listString = writer.writeValueAsString(existing);
System.out.println(listString);
// "<List><ListElement><value>true</value></ListElement></List>"
}
Okay, after some tinkering and debugging with Evertude's proposal I've figured out a solution. I'm not really happy with the serialization part and honestly I don't know why I was supposed to do it this way. When debugging I've noticed that XmlGenerator::setNextName is required to be called once but does not have any effect on the next call, so I had to implement a switch there and set the field name for the next item in the loop directly.
I'ld be glad if somebody has an idea what I'm doing wrong, but at least my attempt is working for now:
#Test
public void testDeSerialization() throws Exception {
final List<ListElement> existing = Arrays.asList(new ListElement(true), new ListElement(false));
JacksonXmlModule module = new JacksonXmlModule();
module.addSerializer(new MyListSerializer());
final ObjectMapper mapper = new XmlMapper(module);
final JavaType listJavaType = mapper.getTypeFactory().constructCollectionType(List.class, ListElement.class);
final ObjectWriter writer = mapper.writerFor(listJavaType);
final String listString = writer.writeValueAsString(existing);
module.addDeserializer(List.class, new MyListDeserializer());
List<ListElement> deserialized = mapper.readValue(listString, List.class);
assertEquals(existing, deserialized); // provided there're proper hash() and equals() methods
}
private class MyListSerializer extends JsonSerializer<List> {
#Override
public void serialize(List list, JsonGenerator jsonGenerator, SerializerProvider serializerProvider)
throws IOException {
boolean done = false;
for (Object obj : list) {
if (jsonGenerator instanceof ToXmlGenerator) {
ToXmlGenerator xmlGenerator = (ToXmlGenerator) jsonGenerator;
String className = obj.getClass().getSimpleName();
// weird switch
if (!done) xmlGenerator.setNextName(new QName(className));
else jsonGenerator.writeFieldName(className);
done = true;
}
jsonGenerator.writeObject(obj);
}
}
#Override
public Class<List> handledType() {
return List.class;
}
}
private class MyListDeserializer extends JsonDeserializer<List> {
#Override
public List deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
List<Object> items = new ArrayList<>();
JsonToken nextToken;
while ((nextToken = p.nextToken()) != JsonToken.END_OBJECT) {
String currentName = p.currentName();
try {
String className = "my.test.project.JacksonCustomSerializer$" + currentName;
Class<?> loadClass = getClass().getClassLoader().loadClass(className);
p.nextToken();
items.add(p.readValueAs(loadClass));
} catch (ClassNotFoundException e) {
// some handling
}
}
return items;
}
#Override
public Class<List> handledType() {
return List.class;
}
}
I'm deserializing a large json value. Deeply nested within that value is a json object like the following:
{
"fieldOne": "valueOne",
"fieldTwo": {
"innerField": "innerValue"
}
}
I'm using the Jackson ObjectMapper to deserialize the large json value into a 3rd party class. Deeply nested within that 3rd party class is another 3rd party class:
public class DeepThirdPartyClass {
public String fieldOne;
}
which unfortunately is missing the fieldTwo property. I can create my own class which adds the missing field:
public class MyClass extends DeepThirdPartyClass {
public MySubObject fieldTwo;
}
How do I configure jackson so that whenever it attempts to deserialize a value to DeepThirdPartyClass, it deserializes to MyClass instead?
I had similar requirement when I have to filter any not allowed characters in all String values.
To create Object Mapper:
public class CustomObjectMapper extends ObjectMapper {
public CustomObjectMapper() {
super();
SimpleModule module = new SimpleModule("HTML XSS Serializer", new Version(1, 0, 0, "FINAL", "com.crowdoptic", "web"));
module.addSerializer(String.class, new JsonHtmlXssSerializer());
module.addDeserializer(String.class, new JsonHtmlXssDeserializer());
this.registerModule(module);
}
}
public class JsonHtmlXssDeserializer extends StdScalarDeserializer<String> {
private static final Logger LOG = LogManager.getLogger(JsonHtmlXssDeserializer.class);
public JsonHtmlXssDeserializer() { super(String.class); }
#Override
public String deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
String value = StringDeserializer.instance.deserialize(p, ctxt);
LOG.trace("in deserialize for value: " + value);
String encodedValue = StringEscapeUtils.escapeHtml4(value);
return encodedValue;
}
#Override
public String deserializeWithType(JsonParser jp, DeserializationContext ctxt, TypeDeserializer typeDeserializer) throws IOException {
return StringDeserializer.instance.deserializeWithType(jp, ctxt, typeDeserializer);
}
#Override
public boolean isCachable() { return StringDeserializer.instance.isCachable(); }
}
In your case you can register your class deserializer, call super method of the object deserializer. Then instead of returning DeepThirdPartyClass, create object of MyClass, set field one from DeepThirdPartyClass and add second field. See StringDeserializer and others for implementation details and available properties.
Let me know if that helps.
I reworked #olga-khylkouskaya's solution to fit my problem:
#Test
public void newDeserializer() throws Exception {
ObjectMapper objectMapper = new ObjectMapper();
SimpleModule module = new SimpleModule("DeepThirdPartyClass subclass override", new Version(1, 0, 0, "FINAL", "com.example", "deep-third-party-class-override"));
module.addDeserializer(DeepThirdPartyClass.class, new JsonDeserializer<DeepThirdPartyClass>() {
#Override
public DeepThirdPartyClass deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
return p.readValueAs(MyClass.class);
}
});
objectMapper.registerModule(module);
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
String json = "{\n" +
" \"middle\": {\n" +
" \"fieldOne\": \"valueOne\",\n" +
" \"fieldTwo\": {\n" +
" \"fieldThree\": \"valueThree\"\n" +
" }\n" +
" }\n" +
"}\n";
ThirdPartyClass thirdPartyClass = objectMapper.readValue(json, ThirdPartyClass.class);
}
public class ThirdPartyClass {
public DeepThirdPartyClass middle;
}
public class InnerClass {
public String fieldThree;
}
Suppose I am writing custom serialization for some class, but would like to process one of its field with default methods.
How to do that?
While serializing we have JsonGenerator#writeObjectField().
But what is corresponding method for deserialization?
Regard the code below:
import com.fasterxml.jackson.core.*;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import java.io.IOException;
import java.util.Objects;
public class TryDelegate {
public static class MyOuterClassSerializer extends JsonSerializer<MyOuterClass> {
#Override
public void serialize(MyOuterClass value, JsonGenerator gen, SerializerProvider serializers) throws IOException, JsonProcessingException {
gen.writeStartObject();
gen.writeObjectField("inner", value.getInner());
gen.writeEndObject();
}
}
public static class MyOuterClassDeserializer extends JsonDeserializer<MyOuterClass> {
#Override
public MyOuterClass deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
MyOuterClass ans = new MyOuterClass();
JsonToken token;
token = p.getCurrentToken();
if( token != JsonToken.START_OBJECT ) {
throw new JsonParseException("Start object expected", p.getCurrentLocation());
}
if( !"inner".equals(p.nextFieldName() ) ) {
throw new JsonParseException("'inner; field expected", p.getCurrentLocation());
}
MyInnerClass inner = null;// how to desrialize inner from here with default processing???
ans.setInner(inner);
token = p.nextToken();
if( token != JsonToken.END_OBJECT ) {
throw new JsonParseException("End object expected", p.getCurrentLocation());
}
return ans;
}
}
public static class MyInnerClass {
private int value;
public int getValue() {
return value;
}
public void setValue(int value) {
this.value = value;
}
#Override
public String toString() {
return "{\"value\":" + value + "}";
}
}
#JsonDeserialize(using = MyOuterClassDeserializer.class)
#JsonSerialize(using = MyOuterClassSerializer.class)
public static class MyOuterClass {
private MyInnerClass inner;
public MyInnerClass getInner() {
return inner;
}
public void setInner(MyInnerClass inner) {
this.inner = inner;
}
#Override
public String toString() {
return "{\"inner\":" + Objects.toString(inner) + "}";
}
}
public static void main(String[] args) throws IOException {
ObjectMapper mapper = new ObjectMapper();
String string;
MyInnerClass inner = new MyInnerClass();
inner.setValue(12);
MyOuterClass outer = new MyOuterClass();
outer.setInner(inner);
string = mapper.writeValueAsString(outer);
System.out.println(string);
MyOuterClass outer2 = mapper.readValue(string, MyOuterClass.class);
System.out.println(outer2); // inner was not deserialized
}
}
How to implement MyOuterDeserializer?
The DeserializationContext offers these tools.
After checking the field name for "inner", move to the next token, the beginning of the JSON object and use the DeserializationContext to deserialize the JSON object into a MyInnerClass object.
if (!"inner".equals(p.nextFieldName())) {
throw new JsonParseException("'inner; field expected", p.getCurrentLocation());
}
p.nextToken(); // consumes the field name token
MyInnerClass inner = ctxt.readValue(p, MyInnerClass.class);
The javadoc states
Convenience method that may be used by composite or container
deserializers, for reading one-off values contained (for sequences, it
is more efficient to actually fetch deserializer once for the whole
collection).
Careful while using the DeserializationContext. Don't try to recursively deserialize types for which you have have registered custom deserializers.
How to custom Jackson to deserialize a value that contains a ${token} ?
Here is an example of the functionnality that i want to add from the Apache Commons Configuration variable interpolation :
{
"file" = "${sys:user.home}/path/to/my_file"
}
You can register a custom string deserialiser based on the default which would interpolate the variables after the deserialisation.
But, as was pointed out in the comments, that would not work for the non-String types such as File and URLs. The better idea is to override the getText() and getValueAsString() methods of the JsonParser by passing a custom JsonFactory.
Here is an example:
public class JacksonInterpolateString {
static final String JSON = "{ \"file\":\"${sys:user.home}/path/to/my_file\" }";
public static class Bean {
public File file;
#Override
public String toString() {
return file.toString();
}
}
private static class MyJsonParser extends JsonParserDelegate {
public MyJsonParser(final JsonParser d) {
super(d);
}
#Override
public String getText() throws IOException {
final String value = super.getText();
if (value != null) {
return interpolateString(value);
}
return value;
}
#Override
public String getValueAsString() throws IOException {
return getValueAsString(null);
}
#Override
public String getValueAsString(final String defaultValue) throws IOException {
final String value = super.getValueAsString(defaultValue);
if (value != null) {
return interpolateString(value);
}
return null;
}
}
private static class MyMappingJsonFactory extends MappingJsonFactory {
#Override
protected JsonParser _createParser(
final char[] data,
final int offset,
final int len,
final IOContext ctxt,
final boolean recyclable)
throws IOException {
return new MyJsonParser(super._createParser(data, offset, len, ctxt, recyclable));
}
#Override
protected JsonParser _createParser(final Reader r, final IOContext ctxt)
throws IOException {
return new MyJsonParser(super._createParser(r, ctxt));
}
}
private static String interpolateString(final String value) {
return value.replace("${sys:user.home}", "/home/user");
}
public static void main(String[] args) throws IOException {
final JsonFactory factory = new MyMappingJsonFactory();
final ObjectMapper mapper = new ObjectMapper(factory);
System.out.println(mapper.readValue(JSON, Map.class));
System.out.println(mapper.readValue(JSON, Bean.class));
}
}
Output:
{file=/home/user/path/to/my_file}
/home/user/path/to/my_file
I have a Map<A,B> fieldOfC as a field of a class C. When I try to deserialize C with Jackson, an Exception is thrown because it can't find a Deserializer for Map's key A. So, I guess the solution is to extend StdJsonDeserializer and do it manually.
My problem is that I can't find an example on how to use the parser and the context of the method "deserialize" that I have to implement.
Can anyone write the code for this simple example so I can use it as a start to build my real deserializer?
public class A{
private String a1;
private Integer a2;
}
public class B{
private String b1;
}
public class C{
#JsonDeserialize(keyUsing=ADeserializer.class)
//also tried this: #JsonDeserialize(keyAs=A.class) without success
private Map<A,B> fieldOfC;
private String c1;
}
public class ADeserializer extends StdKeyDeserializer {
protected ADeserializer(Class<A> cls) {
super(cls);
}
protected Object _parse(String key, DeserializationContext ctxt) throws Exception {
ObjectMapper mapper = new ObjectMapper();
return mapper.readValue(key, A.class);
}
}
Thanks in advance
EDIT: googling, I found a test of the same problem I have. This is exactly my problem
EDIT: changed extended class from StdDeserializer to StdKeyDeserializer as I read here in method findKeyDeserializer(org.codehaus.jackson.map.DeserializationConfig, org.codehaus.jackson.type.JavaType, org.codehaus.jackson.map.BeanProperty)
EDIT: After solving this issue I got this one that is related.
I am a complete newbie with Jackson, but the following works for me.
First I add a JsonCreator method to A:
public class A {
private String a1;
private Integer a2;
public String getA1() { return a1; }
public Integer getA2() { return a2; }
public void setA1(String a1) { this.a1 = a1; }
public void setA2(Integer a2) { this.a2 = a2; }
#JsonCreator
public static A fromJSON(String val) throws JsonParseException, JsonMappingException, IOException {
ObjectMapper mapper = new ObjectMapper();
A a = mapper.readValue(val,A.class);
return a;
}
}
That alone solves the deserialization problem. The harder part for me was the correct serialization of the keys. What I did there was to define a key serializer that serializes named classes as there JSON serialization, like this:
public class KeySerializer extends SerializerBase<Object> {
private static final SerializerBase<Object> DEFAULT = new StdKeySerializer();
private Set<Class<?>> objectKeys_ = Collections.synchronizedSet(new HashSet<Class<?>>());
protected KeySerializer(Class<?>... objectKeys) {
super(Object.class);
for(Class<?> cl:objectKeys) {
objectKeys_.add(cl);
}
}
#Override
public JsonNode getSchema(SerializerProvider provider, Type typeHint) throws JsonMappingException {
return DEFAULT.getSchema(provider, typeHint);
}
#Override
public void serialize(Object value, JsonGenerator jgen,
SerializerProvider provider) throws IOException,
JsonGenerationException {
if (objectKeys_.contains(value.getClass())) {
ObjectMapper mapper = new ObjectMapper();
StringWriter writer = new StringWriter();
mapper.writeValue(writer, value);
jgen.writeFieldName(writer.toString());
} else {
DEFAULT.serialize(value, jgen, provider);
}
}
}
Then to prove it works, serializing and deserializing an instance of class C:
ObjectMapper mapper = new ObjectMapper();
StdSerializerProvider provider = new StdSerializerProvider();
provider.setKeySerializer(new KeySerializer(A.class));
mapper.setSerializerProvider(provider);
StringWriter out = new StringWriter();
mapper.writeValue(out, c);
String json = out.toString();
System.out.println("JSON= "+json);
C c2 = mapper.readValue(json, C.class);
System.out.print("C2= ");
StringWriter outC2 = new StringWriter();
mapper.writeValue(outC2, c2);
System.out.println(outC2.toString());
For me this produced the output:
JSON= {"c1":"goo","map":{"{\"a1\":\"1ccf\",\"a2\":7376}":{"b1":"5ox"},"{\"a1\":\"1cd2\",\"a2\":7379}":{"b1":"5p0"},"{\"a1\":\"1cd5\",\"a2\":7382}":{"b1":"5p3"},"{\"a1\":\"1cd8\",\"a2\":7385}":{"b1":"5p6"}}}
C2= {"c1":"goo","map":{"{\"a1\":\"1ccf\",\"a2\":7376}":{"b1":"5ox"},"{\"a1\":\"1cd2\",\"a2\":7379}":{"b1":"5p0"},"{\"a1\":\"1cd5\",\"a2\":7382}":{"b1":"5p3"},"{\"a1\":\"1cd8\",\"a2\":7385}":{"b1":"5p6"}}}
I feel there ought to have been a better way of doing saying how to serialize the key by using annotations, but I could not work it out.