JSON: use deserializing name different than serializing name json - java

I have one class User, I received JSON (for User class) from system1 and I should read info , validate then forward to system2, I can't touch these 2 systems, the problem is the names of keys are different, I want to differentiate between deserialized and serialized name
received JSON is :
{"userId":"user1","pwd":"123456","country":"US"}
"{"username":"user1","password":"123456","country":"US"}"
But the sent should be like this
I am using Gson lib, and this is my code:
User class:
class User implements Cloneable {
#SerializedName("username")
private String username ;
#SerializedName("password")
private String password ;
#SerializedName("country")
private String country ;
}
TestJson class
class TestJson {
private static GsonBuilder gsonBuilder;
private static Gson gson;
public static Object fromJson(String json, Class clz) {
gson = new Gson();
return gson.fromJson(json, clz);
}
public static String toJson(Object obj) {
gsonBuilder = new GsonBuilder();
gson = gsonBuilder.create();
String json = gson.toJson(obj);
return json;
}
public static void main(String[] args) {
String json2 = "{\"userId\":\"user1\",\"pwd\":\"123456\",\"country\":\"US\"}";
User user = (User) TestJson.fromJson(json2, User.class);
System.out.println(user.getPassword());
User u = new User("user1","123456","US");
String json1 = TestJson.toJson(u);
System.out.println(json1);
}
}

If there are alternative names of field just use alternate param of #SerializedName
public class User {
#SerializedName(value="username", alternate={"userId", "useriD"})
private String username ;
...
}

You can create custom serializer/deserializer for this purpose.
Serializer:
public class UserSerializer implements JsonSerializer<User> {
#Override public JsonElement serialize(User obj, Type type, JsonSerializationContext jsonSerializationContext) {
..........
}
}
Deserializer:
public class UserDeserializer implements JsonDeserializer<User> {
#Override public User deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
...........
}
}
and to create Gson instance:
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapter(User.class, new UserSerializer());
gsonBuilder.registerTypeAdapter(User.class, new UserDeserializer());
Gson gson = gsonBuilder.create();
Example
Edit: this is an example of a custom deserializer which might fit into your need. We don't need a custom serializer in this case.
Add this UserDeserializer.java:
public class UserDeserializer implements JsonDeserializer<User> {
#Override
public User deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
JsonObject obj = json.getAsJsonObject();
User user = new User(obj.get("userId").getAsString(), obj.get("pwd").getAsString(), obj.get("country").getAsString());
return user;
}
}
Replace your fromJson implementation with this (I use generic to avoid the need for casting when calling fromJson):
public static <T> T fromJson(String json, Class<T> clz) {
gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapter(User.class, new UserDeserializer());
gson = gsonBuilder.create();
return gson.fromJson(json, clz);
}

The only way I can think of would be to have a custom Adapter or deser to a JsonObject and then map it to your User.
With Genson you can create two instances of Genson, one for deserialization and another one for serializaiton. The one used in deserialization could be configured with renamed properties like that.
// you can also precise that you want to rename only the properties from User class
Genson genson = new GensonBuilder()
.rename("username", "userId")
.rename("password", "pwd")
.create();
User user = genson.deserialize(json, User.class);

Related

How to have a single GSON custom serializer to apply to all subclasses?

I'm using GSON to apply a universal serializer to all subclasses of an abstract Base class. However, GSON will not call my serializer when given actual subclasses of the Base class unless explicitly told to use Base.class as a cast. Here's a simple instance of what I'm talking about.
public interface Base<T>{
String getName();
public List<Object> getChildren();
}
public class Derived1 implements Base<Integer>{
private Integer x = 5;
String getName(){
return "Name: " + x;
}
List<Object> getChildren(){
return Lists.newArrayList(new Derived2(), "Some string");
}
}
public class Derived2 implements Base<Double>{
private Double x = 6.3;
String getName(){
return "Name: " + x;
}
List<Object> getChildren(){
return new List<>();
}
}
I'm creating a serializer as follows:
JsonSerializer customAdapter = new JsonSerializer<Base>(){
#Override
JsonElement serialize(Base base, Type sourceType, JsonSerializationContext context){
JsonObject jsonObject = new JsonObject();
jsonObject.addProperty("name", base.getName());
JsonArray jsonArray = new JsonArray();
for (Object child : base.getChildren()){
jsonArray.add(context.serialize(child));
}
if (jsonArray.size() != 0){
jsonObject.add("children", jsonArray);
}
}
};
Gson customSerializer = new GsonBuilder()
.registerTypeAdapter(Base.class, customAdapter)
.create();
However, applying my custom serializer to a List of subclasses does not have the desired effect.
customSerializer.toJson(Lists.newArrayList(new Derived1(), new Derived2()));
This applies the default GSON serialization to my subclasses. Is there any easy way to get my custom serializer to use my custom adapter on all subclasses of the parent class? I suspect that one solution is to use reflection to iterate over all subclasses of Base and register the custom adapter, but I'd like to avoid something like that if possible.
Note: I don't care about deserialization right now.
Maybe you should not use JsonSerializer. Namely, this is possible if you use TypeAdapter doing the same magic by registering TypeAdapterFactory that tells Gson how to serialize any class.
See below TypeAdapterFactory and TypeAdapter in it:
public class CustomAdapterFactory implements TypeAdapterFactory {
#SuppressWarnings("unchecked")
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
// If the class that type token represents is a subclass of Base
// then return your special adapter
if(Base.class.isAssignableFrom(typeToken.getRawType())) {
return (TypeAdapter<T>) customTypeAdapter;
}
return null;
}
private TypeAdapter<Base<?>> customTypeAdapter = new TypeAdapter<Base<?>>() {
#Override
public void write(JsonWriter out, Base<?> value) throws IOException {
out.beginObject();
out.value(value.getName());
out.endObject();
}
#Override
public Base<?> read(JsonReader in) throws IOException {
// Deserializing to subclasses not interesting yet.
// Actually it is impossible if the JSON does not contain
// information about the subclass to which to deserialize
return null;
}
};
}
If you do something like this:
#Slf4j
public class SubClassTest {
#Test
public void testIt() {
Gson gson = new GsonBuilder()
.setPrettyPrinting()
.registerTypeAdapterFactory(new CustomAdapterFactory())
.create();
log.info("\n{}", gson.toJson(new Derived1()));
log.info("\n{}", gson.toJson(new Derived2()));
}
}
the output will be like this:
2018-10-12 23:13:17.037 INFO
org.example.gson.subclass.SubClassTest:19 - { "name": "Name: 5" }
2018-10-12 23:13:17.043 INFO
org.example.gson.subclass.SubClassTest:20 - { "name": "Name: 6.3"
}
If it is not exactly what you want just fix the write(..) method in the customTypeAdapter.

How to create a Gson deserilization that handle all String in one way except one field?

I have created a deserialization, that whenever it see the String "nil", it will return null.
private static Gson createCustomGson() {
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapter(String.class, new JsonDeserializer<String>() {
#Override
public String deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) {
return (json.getAsString().equals("nil")) ? null : json.getAsString();
}
});
return gsonBuilder.create();
}
It works good except that, I want to add an exception where for the field "Keyword" that store a List, I don't want to eliminate nil to return null, but retain the String. How to add the exception for "Keyword"?
My Keyword class is of the below type
public class KeywordListing implements Serializable {
List<String> keys;
}
Found a solution. It's by adding another TypeAdapter to use back the default deserizliation from Gson instead as below.
private static Gson createCustomGson() {
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapter(String.class, new JsonDeserializer<String>() {
#Override
public String deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) {
return (json.getAsString().equals("nil")) ? null : json.getAsString();
}
});
gsonBuilder.registerTypeAdapter(KeywordListing.class, new JsonDeserializer< KeywordListing >() {
#Override
public KeywordListing deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) {
return new Gson().fromJson(json, KeywordListing.class);
}
});
return gsonBuilder.create();
}
Let me know if there's a shorter answer than what I got above.

How to use Gson instance in Serializer/Deserializer class?

For example
public class HistoryRecordDeserializer implements JsonDeserializer<HistoryRecord> {
private LocalDateTimeConverter dateTimeConverter = new LocalDateTimeConverter();
#Override
public HistoryRecord deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
User user = new User();
user.setId(UUUID.fromString(json.get("user").get("id").getAsString()));
OtherData data = new OtherData();
data.setData(json.get("otherData").getAsLong());
return UserAndData(user, otherData);
}
As you can see, I instantiate User and OtherData manually, but I think there is a better solution. What is the best way to deserialize user with fromJson(...)? Should I pass Gson instance to HistoryRecordDeserializer? Should I create new one?
My problem was solved by using JsonDeserializationContext.
#Override
public HistoryRecord deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
JsonObject object = json.getAsJsonObject();
JsonObject extras = object.get("extraData").getAsJsonObject();
HistoryRecord hr = object.context.deserialize(object.get("data"), HistoryRecord.class);
hr.appendExtraData(extras, HistoryRecordExtraData.class);
...
}
As #varren sad:
If you Gson can deserialize this, then context will be also able to do this.
So, you can even apply another custom type adapter(LocalDateTimeConverter):
gson = new GsonBuilder()
.registerTypeAdapter(LocalDateTime.class, new LocalDateTimeConverter())
.registerTypeHierarchyAdapter(HistoryRecord.class, new HistoryRecordDeserializer())
.create();
and use it inside HistoryRecordDeserializer:
LocalDateTime localDateTime = context.deserialize(object.get("dateTime"), LocalDateTime.class);

Parsing single json entry to multiple objects with Gson

I have a json response that looks like this:
{
"id":"001",
"name":"Name",
"param_distance":"10",
"param_sampling":"2"
}
And I have two classes: Teste and Parameters
public class Test {
private int id;
private String name;
private Parameters params;
}
public class Parameters {
private double distance;
private int sampling;
}
My question is: is there a way to make Gson understand that some of the json attributes should go to the Parameters class, or the only way is to "manually" parse this ?
EDIT
Well, just to make my comment in #MikO's answer more readable:
I'll add a list of an object to the json output, so json response should look like this:
{
"id":"001",
"name":"Name",
"param_distance":"10",
"param_sampling":"2",
"events":[
{
"id":"01",
"value":"22.5"
},
{
"id":"02",
"value":"31.0"
}
]
}
And the Deserializer class would look like this:
public class TestDeserializer implements JsonDeserializer<Test> {
#Override
public Test deserialize(JsonElement json, Type type,
JsonDeserializationContext context) throws JsonParseException {
JsonObject obj = json.getAsJsonObject();
Test test = new Test();
test.setId(obj.get("id").getAsInt());
test.setName(obj.get("name").getAsString());
Parameters params = new Parameters();
params.setDistance(obj.get("param_distance").getAsDouble());
params.setSampling(obj.get("param_sampling").getAsInt());
test.setParameters(params);
Gson eventGson = new Gson();
Type eventsType = new TypeToken<List<Event>>(){}.getType();
List<Event> eventList = eventGson.fromJson(obj.get("events"), eventsType);
test.setEvents(eventList);
return test;
}
}
And doing:
GsonBuilder gBuilder = new GsonBuilder();
gBuilder.registerTypeAdapter(Test.class, new TestDeserializer());
Gson gson = gBuilder.create();
Test test = gson.fromJson(reader, Test.class);
Gives me the test object the way I wanted.
The way to make Gson understand it is to write a custom deserializer by creating a TypeAdapter for your Test class. You can find information in Gson's User Guide. It is not exactly a manual parsing, but it is not that different, since you have to tell Gson how to deal with each JSON value...
It should be something like this:
private class TestDeserializer implements JsonDeserializer<Test> {
public Test deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
throws JsonParseException {
JsonObject obj = json.getAsJsonObject();
int id = obj.get("id").getAsInt();
String name = obj.get("name").getAsString();
double distance = obj.get("param_distance").getAsDouble();
int sampling = obj.get("param_sampling").getAsInt();
//assuming you have suitable constructors...
Test test = new Test(id, name, new Parameters(distance, sampling));
return test;
}
}
Then you have to register the TypeAdapter with:
GsonBuilder gson = new GsonBuilder();
gson.registerTypeAdapter(Test.class, new TestDeserializer());
And finally you just have to parse your JSON as usual, with:
gson.fromJson(yourJsonString, Test.class);
Gson will automatically use your custom deserializer to parse your JSON into your Test class.

Gson exclusion strategy and custom serialization

I am using Gson custom serialization on my persistent object. At the same time, I am also using serialization exclusion strategy. The code is as shown below :
public class GsonFactory {
public static Gson build(Type type, final List<String> fieldExclusions,
final List<Class<?>> classExclusions) {
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.addSerializationExclusionStrategy(new ExclusionStrategy() {
public boolean shouldSkipField(FieldAttributes f) {
return fieldExclusions == null ? false : fieldExclusions
.contains(f.getName());
}
public boolean shouldSkipClass(Class<?> clazz) {
return classExclusions == null ? false : classExclusions
.contains(clazz);
}
});
// Uncommenting this line will produce error
// gsonBuilder.registerTypeAdapter(type,
// new PersistentObjectJsonSerializer());
return gsonBuilder.create();
}
}
class PersistentObjectJsonSerializer implements
JsonSerializer<PersistentObject> {
public JsonElement serialize(PersistentObject src, Type typeOfSrc,
JsonSerializationContext context) {
src.setDT_RowId(src.getId());
Gson gson = new Gson();
return gson.toJsonTree(src);
}
}
However if I uncomment the gsonBuilder.registerTypeAdapter(type, new PersistentObjectJsonSerializer());, upon creation of gsonBuilder will give the following error:
java.lang.StackOverflowError
com.google.gson.reflect.TypeToken.equals(TypeToken.java:284)
java.util.HashMap.get(HashMap.java:305)
java.util.Collections$SynchronizedMap.get(Collections.java:1979)
com.google.gson.Gson.getAdapter(Gson.java:337)
com.google.gson.internal.bind.TypeAdapterRuntimeTypeWrapper.write(TypeAdapterRuntimeTypeWrapper.java:55)
com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$1.write(ReflectiveTypeAdapterFactory.java:89)
com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.write(ReflectiveTypeAdapterFactory.java:195)
com.google.gson.Gson$FutureTypeAdapter.write(Gson.java:883)
My PersistentObject java class:
#MappedSuperclass
public abstract class PersistentObject implements Serializable {
....
#Transient
protected long DT_RowId;
// getters, setters not shown
...
}
This is how I call the GsonFactory.build in GenericHibernateDAO:
public abstract class GenericHibernateDAO<T, ID extends Serializable> {
private Class<T> persistentClass;
#SuppressWarnings("unchecked")
public GenericHibernateDAO() {
this.persistentClass = (Class<T>) ((ParameterizedType) getClass()
.getGenericSuperclass()).getActualTypeArguments()[0];
}
public String listAsJsonResponse(){
...
// testing start
List<Class<?>> classExclusions = new ArrayList<Class<?>>();
classExclusions.add(Role.class);
List<String> fieldExclusions = new ArrayList<String>();
fieldExclusions.add("users");
fieldExclusions.add("password");
Gson gson = GsonFactory.build(persistentClass,fieldExclusions, null);
...
return jsonResponse.toString();
}
}
The persistentClass here refers to User class:
#Repository
public class UserDAO extends GenericHibernateDAO<User, Long> {
}
Basically I can't manage to use both function at the same time. Any pointer on what might cause this issue ?
It's an old question, but maybe my answer could still help someone.
You create a new Gson object in your serializer. This Gson object doesn't know your ExclusionStrategies for the PersistentObjects anymore. If your PersistantObject has attribute objects which will lead to an PersistentObject again (in your case the users might have attributes of persistantObject), than you will run into an endless loop and into the stackoverflow exception.
One solution is to add exclusion strategies again to the gson object in your serializer. But don't add the serializer to this gson object again!
class PersistentObjectJsonSerializer implements
JsonSerializer<PersistentObject> {
public JsonElement serialize(PersistentObject src, Type typeOfSrc,
JsonSerializationContext context) {
src.setDT_RowId(src.getId());
Gson gson = new Gson();
//add exclusion strategy here again
return gson.toJsonTree(src);
}
}
Gson is open source and you can get the source code. Why dont you do that and follow the code to see in which point it throws the exception? It seems like gson falls in an infinite loop. It s true that gson uses recursion a lot. If it is actually a bug you can report it to the Gson guys and they will solve it in the next release.
http://code.google.com/p/google-gson/issues/list

Categories