Augment jackson's JsonParser - java

Currently my applications can do as follows. It can read a JSON configuration such as:
{
"a": 5,
"b": 3
}
Into a POJO that looks like:
public class AddConf {
private Number a;
private Number b;
// constructor, getters and setters
public int add() {
return a.intValue() + b.intValue();
}
}
Then we can call the add function a return a result of 8 in this example. I would like to augment com.fasterxml.jackson.databind somehow to allow my JSON to have placeholders. So let's say when my configuration I don't know what value b will be until runtime, then I might make a configuration like this:
{
"a": 5,
"b": $b_placeholder$
}
To signify that b value will be provided at runtime. Obviously the above is not a valid JSON, and by default jackson throws an Exception (as it should) when it attempts to parse this with ObjectMapper's readValue. Ideally, I would like to read the above "JSON" (or something equivalent) into a POJO that looks something like this:
public class AddConf {
private Map<String,String> usedPlaceholders;
private Number a;
private Number b;
// constructor, getters and setters
public int add(Map<String,String> runtimeConf) {
if (usedPlaceholders.contains("a")) { // if "a" was a placeholder
a = runtimeConf.get(usedPlaceholder.get("a"));
}
if (usedPlaceholders.contains("b")) { // if "b" was a placeholder
b = runtimeConf.get(usedPlaceholder.get("b"));
}
return a.intValue() + b.intValue();
}
public void setPlaceholder(String key, String value) {
usedPlaceholder.put(key, value);
}
}
The idea is when deserializing the augmented JSON above, it would call setPlaceholder("b", "b_placeholder") instead of setting the value b and therefore when add is called, it will use values in the passed in runtimeConf Map instead of values from the JSON to do its configuration.
Given this, I have 2 questions:
Is there an easier way to accomplish my goal of having "placeholders" in my JSON configuration? It seems if I was to implement my idea here, I would have to override some of the Jackson classes. I would have to override the com.fasterxml.jackson.core.JsonParser to allow $ as a valid token in some scenarios, I would also have to write custom derserializers for all my configuration POJO (such as AddConf). This would likely casade into me having to override much of the jackson code base, which I would rather not do.
If I were to take this approach to override some of the default jackson classes, how might I go about doing that?
I am currently using jackson 2.6.0

You may be looking for annotation #JsonRawValue, use of which allows specifying EXACT contents to include as value, while still taking care of adding necessary separators.

Related

Serializing a POJO based on a runtime whitelist of properties

Is it possible to serialize a whitelisted subset of a POJO's properties (where the whitelist is known only at runtime) using Jackson?
All the solutions I know of so far (Views, #JsonIgnoreProperties etc.) are static, compile-time solutions.
Further, my backend returns results in the following format:
{
"outcome": "SUCCESS", // an enum
"message": "Success.", // a message for the developer
"result": {
// Some result that's different for each call
}
}
So I am looking for a solution that can be applied to only parts of the object graph (like the contents of the result property).
You probably want to look at #JsonFilter.
See this tutorial on serializing only fields that meet some criteria which includes details of this, and a couple of other methods.
For completeness
#JsonFilter("pojo-filter")
class Pojo {
public int foo;
}
FilterProvider filters = new SimpleFilterProvider()
.addFilter("pojo-filter", new SimpleBeanPropertyFilter() {
#Override
protected boolean include(PropertyWriter writer) {
return "foo".equals(writer.getName())
? Random.nextBoolean()
: true;
}
});
new ObjectMapper().writer().filters(filters).write(new Pojo());
Globally you can use ObjectMapper.setFilterProvider

Include enum type to serialize and deserialize with jackson

I have a simple enum I'd like to serialize and deserialize. The class looks like this:
public enum TipusViatge {
OCI,
NEGOCIS,
FAMILIA;
#Override
public String toString() {
return name().toUpperCase();
}
}
The thing is, I send it via a restful call and the receiving side may receive any type (so it only knows it will receive Object). So Jackson should be able to figure out the type of the argument to deserialize it.
Is it possible to do so? I was thinking that including the class name in the resulting json should allow Jackson to figure out the type, but I've been unable to do so.
I have worked over this problem for a while.
1st you could deserialize your json with Map<String, Object>. It alway works; you get standard types (your enumeration will be readed as plain string).
2nd in general case you alway know what kind of object you read. This is top-level object and you can set it to Jackson mapper: mapper.readerFor(cls).readValue(json). In case of your enumeration is a part of this cls object, then Jackson knows the type and just read value and parse to it.
3rd you actually could have multiple objects for one json string. I am talking about inheritance. And you could look at #JsonTypeInfo in Jackson documentation.
4th imagin that you read a json source and do not know what you read. In this case, you could ask Jackson to write marker at the beginning of the object. Just like you asking about class name. I think it relates to #JsonRootName. You can look on it here: Jackson JSON Deserialization with Root Element
I think that it is clean now how to work with objects in Jackson. I mean that we know how to tell Jackson what element we want to deserialize. Now we have one problem: how to serialize json -> our enumeration.
5th this is not a problem and works out of the box. Jackson uses name() method to serialize enumeration, and valueOf() to deserialize. You can look at it closer in EnumDeserializer in Jackson.
6th I do not like this behaviour, becuase it is case-sencitive. I faced with situation that when people write json string manually, the use lower-case and cannot deserialize it. Moreover, I belive, that writing enumeration constants directly to the json file is a bad practise, because If I want to refactor names of the enumeration, all existed json string should be modified as well (brrr). To solve thiese issues, I do following trick:
1. Implement EnumId interface with default implementation of parseId(String id) with using getId() to identify enumeration constants and using ignore case for compare.
1. I add id field to the enumeration
2. Add getId() - for serialization
3. Add parseId(String id) - for deserialization
4. Add new module in Jackson ObjectMapper with my customer serializer (it
should use `getId()` instead of `name()`).
if (enumId != null) {
generator.writeString(enumId.getId());
}
And tell Jackson how to deserialize this enum. Here this is dificult situation, becuase in different sources, Jackson use different deseriaization hierarchy and just adding another module to ObjectMapper with custom deserialize (just like in 4.) will not be working with all situations. To solve this problem, I found out that we could add #JsonCreator to parseId(String id) method in enumeration and Jackson will be using it in all situation.
I think that is all about this topic. I give you a code example to make it more clearly (it is better to write once, then explain twice):
public interface EnumId {
String name();
default String getId() {
return name().toLowerCase();
}
static <T extends Enum<?> & EnumId> T parseId(Class<T> cls, String id) {
T res = parseId(cls.getEnumConstants(), id, null);
if (res != null) {
return res;
}
throw new EnumConstantNotPresentException(cls, id);
}
static <T extends EnumId> T parseId(T[] values, String id, T def) {
for (T value : values) {
if (id != null ? id.equalsIgnoreCase(value.getId()) : value.getId() == null) {
return value;
}
}
return def;
}
static <T extends EnumId> T get(T value, T def) {
return value != null ? value : def;
}
}
public enum TipusViatge implements EnumId {
OCI,
NEGOCIS,
FAMILIA;
#JsonCreator
public static TipusViatge parseId(String id) {
return EnumId.parseId(TipusViatge.class, id);
}
}

Is it possible to parse JSON data of one complex object to custom object via API

I have a web project with 2 Java Entities(Lets Say E1,E2) like how mybatis and VO works.
Object structure:
class E1{
String a;
.... n other data members
E2 e2;
}
class E2{
String b;
.... n other data members
}
Is it possible to make a single class in Android project, i.e.
class E1 {
String a;
String b; //This Data member belongs to class E2
}
and parse it with the help of a framework (like Jackson) or I have to write a custom class for that?
My JSON Data will look like this:
{
"E1": {
"a": "some data",
.
.
.
"E2": {
"b": "some data",
.
.
other data
}
}
}
Is there any API which can do this?
I asked this because with my web Application its not just 2 Class but atleast 10 interconnected class and I am not Using them in my android app. So don't wanna replicate the same classes in android app.
Also if you can suggest any other possible way.
It would be a very bad design practice/approach, making things very difficult to debug, error prone and not future proof (think about it, what if you add to one of the 10 classes a field that conflict with another class' field?).
Anyway, if you still want to trick your way out of the correct approach that would be to have 10 classes, I am not aware of any lib that provides you with this feature. You could parse json ==> 10 Java Map, then merge the 10 Map through the Map::putAll method and finally pass the obtained Map that contains all the objects to Jackson.
Your best bet is to use #JsonAnySetter annotation from Jackson library on the receiver POJO.
public class E1{
public String a;
private Map<String, Object> paramMap = new HashMap<>();
#JsonAnyGetter
public Map<String, Object> getParamMap() {
return paramMap;
}
#JsonAnySetter
public void setParamMap(String s, Object o) {
paramMap.put(s, o);
}
}
This will put every unimplemented attributes in a HashMap.
In combination with #JsonAnyGetter, the serialization of the receiver POJO will give the same result as the JSON input.

scala jackson dealing with any type, possible custom interceptor required

I'm currently rewriting one of our applications, which uses PostgreSQL and now should use Mongo.
The architecture is pretty simple
db => case class => rest api
we are using scala jackson for it and everything works fine, except for some minor annoyance and I'm just looking for the right approach.
consider this case class to understand my annoyance
case class Test(val id:String, val value:Any)
the value in our application can be a number or a string at this point, number either an Integer or a Double.
so when we receive JSON like this, from our legacy application:
{ id:"a",value:"test"}
it gets mapped correctly and results in the expected types of String, String.
But assuming the following:
{ id:"b", value:"1"}
we would like to have this mapped instead to String,Integer. But obviously jackson thinks it's a String and maps it to String, String.
Is there some transparent way todo this? My thinking, would be something like an interceptor for jackson would be nice, which simple tests
if type.isDouble == true return double value
else if type.isInteger == true return integer value
else return string value
so we would end up with
String,Double
String,Integer
String,String
in this example.
Obviously I can write a generic parser and bring the dataformat into the correct form beforehand, but I rather would have this done transparency, since we never know when user will submit the legacy JSON format, with this bug and so possibly corrupt the system.
thx
Ok it looks like this is easier than exspected, with writing a customer serializer and annotating our one field which needs it
Solution found here
actual example:
class NumberDeserializer extends JsonDeserializer[Any] {
override def deserialize(jsonParser: JsonParser, deserializationContext: DeserializationContext): Any = {
val jsonNode: JsonNode = jsonParser.getCodec.readTree(jsonParser)
val content = jsonNode.textValue
try {
content.toInt
} catch {
case e: NumberFormatException => try {
content.toDouble
}
catch {
case e2: NumberFormatException => content
}
}
}
}
usage:
case class Test(
#JsonDeserialize(using = classOf[NumberDeserializer])
value: Any
)

morphia handle bad data

Let's say I have some json like this in mongo:
{"n":"5"}
and a java class like this:
#Entity
public class Example {
Integer n;
}
This works (I know that the json should store the value as an int not a string but I don't control that part).
Now when I have data like this morphia throws:
{"n":""}
I'm looking for a workaround (the behavior I'd like is for empty string to be treated same as null).
The only workaround I have so far is:
public class Example {
String n;
public Integer getN() {
return NumberUtils.isNumber(n) ? NumberUtils.createInteger(n) : null;
}
}
But I'm hoping for some way to hang an annotation on the Integer property that customizes the deserialization behavior.
So I asked this on the morphia google group and I thought I'd share the answer. Using the lifecycle annotation #PreLoad allows me to modify the DBObject before conversions into POJO takes place. So this should do it:
#PreLoad void fixup(DBObject obj) {
if (StringUtils.isEmpty(obj.get("n"))) {
obj.put("n",null);
}
}

Categories

Resources