I know Gson doesn't come with a similar feature, but is there a way to add support for unwrapping Json fields the way #JsonUnwrap does?
The goal is to allow a structure like:
public class Person {
public int age;
public Name name;
}
public class Name {
public String first;
public String last;
}
to be (de)serialized as:
{
"age" : 18,
"first" : "Joey",
"last" : "Sixpack"
}
instead of:
{
"age" : 18,
"name" : {
"first" : "Joey",
"last" : "Sixpack"
}
}
I understand it could get fairly complex, so I'm not looking for a full solution, just some high-level guidelines if this is even doable.
I've made a crude implementation of a deserializer that supports this. It is fully generic (type-independent), but also expensive and fragile and I will not be using it for anything serious. I am posting only to show to others what I've got, if they end up needing to do something similar.
public class UnwrappingDeserializer implements JsonDeserializer<Object> {
//This Gson needs to be identical to the global one, sans this deserializer to prevent infinite recursion
private Gson delegate;
public UnwrappingDeserializer(Gson delegate) {
this.delegate = delegate;
}
#Override
public Object deserialize(JsonElement json, Type type, JsonDeserializationContext context) throws JsonParseException {
Object def = delegate.fromJson(json, type); //Gson doesn't care about unknown fields
Class raw = GenericTypeReflector.erase(type);
Set<Field> unwrappedFields = ClassUtils.getAnnotatedFields(raw, GsonUnwrap.class);
for (Field field : unwrappedFields) {
AnnotatedType fieldType = GenericTypeReflector.getExactFieldType(field, type);
field.setAccessible(true);
try {
Object fieldValue = deserialize(json, fieldType.getType(), context);
field.set(def, fieldValue);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
return def;
}
}
It can then be registered globally via new GsonBuilder().registerTypeHierarchyAdapter(Object.class, new UnwrappingDeserializer(new Gson())).create() or for a specific type via registerTypeAdapter.
Notes:
A real implementation should recursively check the entire class structure for the presence of GsonUnwrap, cache the result in a concurrent map, and only go through this procedure if it needs to. Otherwise it should just return def immediately
It should also cache discovered annotated fields to avoid scanning the hierarchy each time
GenericTypeReflector is coming from GeAnTyRef
ClassUtils#getAnnotatedFields is my own implementation, but it doesn't do anything special - it just gathers declared fields (via Class#getDeclaredFields) recursively for the class hierarchy
GsonUnwrap is just a simple custom annotation
I presume a similar thing can be done for serialization as well. Examples linked from Derlin's answer can be a starting point.
Currently, there is no easy way to do that. Here are anyway some pointers/alternative ways to make it work.
GsonFire: GsonFire implements some useful features missing from Gson. While it does not yet offer automatic wrapping/unwrapping, it may be a good starting point to create your custom logic.
If you only need serialization, you can add getters for first and last in Person and use #ExposeMethodResult to serialize them. Unfortunately, setters are not supported (cf. Is possible to use setters when Gson deserializes a JSON?).
Another way to support the serialization is to follow the advices from How to move fields to parent object.
Custom TypeAdapters : on of the only ways to support both serialization and deserialization is to create custom TypeAdapters. This won't be generic, but it will suit your usecase.
The thread Serialize Nested Object as Attributes already gives you examples, so I won't repeat them here.
Related
Please note: even though I mention Dozer in this question, I do believe its really just a pure Java generics question at heart. There may be a Dozer-specific solution out there, but I think anyone with strong working knowledge of Java (11) generics/captures/erasures should be able to help me out!
Java 11 and Dozer here. Dozer is great for applying default bean mapping rules to field names, but anytime you have specialized, custom mapping logic you need to implement a Dozer CustomConverter and register it. That would be great, except the Dozer API for CustomConverter isn't genericized, is monolithic and leads to nasty code like this:
public class MyMonolithicConverter implements CustomConverter {
#Override
public Object convert(Object destination, Object source, Class<?> destinationClass, Class<?> sourceClass) {
if (sourceClass.isAssignableFrom(Widget.class)) {
Widget widget = (Widget)source;
if (destinationClass.isAssignableFrom(Fizz.class)) {
Fizz fizz = (Fizz)destination;
// write code for mapping widget -> fizz here
} else if (destinationClass.isAssignableFrom(Foo.class)) {
// write code for mapping widget -> foo here
}
... etc.
} else if (sourceClass.isAssignableFrom(Baz.class)) {
// write all the if-else-ifs and mappings for baz -> ??? here
}
}
}
So again: monolithic, not genericized and leads to large, complex nested if-else-if blocks. Eek.
I'm trying to make this a wee bit more palatable:
public abstract class BeanMapper<SOURCE,TARGET> {
private Class<SOURCE> sourceClass;
private Class<TARGET> targetClass;
public abstract TARGET map(SOURCE source);
public boolean matches(Class<?> otherSourceClass, Class<?> otherTargetClass) {
return sourceClass.equals(otherSourceClass) && targetClass.equals(otherTargetClass);
}
}
Then, an example of it in action:
public class SignUpRequestToAccountMapper extends BeanMapper<SignUpRequest, Account> {
private PlaintextEncrypter encrypter;
public SignUpRequestToAccountMapper(PlaintextEncrypter encrypter) {
this.encrypter = encrypter;
}
#Override
public Account map(SignUpRequest signUpRequest) {
return Account.builder()
.username(signUpRequest.getRequestedName())
.email(signUpRequest.getEmailAddr())
.givenName(signUpRequest.getFirstName())
.surname(signUpRequest.getLastName()())
.dob(DateUtils.toDate(signUpRequest.getBirthDate()))
.passwordEnc(encrypter.saltPepperAndEncrypt(signUpRequest.getPasswordPlaintext()))
.build();
}
}
And now a way to invoke the correct source -> target mapper from inside my Dozer converter:
public class DozerConverter implements CustomConverter {
private Set<BeanMapper> beanMappers;
#Override
public Object convert(Object destination, Object source, Class<?> destinationClass, Class<?> sourceClass) {
BeanMapper<?,?> mapper = beanMappers.stream()
.filter(beanMapper -> beanMapper.matches(sourceClass, destinationClass))
.findFirst()
.orElseThrow();
// compiler error here:
return mapper.map(source);
}
}
I really like this design/API approach, however I get a compiler error on that mapper.map(source) line at the very end:
"Required type: capture of ?; Provided: Object"
What can I do to fix this compiler error? I'm not married to this API/approach, but I do like the simplicity it adds over the MyMonolithicConverter example above, which is the approach Dozer sort of forces on you. It is important to note that I am using Dozer elsewhere for simple bean mappings so I would prefer to use a CustomConverter impl and leverage Dozer for this instead of bringing in a whole other dependency/library for these custom/complex mappings. If Dozer offers a different solution I might be happy with that as well. Otherwise I just need to fix this capture issue. Thanks for any help here!
The issue seems to come from the beanMappers. You have a set of mappers of various types. The compiler cannot infer what types the found mapper will have.
You can make the compiler believe you by casting the result and suppress the warning it gives you.
Casting to a <?,?> isn't going to happen, so I've added symbols for the convert method. At least it can then be assumed that when you get a BeanMapper<S,T>, map will indeed return a T upon an S source.
class DozerConverter {
private Set<BeanMapper<Object,Object>> beanMappers;
public <S,T> T convert(S source,
Class<?> destinationClass,
Class<?> sourceClass) {
#SuppressWarnings("unchecked")
BeanMapper<S,T> mapper = (BeanMapper<S,T>) beanMappers.stream()
.filter(beanMapper -> beanMapper.matches(sourceClass, destinationClass))
.findFirst()
.orElseThrow();
return mapper.map(source);
}
}
I'm afraid you're going to have to call it like so:
TARGET-TYPE target = dozerConverter.<SOURCE-TYPE,TARGET-TYPE>convert(...);
I have a JSON structure where objects are basically unions - a discriminator field determines the type of the object, and each type has custom properties. As an example, imagine an Animal structure:
{
animals: [
{
species: 'DOG',
data: {
buriedBones: 8
}
},
{
species: 'CAT',
data: {
hasEvilPlan: true
}
}
]
}
On the Java side, I'd like this to be represented by a class with the species discriminator enum and a data member, which can be either a CatData or a DogData, so something like:
enum Species {DOG, CAT}
abstract class Data {}
class DogData extends Data {
int buriedBones;
}
class CatData extends Data {
boolean hasEvilPlan;
}
public class Animal {
public Species species;
public Data data;
}
This means that when serializing, I don't really need to do anything special, but when deserializing, I need to read the species value and from it determine the class of the Data object (DogData.class or CatData.class), and then deserialize the data as that class and put it in the data field.
For this I've written a custom type adapter which uses annotations to supply a map of discriminator->data class, and it works, but I'm not quite happy. I can get the JSON tree, find the species value and from it the data class, but I can't find a clever way to deserialize only the data field using the specific class, while leaving everything else to the default behaviour. The only solution I've found so far is to remove the data property from the JSON tree, call the delegate adapter (which results in animal.data being null) and then manually use reflection to set the data field:
discriminator = "species";
...
#Override
public T read(JsonReader in) throws IOException {
JsonObject tree = treeAdapter.read(in).getAsJsonObject();
// look up class from map of "DOG"->DogData.class etc
Type dataType = getDataType(tree.get(discriminator).getAsString());
tree.remove("data");
T result = delegate.fromJsonTree(tree); // Creates an Animal with data = null
Field field = result.getClass().getField("data");
field.setAccessible(true);
field.set(result, gson.fromJson(data, dataType)); // Create DogData or CatData and assign it to animal.data afterwards
return result;
I'd rather not have to resort to manual reflection, but I can't find a way to use the delegate adapter, but tell it "when you reach the data field, please use class DogData.class to deserialize it".
Note that I could of course write a TypeAdapter specifically for Animal which simply does return new Animal(Species.valueOf(species), gson.fromJson(data, dataClass)), but what I need is a generic adapter that can be used on various classes using this pattern, and where any other properties in the class are deserialized the default way.
I also know that if the classes that use this pattern would be generic, as in Animal<D extends Data>, I could probably deserialize them using a TypeToken<Animal<DogData>>, but that also puts unnecessary constraints on the data model. Again, what I'm after is a way to only override one specific field by telling GSON to use a specific class, derived from another field, to deserialize it, but then handle all other deserialization automatically (so that e.g. a property like animal.nrLegs would be preserved). Is there a clever way I missed?
I'd like the class name of all POJOs Jackson serializes to JSON objects to be included as a property of the respective object. Ideally, this should be achieved through some general setting of the ObjectMapper or similar.
Serialization example:
public class MyClass {
private String someField;
private MyOtherClass anotherField;
}
to
{
"$type": "MyClass",
"someField": "abc",
"anotherField": {
"$type": "MyOtherClass",
...
}
}
I know this could also be done by annotating all the respective classes with something like
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "$type")
But I'd like to avoid that.
I've also tried
objectMapper.enableDefaultTypingAsProperty(ObjectMapper.DefaultTyping.JAVA_LANG_OBJECT, "$type")
which didn't seem to work.
Is there such a general setting available?
EDIT:
Thanks to AZWN's hints I actually got what I was looking for by customizing and constructing a TypeResolverBuilder with DefaultTyping.NON_FINAL.
StdTypeResolverBuilder typer = new ObjectMapper.DefaultTypeResolverBuilder(ObjectMapper.DefaultTyping.NON_FINAL) {
#Override
public boolean useForType(JavaType t) {
return !(t.isCollectionLikeType() || t.isMapLikeType()) && super.useForType(t);
}
}
.init(JsonTypeInfo.Id.NAME, null)
.inclusion(JsonTypeInfo.As.PROPERTY)
.typeProperty("$type");
objectMapper.setDefaultTyping(typer);
You should use ObjectMapper.DefaultTyping.NON_FINAL instead of ObjectMapper.DefaultTyping.JAVA_LANG_OBJECT, since the latter one only includes type properties when the field type of a class is Object at compile time.
For more info, see the docs. Also, be aware of the security issues they mention.
I can't find a simple way to add a custom field during serialization in Gson and I was hoping someone else may be able to help.
Here is a sample class to show my issue:
public class A {
String id;
String name;
...
}
When I serialize class A I would like to return something like:
{ "id":"123", "name":"John Doe", "url_to_user":"http://www.example.com/123" }
where url_to_user is not stored in my instance of class A, but can be generated with data in the instance of class A.
Is there a simple way of doing this? I would prefer to avoid writing an entire serializer just to add one field.
Use Gson.toJsonTree to get a JsonElement, with which you can interact dynamically.
A a = getYourAInstanceHere();
Gson gson = new Gson();
JsonElement jsonElement = gson.toJsonTree(a);
jsonElement.getAsJsonObject().addProperty("url_to_user", url);
return gson.toJson(jsonElement);
Well, the top rated answer is quite a quick one and not essentially bad when you are lacking much time but here is the problem: There is no proper separation of concern
You are modifying the serialized JSON at the same place where you are writing your business logic. You should be doing all the serialization inside of a TypeAdapter or a JsonSerializer.
How can we maintain a proper separation of concern?
The answer wraps around a bit of additional complexity but the architecture demands it. Here we go(taken from my other answer):
First, we would be using a custom serializer for the type. Second, we would have to create a copy constructor inside the base class and a wrapper subclass as follows:
Note: The custom serializer might seem like an overkill but trust me, it pays off in long run for maintainability.
.
// Lets say the base class is named Cat
public class Cat {
public String name;
public Cat(String name) {
super();
this.name = name;
}
// COPY CONSTRUCTOR
public Cat(Cat cat) {
this.name = cat.name;
}
#Override
public String sound() {
return name + " : \"meaow\"";
};
}
// The wrapper subclass for serialization
public class CatWrapper extends Cat{
public CatWrapper(String name) {
super(name);
}
public CatWrapper(Cat cat) {
super(cat);
}
}
And the serializer for the type Cat:
public class CatSerializer implements JsonSerializer<Cat> {
#Override
public JsonElement serialize(Cat src, Type typeOfSrc, JsonSerializationContext context) {
// Essentially the same as the type Cat
JsonElement catWrapped = context.serialize(new CatWrapper(src));
// Here, we can customize the generated JSON from the wrapper as we want.
// We can add a field, remove a field, etc.
// The main logic from the top rated answer now here instead of *spilling* around(Kindly ignore the cat having a url for the sake of example)
return catWrapped.getAsJsonObject().addProperty("url_to_user", url);
}
}
So, why a copy constructor?
Well, once you define the copy constructor, no matter how much the base class changes, your wrapper will continue with the same role. Secondly, if we don't define a copy constructor and simply subclass the base class then we would have to "talk" in terms of the extended class, i.e, CatWrapper. It is quite possible that your components talk in terms of the base class and not the wrapper type.
How do I tell Xstream to serialize only fields which are annotated explicitly and ignore the rest?
I am trying to serialize a hibernate persistent object and all proxy related fields get serialized which I don’t want in my xml.
e.g.
<createdBy class="com..domain.Users " reference="../../values/createdBy"/>
is not something I want in my xml.
Edit: I don’t think I made this question clear. A class may inherit from a base class on which I have no control (as in hibernate’s case) on the base class properties.
public class A {
private String ShouldNotBeSerialized;
}
public class B extends A {
#XStreamAlias("1")
private String ThisShouldbeSerialized;
}
In this case when I serialize class B, the base class field ShouldNotBeSerialized will also get serialized. This is not something I want. In most circumstances I will not have control on class A.
Therefore I want to omit all fields by default and serialize only fields for which I explicitly specify the annotation. I want to avoid what GaryF is doing, where I need to explicitly specify the fields I need to omit.
You can omit fields with the #XstreamOmitField annotation. Straight from the manual:
#XStreamAlias("message")
class RendezvousMessage {
#XStreamOmitField
private int messageType;
#XStreamImplicit(itemFieldName="part")
private List<String> content;
#XStreamConverter(SingleValueCalendarConverter.class)
private Calendar created = new GregorianCalendar();
public RendezvousMessage(int messageType, String... content) {
this.messageType = messageType;
this.content = Arrays.asList(content);
}
}
I can take no credit for this answer, just sharing what I have found. You can override the wrapMapper method of the XStream class to achieve what you need.
This link explains in detail: http://pvoss.wordpress.com/2009/01/08/xstream/
Here is the code you need if you don't want the explanation:
// Setup XStream object so that it ignores any undefined tags
XStream xstream = new XStream() {
#Override
protected MapperWrapper wrapMapper(MapperWrapper next) {
return new MapperWrapper(next) {
#Override
public boolean shouldSerializeMember(Class definedIn,
String fieldName) {
if (definedIn == Object.class) {
return false;
}
return super
.shouldSerializeMember(definedIn, fieldName);
}
};
}
};
You might want to do all your testing before you implement this code because the exceptions thrown by the default XStream object are useful for finding spelling mistakes.
There was already a ticket for the XStream people:
Again, this is by design. XStream is a serialization tool, not a data
binding tool. It is made to serialize Java objects to XML and back. It
will write anything into XML that is necessary to recreate an equal
object graph. The generated XML can be tweaked to some extend by
configuration for convenience, but this is already an add-on. What you
like to do can be done by implementing a custom mapper, but that's a
question for the user's list and cannot be handled here.
http://jira.codehaus.org/browse/XSTR-569
I guess the only direct way is to dive into writing a MapperWrapper and exclude all fields you have not annotated. Sounds like a feature request for XStream.