Java, Jackson Serialize/Deserialize Generic - java

With Jackson 2.6.7. I am trying to serialize/deserialize Foo.
public class Foo {
#JsonProperty("bar")
private ValueObject<String> bar;
#JsonProperty("baz")
private ValueObject<Integer> baz;
// potentially we be stretching to have something like
// private ValueObject<OtherClass> otherObject;
// but now just the above
// getters, setters
}
public class ValueObject<T> {
private static ObjectMapper MAPPER = new ObjectMapper();
Class<T> containedClass;
JsonNode value; // This is a requirement to store it as JsonNode
String otherContext; // This is not in the original json, but something populated after the serialize/deserialization.
String anotherContext; // This is not in the original json, but something populated after the serialize/deserialization.
public ValueObject(Class<T> containedClass) {
this.containedClass = containedClass;
}
public T get() {
return MAPPER.convertValue(this.value, containedClass);
}
public void set(T value) {
if (value.getClass() != containedClass) {
throw new RuntimeException("cannot set value");
}
this.value = MAPPER.valueToTree(value);
}
// getters, setters
}
Sample json
{
"bar": "BAR",
"baz": 1
}
Expected equivalent object
Foo expect = new Foo();
ValueObject<String> bar = new ValueObject(String.class);
bar.set("bar");
ValueObject<Integer> baz = new ValueObejct(Integer.class);
baz.set(1);
expected.setBar(bar);
expected.setBaz(baz);
Currently, I am thinking of just implementing a CustomSerializer for each ValueObject type, like ValueObjectStringSerializer, ValueObjectIntegerSerializer. Is there another of approaching this?

Related

Johnzon sub dynamic json tree mapping

In Apache Johnzon, is there a way to hava a generic field that contains dynamic JSON data, not mappable to a pre-defined POJO?
In Jackson you can simply use ObjectNode as a generic container, have some JSON processing on it, and then write the whole object in JSON format.
In Jackson it works as expected using ObjectNode, here is my code:
public class JsonTest {
private String myStaticKey = "foo";
private ObjectNode jsonData;
//code to initialize ObjectNode + getters + setters
#JsonIgnore
public void addValue(String key, String value) {
jsonData.put(key, value);
}
#JsonIgnore
public String toJson() {
return new ObjectMapper().writeValueAsString(this);
}
}
public class MainTest {
public static void main(String[] args) {
JsonTest t = new JsonTest();
t.addValue("myDynamicKey", "bar");
System.out.println(t.toJson());
}
}
Expected result:
{
"myStaticKey": "foo",
"jsonData": {
"myDynamicKey": "bar"
}
}

Gson Serialize Circular References Using Stubs

I'm trying to implement some simple Json serialization functionality but I'm having a hard time coping with the massive complexity of Gson.
So basically I have a bunch of Entity classes which reference each other with a lot of circular reference. To serialize this structure to JSON I want to keep track of the objects already serialized. The Entity classes all implement an interface called Identified which has one method String getId() giving a globally unique id. So during serializiation of one root element, I want to store all encountered ids in a Set and decide based on that set, whether to fully serialize an object or to serialize that object as a stub
"something": {
"__stub": "true",
"id": "..."
}
This shouldn't be too hard a task in my opinion, but I haven't been able to put something together. Using a custom JsonSerializer I'm not able to have an object (that is not to be serialized as a stub) serialized in the default way. Using a TypeAdapterFactory, I'm not able to access the actual object.
So, any help on how to achieve this, would be very nice!
Best regards
I'm not sure if it's possible easily. As far as I know, Gson promotes immutability and seems to lack custom serialization context support (at least I don't know if it's possible to use custom JsonSerializationContext wherever possible). Thus, one of possible work-around might be the following:
IIdentifiable.java
A simple interface to request a custom ID for an object.
interface IIdentifiable<ID> {
ID getId();
}
Entity.java
A simple entity that can hold another entity references in two manners:
a direct dependency to a "next" entity;
a collection of references to other references.
final class Entity
implements IIdentifiable<String> {
#SerializedName(ID_PROPERTY_NAME)
private final String id;
private final Collection<Entity> entities = new ArrayList<>();
private Entity next;
private Entity(final String id) {
this.id = id;
}
static Entity entity(final String id) {
return new Entity(id);
}
#Override
public String getId() {
return id;
}
Entity setAll(final Entity... entities) {
this.entities.clear();
this.entities.addAll(asList(entities));
return this;
}
Entity setNext(final Entity next) {
this.next = next;
return this;
}
}
IdentitySerializingTypeAdapterFactory.java
I didn't find any easier way rather than making it a type adapter factory, and, unfortunately, this implementation is totally stateful and cannot be reused.
final class IdentitySerializingTypeAdapterFactory
implements TypeAdapterFactory {
private final Collection<Object> traversedEntityIds = new HashSet<>();
private IdentitySerializingTypeAdapterFactory() {
}
static TypeAdapterFactory identitySerializingTypeAdapterFactory() {
return new IdentitySerializingTypeAdapterFactory();
}
#Override
public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> typeToken) {
final boolean isIdentifiable = IIdentifiable.class.isAssignableFrom(typeToken.getRawType());
final TypeAdapter<T> delegateAdapter = gson.getDelegateAdapter(this, typeToken);
if ( isIdentifiable ) {
return new TypeAdapter<T>() {
#Override
public void write(final JsonWriter out, final T value)
throws IOException {
final IIdentifiable<?> identifiable = (IIdentifiable<?>) value;
final Object id = identifiable.getId();
if ( !traversedEntityIds.contains(id) ) {
delegateAdapter.write(out, value);
traversedEntityIds.add(id);
} else {
out.beginObject();
out.name(REF_ID_PROPERTY_NAME);
writeSimpleValue(out, id);
out.endObject();
}
}
#Override
public T read(final JsonReader in) {
throw new UnsupportedOperationException();
}
};
}
return delegateAdapter;
}
}
The type adapter firstly tries to check if a given entity has been already traversed. If yes, then it's writing a special object similar to your one (the behavior could be rewritten via the strategy pattern, of course, but let it be more simple). If no, then the default type adapter is obtained, and then the given entity is delegated to that adapter, and registered as a traversed one if the latter type adapter succeeds.
The rest
And here is the rest.
SystemNames.java
final class SystemNames {
private SystemNames() {
}
private static final String SYSTEM_PREFIX = "__$";
static final String ID_PROPERTY_NAME = SYSTEM_PREFIX + "id";
static final String REF_ID_PROPERTY_NAME = SYSTEM_PREFIX + "refId";
}
GsonJsonWriters.java
final class GsonJsonWriters {
private GsonJsonWriters() {
}
static void writeSimpleValue(final JsonWriter writer, final Object value)
throws IOException {
if ( value == null ) {
writer.nullValue();
} else if ( value instanceof Double ) {
writer.value((double) value);
} else if ( value instanceof Long ) {
writer.value((long) value);
} else if ( value instanceof String ) {
writer.value((String) value);
} else if ( value instanceof Boolean ) {
writer.value((Boolean) value);
} else if ( value instanceof Number ) {
writer.value((Number) value);
} else {
throw new IllegalArgumentException("Cannot handle values of type " + value);
}
}
}
Testing
In the test below, there are three entities identified by FOO, BAR, and BAZ string identifiers. All of them have circular dependencies like this:
FOO -> BAR, BAR -> BAZ, BAZ -> FOO using the next property;
FOO -> [BAR, BAZ], BAR -> [FOO, BAZ], BAZ -> [FOO, BAR] using the entities property.
Since the type adapter factory is stateful, even GsonBuilder must be created from scratch thus not having "spoiled" state between use. Simply speaking, once a Gson instance is used once, it must be disposed, so there are GsonBuilder suppliers in the test below.
public final class Q41213747Test {
private static final Entity foo = entity("FOO");
private static final Entity bar = entity("BAR");
private static final Entity baz = entity("BAZ");
static {
foo.setAll(bar, baz).setNext(bar);
bar.setAll(foo, baz).setNext(baz);
baz.setAll(foo, bar).setNext(foo);
}
#Test
public void testSerializeSameJson() {
final String json1 = newSerializingGson().toJson(foo);
final String json2 = newSerializingGson().toJson(foo);
assertThat("Must be the same between the calls because the GSON instances are stateful", json1, is(json2));
}
#Test
public void testSerializeNotSameJson() {
final Gson gson = newSerializingGson();
final String json1 = gson.toJson(foo);
final String json2 = gson.toJson(foo);
assertThat("Must not be the same between the calls because the GSON instance is stateful", json1, is(not(json2)));
}
#Test
public void testOutput() {
out.println(newSerializingGson().toJson(foo));
}
private static Gson newSerializingGson() {
return newSerializingGson(GsonBuilder::new);
}
private static Gson newSerializingGson(final Supplier<GsonBuilder> defaultGsonBuilderSupplier) {
return defaultGsonBuilderSupplier.get()
.registerTypeAdapterFactory(identitySerializingTypeAdapterFactory())
.create();
}
}
{
"__$id": "FOO",
"entities": [
{
"__$id": "BAR",
"entities": [
{
"__$refId": "FOO"
},
{
"__$id": "BAZ",
"entities": [
{
"__$refId": "FOO"
},
{
"__$refId": "BAR"
}
],
"next": {
"__$refId": "FOO"
}
}
],
"next": {
"__$refId": "BAZ"
}
},
{
"__$refId": "BAZ"
}
],
"next": {
"__$refId": "BAR"
}
}
Deserialization of such stuff looks really complicated. At least using GSON facilities.
Do you consider rethinking your JSON model in order to avoid circular dependencies in JSON output? Maybe decomposing your objects to a single map like Map<ID, Object> and making references transient or #Expose-annotated could be easier for you to use? It would simplify deserialization as well.

Json Deserialization in Java /w Jackson of mixed types, contained in one array

Consider the following json, getting from an public API:
anyObject : {
attributes: [
{
"name":"anyName",
"value":"anyValue"
},
{
"name":"anyName",
"value":
{
"key":"anyKey",
"label":"anyLabel"
}
}
]
}
As you can see, sometimes the value is a simple string and sometimes its an object. Is it somehow possible to deserialize those kind of json-results, to something like:
class AnyObject {
List<Attribute> attributes;
}
class Attribute {
private String key;
private String label;
}
How would I design my model to cover both cases. Is that possible ?
Despite being hard to manage as others have pointed out, you can do what you want. Add a custom deserializer to handle this situation. I rewrote your beans because I felt your Attribute class was a bit misleading. The AttributeEntry class in the object that is an entry in that "attributes" list. The ValueObject is the class that represents that "key"/"label" object. Those beans are below, but here's the custom deserializer. The idea is to check the type in the JSON, and instantiate the appropriate AttributeEntry based on its "value" type.
public class AttributeDeserializer extends JsonDeserializer<AttributeEntry> {
#Override
public AttributeEntry deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
JsonNode root = p.readValueAsTree();
String name = root.get("name").asText();
if (root.get("value").isObject()) {
// use your object mapper here, this is just an example
ValueObject attribute = new ObjectMapper().readValue(root.get("value").asText(), ValueObject.class);
return new AttributeEntry(name, attribute);
} else if (root.get("value").isTextual()) {
String stringValue = root.get("value").asText();
return new AttributeEntry(name, stringValue);
} else {
return null; // or whatever
}
}
}
Because of this ambiguous type inconvenience, you will have to do some type checking throughout your code base.
You can then add this custom deserializer to your object mapper like so:
ObjectMapper objectMapper = new ObjectMapper();
SimpleModule simpleModule = new SimpleModule();
simpleModule.addDeserializer(AttributeEntry.class, new AttributeDeserializer());
objectMapper.registerModule(simpleModule);
Here's the AttributeEntry:
public class AttributeEntry {
private String name;
private Object value;
public AttributeEntry(String name, String value) {
this.name = name;
this.value = value;
}
public AttributeEntry(String name, ValueObject attributes) {
this.name = name;
this.value = attributes;
}
/* getters/setters */
}
Here's the ValueObject:
public class ValueObject {
private String key;
private String label;
/* getters/setters */
}

Jackson Json deserialization of an object to a list

I'm consuming a web service using Spring's RestTemplate and deserializing with Jackson.
In my JSON response from the server, one of the fields can be either an object or a list. meaning it can be either "result": [{}] or "result": {}.
Is there a way to handle this kind of things by annotations on the type I'm deserializing to ? define the member as an array[] or List<> and insert a single object in case of the second example ?
Can I write a new HttpMessageConverter that will handle it ?
Since you are using Jackson I think what you need is JsonDeserializer class (javadoc).
You can implement it like this:
public class ListOrObjectGenericJsonDeserializer<T> extends JsonDeserializer<List<T>> {
private final Class<T> cls;
public ListOrObjectGenericJsonDeserializer() {
final ParameterizedType type = (ParameterizedType) this.getClass().getGenericSuperclass();
this.cls = (Class<T>) type.getActualTypeArguments()[0];
}
#Override
public List<T> deserialize(final JsonParser p, final DeserializationContext ctxt) throws IOException, JsonProcessingException {
final ObjectCodec objectCodec = p.getCodec();
final JsonNode listOrObjectNode = objectCodec.readTree(p);
final List<T> result = new ArrayList<T>();
if (listOrObjectNode.isArray()) {
for (JsonNode node : listOrObjectNode) {
result.add(objectCodec.treeToValue(node, cls));
}
} else {
result.add(objectCodec.treeToValue(listOrObjectNode, cls));
}
return result;
}
}
...
public class ListOrObjectResultItemJsonDeserializer extends ListOrObjectGenericJsonDeserializer<ResultItem> {}
Next you need to annotate your POJO field. Let's say you have classes like Result and ResultItem:
public class Result {
// here you add your custom deserializer so jackson will be able to use it
#JsonDeserialize(using = ListOrObjectResultItemJsonDeserializer.class)
private List<ResultItem> result;
public void setResult(final List<ResultItem> result) {
this.result = result;
}
public List<ResultItem> getResult() {
return result;
}
}
...
public class ResultItem {
private String value;
public String getValue() {
return value;
}
public void setValue(final String value) {
this.value = value;
}
}
Now you can check your deserializer:
// list of values
final String json1 = "{\"result\": [{\"value\": \"test\"}]}";
final Result result1 = new ObjectMapper().readValue(json1, Result.class);
// one value
final String json2 = "{\"result\": {\"value\": \"test\"}}";
final Result result2 = new ObjectMapper().readValue(json2, Result.class);
result1 and result2 contain the same value.
You can achieve what you want with a configuration flag in Jackson's ObjectMapper:
ObjectMapper mapper = Jackson2ObjectMapperBuilder.json()
.featuresToEnable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY)
.build();
Just set this ObjectMapper instance to your RestTemplate as explained in this answer, and in the class you are deserializing to, always use a collection, i.e. a List:
public class Response {
private List<Result> result;
// getter and setter
}

Deserializing flattened JSON to Java Object using Jackson

So I am currently using Jackson to deserialize JSON into complex java objects. Everything works well but I also have some fields such as:
{
"foo.bar.baz":"qux"
}
which correspond to java objects such as:
class Foo {
AnotherClass bar;
}
class AnotherClass {
String baz;
}
Jackson is unable to figure out that the dots correspond to inner objects. Is there a way to get Jackson to be able to deserialize even on flattened fields such as the field in my example?
No Jackson JSON library will not detect this as different object levels. You can use this instead:
{
"foo": {
"bar": {
"baz":"qux"
}
}
}
And you will have to create:
Class WrapperClass containing "foo" of type FooClass
Class FooClass containing "bar" of type BarClass
Class BarClass containing "baz" of type String
You can do something like that by using #JsonUnwrapped:
public class Wrapper {
#JsonUnwrapped(prefix="foo.bar.")
public AnotherClass foo; // name not used for property in JSON
}
public class AnotherClass {
String baz;
}
ObjectMapper mapper = new ObjectMapper();
JsonNode root = mapper.readTree(jsonString);
Iterator<String> iterator = root.fieldNames();
while (iterator.hasNext()) {
String fieldName = iterator.next();
if (fieldName.contains(".")) {
String[] items = fieldName.split("\\.");
if (items[0].equals("foo")) {
Foo foo = new Foo();
if (items[1].equals("bar")) {
AnotherClass bar = new AnotherClass();
foo.bar = bar;
if (items[2].equals("baz")) {
bar.baz = root.get(fieldName).asText();
}
}
}
}
}

Categories

Resources