I receive different objects set from the API. Each response have a follow structure:
items:[
{
user_id:1,
tags: {..}
},
{..}
]
The problem is that I do not want so unuseful and not readable structure.
I mean, all my methods (I use Retrofit library) must have some next signature:
Call<UserRepresantation>...
Call<RepoRepresentation>...
instead
Call<List<Users>>
Call<List<Repos>>
And also I have to use additional entities every time:
class UserRepresentation{
List<Users> items;
}
The Retrofite has possibility to use different converters for the serialization, for example:
Retrofit.Builder()
.baseUrl(stckUrl)
.addConverterFactory(GsonConverterFactory.create(new Gson())) < --- converter applying
.build();
As I understand I can use JsonSeializer to configure such behavior, but I can't figure out in which way. Can anyone help me to solve this issue?
So, in the simple words:
we have a response:
items:[
{
user_id:1,
tags: {..}
},
{..}
]
And we need to receive:
List<Users> = gson.fromJson(respose, User.class);
One solution would be to write a TypeAdapterFactory which performs the unwrapping when asked to deserialize any List<User> and List<Repo>, or in general for any List. However, the problem with this is that it would also apply to any nested lists of these types, for example when your User class has a field List<Repo> repos then that adapter factory would also try to unwrap its value, and fail.
So a more reliable solution might be to implement a TypeAdapterFactory which keeps track of whether it is currently being used to deserialize the top-level value and in that case unwrap / flatten the data. If not used for the top-level value it could simply let the other registered adapter factories handle the data:
class FlatteningTypeAdapterFactory implements TypeAdapterFactory {
public static final FlatteningTypeAdapterFactory INSTANCE = new FlatteningTypeAdapterFactory();
private FlatteningTypeAdapterFactory() { }
/** Tracks whether this is a nested call to this factory */
private static final ThreadLocal<Boolean> isNestedCall = new ThreadLocal<>();
#Override
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
// Only handle top-level value, for nested calls let other factory handle it
// Uses Boolean.TRUE.equals to handle case where value is `null`
if (Boolean.TRUE.equals(isNestedCall.get())) {
return null;
}
TypeAdapter<T> delegate;
isNestedCall.set(true);
try {
delegate = gson.getDelegateAdapter(this, type);
} finally {
isNestedCall.remove();
}
return new TypeAdapter<T>() {
#Override
public void write(JsonWriter out, T value) {
throw new UnsupportedOperationException();
}
#Override
public T read(JsonReader in) throws IOException {
in.beginObject();
String name = in.nextName();
if (!name.equals("items")) {
throw new IllegalArgumentException("Unexpected member name: " + name);
}
T value;
// While using delegate adapter also set isNestedCall in case delegate looks up
// another adapter dynamically while its `read` method is called
isNestedCall.set(true);
try {
value = delegate.read(in);
} finally {
isNestedCall.remove();
}
in.endObject();
return value;
}
};
}
}
You would then have to register it with a GsonBuilder before constructing the GsonConverterFactory:
Gson gson = new GsonBuilder()
.registerTypeAdapterFactory(FlatteningTypeAdapterFactory.INSTANCE)
.create();
Note that the code above has not been extensively tested; there might be bugs or corner cases where it does not work correctly.
Related
I have nested Object (composition) use to represent data that i want to store and using Dyanmodb enhanced client as part of AWS Java version 2 api. In the readme it explains how to flatten the objects. In the version one of the api was able to store list of objects as json documents in dyanmodb.
public class Customer{
private String name;
private List<GenericRecord> recordMetadata;
//getters and setters for all attributes
}
public class GenericRecord {
private String id;
private String details;
//getters and setters for all attributes
}
Would like it to be stored as below not flattened for backward compatibility:
{
"name": "ABC",
"recordMetadata": [
{
"id":"123",
"details":"hello"
},
{
"id":"456",
"details":"yellow"
}
]
}
https://github.com/aws/aws-sdk-java-v2/blob/master/services-custom/dynamodb-enhanced/README.md
If I understood, you want to serialize the nested object to a String, just like the #DynamoDBTypeConvertedJson annotation did with the DynamoDBMapper in v1 of the AWS SDK for Java. There is nothing that comes out of the box to do this in v2 of the AWS SDK for Java. You'll have to write your own converter by hand as shown below.
But there's really no benefit to serializing it as a String, so you may consider just storing it as a nested document. It shouldn't require any changes to the code you posted. Storing it as a document does have benefits like being able to update a single nested field. You never know when a requirement may come along that requires this, and again, I'm not aware of any downside to storing it as a document.
Note: I don't think #DynamoDbFlatten will work in your case because it doesn't make sense to flatten a list.
class GenericRecordListConverter implements AttributeConverter<List<GenericRecord>> {
private static final ObjectMapper MAPPER = new ObjectMapper();
public static GenericRecordListConverter create() {
return new GenericRecordListConverter();
}
#Override
public AttributeValue transformFrom(List<GenericRecord> input) {
try {
return AttributeValue.builder().s(MAPPER.writeValueAsString(input)).build();
}
catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
}
#Override
public List<GenericRecord> transformTo(AttributeValue input) {
try {
return MAPPER.readValue(input.s(), new TypeReference<List<GenericRecord>>() {});
}
catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
}
#Override
public EnhancedType<List<GenericRecord>> type() {
return EnhancedType.listOf(GenericRecord.class);
}
#Override
public AttributeValueType attributeValueType() {
return AttributeValueType.S;
}
}
This is resolved, don't need converter see:
.addAttribute(EnhancedType.listOf(EnhancedType.documentOf(GenericRecord.class,TableSchema.fromClass(GenericRecord.class))),
a -> a.name("recordMetadata").getter(Customer::getRecordMetadata)
.setter(Customer::setRecordMetadata) )
Official response:
https://github.com/aws/aws-sdk-java-v2/issues/2265
I'm retrieving comments from the Reddit API. The model is threaded such that each Comment can internally have a List of Comments, named replies. Here's an example of how a JSON response would look:
[
{
"kind":"Listing",
"data":{
"children":[
{
"data":{
"body":"comment",
"replies":{
"kind":"Listing",
"data":{
"children":[
{
"data":{
"body":"reply to comment",
"replies":""
}
}
]
}
}
}
}
]
}
}
]
Here is how I model this with POJOs. The response above would be considered a List of CommentListings.
public class CommentListing {
#SerializedName("data")
private CommentListingData data;
}
public final class CommentListingData {
#SerializedName("children")
private List<Comment> comments;
}
public class Comment {
#SerializedName("data")
private CommentData data;
}
public class CommentData {
#SerializedName("body")
private String body;
#SerializedName("replies")
private CommentListing replies;
}
Note how the bottom level CommentData POJO refers to another CommentListing called "replies".
This model works until GSON reaches the last child CommentData where there are no replies. Rather than providing a null, the API is providing an empty String. Naturally, this causes a GSON exception where it expects an object but finds a String:
"replies":""
Expected BEGIN_OBJECT but was STRING
I attempted to create a custom deserializer on the CommentData class, but due to the recursive nature of the model it seems not to reach the bottom levels of the model. I imagine this is because I'm using a separate GSON instance to complete deserialization.
#Singleton
#Provides
Gson provideGson() {
Gson gson = new Gson();
return new GsonBuilder()
.registerTypeAdapter(CommentData.class, new JsonDeserializer<CommentData>() {
#Override
public CommentData deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
JsonObject commentDataJsonObj = json.getAsJsonObject();
JsonElement repliesJsonObj = commentDataJsonObj.get("replies");
if (repliesJsonObj != null && repliesJsonObj.isJsonPrimitive()) {
commentDataJsonObj.remove("replies");
}
return gson.fromJson(commentDataJsonObj, CommentData.class);
}
})
.serializeNulls()
.create();
}
How can I force GSON to return a null instead of a String so that it doesn't try to force a String into my POJO? Or if that's not possible, manually reconcile the data issue? Please let me know if you need additional context or information. Thanks.
In general your code looks good, but I would recommend a few things:
Your type adapters should not capture Gson instances from outside. Type adapter factories (TypeAdapterFactory) are designed for this purpose. Also, in JSON serializers and deserializers you can implicitly refer it through JsonSerializationContext and JsonDeserializationContext respectively (this avoids infinite recursion in some cases).
Avoid modification JSON objects in memory as much as possible: serializers and deserializers are just a sort of pipes and should not bring you surprises with modified objects.
You can implement a generic "empty string as a null" type deserializer and annotate each "bad" field that requires this kind of deserialization strategy. You might consider it's tedious, but it gives you total control wherever you need it (I don't know if Reddit API has some more quirks like this).
public final class EmptyStringAsNullTypeAdapter<T>
implements JsonDeserializer<T> {
// Let Gson instantiate it itself
private EmptyStringAsNullTypeAdapter() {
}
#Override
public T deserialize(final JsonElement jsonElement, final Type type, final JsonDeserializationContext context)
throws JsonParseException {
if ( jsonElement.isJsonPrimitive() ) {
final JsonPrimitive jsonPrimitive = jsonElement.getAsJsonPrimitive();
if ( jsonPrimitive.isString() && jsonPrimitive.getAsString().isEmpty() ) {
return null;
}
}
return context.deserialize(jsonElement, type);
}
}
And then just annotate the replies field:
#SerializedName("replies")
#JsonAdapter(EmptyStringAsNullTypeAdapter.class)
private CommentListing replies;
I am trying to implement a JSON serialization in Java with Genson 1.3 for polymorphic types, including:
Numbers
Arrays
Enum classes
The SSCCE below demonstrates roughly what I am trying to achieve:
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import com.owlike.genson.Genson;
import com.owlike.genson.GensonBuilder;
/**
* A Short, Self Contained, Compilable, Example for polymorphic serialization
* and deserialization.
*/
public class GensonPolymoprhicRoundTrip {
// our example enum
public static enum RainState {
NO_RAIN,
LIGHT_RAIN,
MODERATE_RAIN,
HEAVY_RAIN,
LIGHT_SNOW,
MODERATE_SNOW,
HEAVY_SNOW;
}
public static class Measurement<T> {
public T value;
public int qualityValue;
public String source;
public Measurement() {
}
public Measurement(T value, int qualityValue, String source) {
this.value = value;
this.qualityValue = qualityValue;
this.source = source;
}
}
public static class DTO {
public List<Measurement<?>> measurements;
public DTO(List<Measurement<?>> measurements) {
this.measurements = measurements;
}
}
public static void main(String... args) {
Genson genson = new GensonBuilder()
.useIndentation(true)
.useRuntimeType(true)
.useClassMetadataWithStaticType(false)
.addAlias("RainState", RainState.class)
.useClassMetadata(true)
.create();
DTO dto = new DTO(
new ArrayList(Arrays.asList(
new Measurement<Double>(15.5, 8500, "TEMP_SENSOR"),
new Measurement<double[]>(new double[] {
2.5,
1.5,
2.0
}, 8500, "WIND_SPEED"),
new Measurement<RainState>(RainState.LIGHT_RAIN, 8500, "RAIN_SENSOR")
)));
String json = genson.serialize(dto);
System.out.println(json);
DTO deserialized = genson.deserialize(json, DTO.class);
}
}
Numbers and Arrays worked well out-of-the-box, but the enum class is providing a bit of a challenge. In this case the serialized JSON form would have to be IMO a JSON object including a:
type member
value member
Looking at the EnumConverter class I see that I would need to provide a custom Converter. However I can't quite grasp how to properly register the Converter so that it would be called during deserialization. How should this serialization be solved using Genson?
Great for providing a complete example!
First problem is that DTO doesn't have a no arg constructor, but Genson supports classes even with constructors that have arguments. You just have to enable it via the builder with 'useConstructorWithArguments(true)'.
However this will not solve the complete problem. For the moment Genson has full polymorphic support only for types that are serialized as a json object. Because Genson will add a property called '#class' to it. There is an open issue for that.
Probably the best solution that should work with most situations would be to define a converter that automatically wraps all the values in json objects, so the converter that handles class metadata will be able to generate it. This can be a "good enough" solution while waiting for it to be officially supported by Genson.
So first define the wrapping converter
public static class LiteralAsObjectConverter<T> implements Converter<T> {
private final Converter<T> concreteConverter;
public LiteralAsObjectConverter(Converter<T> concreteConverter) {
this.concreteConverter = concreteConverter;
}
#Override
public void serialize(T object, ObjectWriter writer, Context ctx) throws Exception {
writer.beginObject().writeName("value");
concreteConverter.serialize(object, writer, ctx);
writer.endObject();
}
#Override
public T deserialize(ObjectReader reader, Context ctx) throws Exception {
reader.beginObject();
T instance = null;
while (reader.hasNext()) {
reader.next();
if (reader.name().equals("value")) instance = concreteConverter.deserialize(reader, ctx);
else throw new IllegalStateException(String.format("Encountered unexpected property named '%s'", reader.name()));
}
reader.endObject();
return instance;
}
}
Then you need to register it with a ChainedFactory which would allow you to delegate to the default converter (this way it works automatically with any other type).
Genson genson = new GensonBuilder()
.useIndentation(true)
.useConstructorWithArguments(true)
.useRuntimeType(true)
.addAlias("RainState", RainState.class)
.useClassMetadata(true)
.withConverterFactory(new ChainedFactory() {
#Override
protected Converter<?> create(Type type, Genson genson, Converter<?> nextConverter) {
if (Wrapper.toAnnotatedElement(nextConverter).isAnnotationPresent(HandleClassMetadata.class)) {
return new LiteralAsObjectConverter(nextConverter);
} else {
return nextConverter;
}
}
}).create();
The downside with this solution is that useClassMetadataWithStaticType needs to be set to true...but well I guess it is acceptable as it's an optim and can be fixed but would imply some changes in Gensons code, the rest still works.
If you are feeling interested by this problem it would be great you attempted to give a shot to that issue and open a PR to provide this feature as part of Genson.
I have an existing class hierarchy that looks like this:
public interface Service {
String getId();
String getName();
}
public class FooTask extends AbstractTask {
private final static ObjectMapper JSON_MAPPER = new ObjectMapper();
static {
JSON_MAPPER.registerModule(new SimpleModule().addDeserializer(Result.class, new ResultDeserializer());
}
public FooTask(Service service) {
super(service);
}
#Override public Result call() throws Exception {
InputStream json = <... execute some code to retrieve JSON ...>
Result result = JSON_MAPPER.readValue(json, Result.class);
}
private static class ResultDeserializer {
#Override public Result deserialize(JsonParser parser, DeserializationContext ctx) throws IOException {
//
// Need to access service#getId() down here... but we're in a static nested class
// and I don't know how to access it. Is there some way to pass that info via the DeserializationContext?
//
<... Deserialization logic ...>
}
}
}
I need to pass some information to the deserializer at deserialization time but I cannot find a way to pass some contextual information to the deserializer at deserialization time. Is this possible? If so, how? I would prefer to not have to allocate a new ObjectMapper every time the FooTask is instantiated or #call() method is invoked.
So I came up with a solution... no idea if it is the ideal solution, but it is a solution - basically I first create an instance of InjectableValues:
private InjectableValues newInjectableValues() {
return new InjectableValues.Std()
.addValue("providerId", service.getId())
}
Then I get a new ObjectReader instance from the ObjectMapper and use that to perform the deserialization:
JSON_MAPPER.reader(newInjectableValues()).withType(Result.class).readValue(inputStream)
Down in the actual Deserializer I use this method to retrieve the values provided by the InjectableValues:
ctx.findInjectableValue("providerId", null, null);
I'm using Gson to parse responses from a server on Android. Each response has some useless (to me) data on it that complicates my Gson models. Here is the general hierarchy of json returned:
response: {
date: 1406253006807,
otherUselessData1: "This is some useless data",
otherUselessData2: "This is some useless data",
usefulJsonObject: { <---- This is really the object that I care about
}
}
Everything above or at the same level as usefulJsonObject I could really do without. The useless data is returned for every request, and the actual response is embedded beneath as the usefulJsonObject. This wouldn't be a big problem but it's really cluttering up my gson model objects.
For example:
Let's say I have 3 requests I can make: A, B, and C. For each response it seems I need to make a minimum of 3 custom classes.
public class ResponseA {
#SerializedName("response") ResponseObjectA responseObject;
public static class ResponseObjectA {
#SerializedName("usefulJsonObject") UsefulObjectA usefulObject;
}
public static class UsefulObjectA {
}
}
I've tried a few solutions, but I haven't found anything elegant that wouldn't add an extra step to my process. I'm using retrofit to do my http requests and it's really nice that it just returns the fully parsed gson object to me. I've thought of other solutions like having the useful object just be a JsonElement and then doing a 2nd gson call after the first comes back. Again, not ideal.
I just wanted to know if I was missing something. Surely I'm not the only one who's encountered something like this, and so I thought I'd ask how other people would handle something like this.
It is initialization Instance value, not NULL value. Check my example.
Address.java
public class Address {
public Address(){
}
}
Person.java
public class Person {
private String name;
private String nrc;
private Address address;
public Person(String name, String nrc, Address address) {
this.name = name;
this.nrc = nrc;
this.address = address;
}
}
The following Json string is equalvent to
Person person = new Person("Zaw Than Oo", "11111", null);
{
"name": "Zaw Than Oo",
"nrc": "11111"
}
The following Json string is equalvent to
Person person = new Person("Zaw Than Oo", "11111", new Address());
{
"name": "Zaw Than Oo",
"nrc": "11111",
"address": {} <-- here use less object for you.
}
Even if you don't create new Instance, Other lib/api(you used) may be create that instance by Reflection.
Short to the Point
{
...
"xxx": {} --> new instance without data/value
...
}
{
...
--> null value
...
}
I never found an elegant way dealing with just Gson. I tried several options with Generics, all of which didn't work or left something to be desired.
Since I'm using Retrofit, I decided to override the GsonConverter, and just filter out the unnecessary information from all my requests. It ends up not being as flexible, as in I can't use the same Retrofit network interface for calls to other servers, but I'm not really doing that, and it also has the down side of having 2 rounds of json parsing calls (meh). You could probably do this more efficiently, but this is working for me for now.
public class CustomGsonConverter extends GsonConverter {
private Gson mGson;
public CustomGsonConverter(Gson gson) {
super(gson);
this.mGson = gson;
}
public CustomGsonConverter(Gson gson, String encoding) {
super(gson, encoding);
this.mGson = gson;
}
#Override public Object fromBody(TypedInput body, Type type) throws ConversionException {
try {
CustomResponse customResponse = mGson.fromJson(new InputStreamReader(body.in()), CustomResponse.class);
return mGson.fromJson(customResponse.responseObject.data, type);
} catch (IOException e) {
throw new ConversionException(e);
}
}
public static class CustomResponse {
#SerializedName("rsp") ResponseObject responseObject;
public static class ResponseObject {
// #SerializedName("date") long date;
#SerializedName("data") JsonElement data;
}
}
}
Maybe there is a better way that I'm just not realizing.