I wrote a Generic method which works fine converting any Json object to Generic Object.
public <T> T convertJsontoObject (String jsonObj, Class<T> any Type)
throws JsonMappingException, JsonProcessingException
{
ObjectMapper objectMap = new ObjectMapper();
return objectMap.readValue(jsonObj, any Type);
}
But has an issue while converting the Generic Object type to JSON format with the below code format. Would someone help me or guide me with related code.. I am not sure how to retain an Object from Generic class type as the method doesn't support for generic and I want to have a Generic method to perform the conversions....
public <T> String convertObjectToJson(Class<T> anyType)
{ String jsonStringObj = "";
try { jsonStringObj = new
ObjectMapper().writerWithDefaultPrettyPrinter().writeValueAsString(anyType);}
}
When you return a String you don't need to use generics and you can receive only an Object on methods
// you can treate try and catch inside them as you prefer
public static <T> T fromJson(String json, Class<T> classToReturn) throws Exception{
return new ObjectMapper().readValue(json, classToReturn);
}
public static String toJson(Object obj) throws Exception{
return new ObjectMapper().writeValueAsString(obj);
}
public static String toJsonPrettyNonNullTreatingDateTypes(Object obj) throws Exception{
return new ObjectMapper()
.setSerializationInclusion(Include.NON_NULL)
.setSerializationInclusion(Include.NON_EMPTY)
.registerModule(new JavaTimeModule() );
.writerWithDefaultPrettyPrinter()
.writeValueAsString(obj);
}
using them
MyObject obj = fromJson(jsonString, MyObject.class );
String json = toJson(new MyObject()); // Object can accept any types
String json = toJsonPrettyNonNullTreatingDateTypes(new MyObject());
Related
I have the following class
private static class ClassWithGenericType<T> {
Set<T> values;
}
If I initialize now the class with a Set of Enum-values, serialize and deserialize the object by using gson, the Set of the deserialized object does not contain the Enum-values, but the values as String.
I think this is because the generic type is thrown away through the serialization. I saw, that I could use new TypeToken<...>(){}.getType();, but the problem is, that the class above is part of a bigger object, so I cannot call gson.fromJson(classWithGenericType, typeToken) directly.
Is there a smart way of solving this problem? I thought of a TypeAdapter, which does not serialize only the values of the Set, but also it's type.
I found now a solution and created a TypeAdapter.
public class SetTypeAdapterFactory implements TypeAdapterFactory {
#Override
public <T> TypeAdapter<T> create(Gson gson, #NonNull TypeToken<T> type) {
if (!Set.class.isAssignableFrom(type.getRawType())) {
return null;
}
return (TypeAdapter<T>) new SetTypeAdapter(gson);
}
}
public class SetTypeAdapter extends TypeAdapter<Set<?>> {
public static final String TYPE = "#type";
public static final String DATA = "#data";
private final Gson gson;
public SetTypeAdapter(#NonNull Gson gson) {
this.gson = gson;
}
#Override
public void write(final JsonWriter out, final Set<?> set
) throws IOException {
out.beginArray();
for (Object item : set) {
out.beginObject();
out.name(TYPE).value(item.getClass().getName());
out.name(DATA).jsonValue(gson.toJson(item));
out.endObject();
}
out.endArray();
}
#Override
public Set<?> read(final JsonReader in) throws IOException {
final Set<Object> set = Sets.newHashSet();
in.beginArray();
while (in.hasNext()) {
in.beginObject();
set.add(readNextObject(in));
in.endObject();
}
in.endArray();
return set;
}
private Object readNextObject(JsonReader in) throws IOException {
try {
checkNextName(in, TYPE);
Class<?> cls = Class.forName(in.nextString());
checkNextName(in, DATA);
return gson.fromJson(in, cls);
} catch (ClassNotFoundException exception) {
throw new IOException(exception);
}
}
private void checkNextName(JsonReader in, String name) throws IOException {
if (!in.nextName().equals(name)) {
throw new IOException("Name was not: " + name);
}
}
}
We can add the factory to the GsonBuilder and afterwards we are capable of serializing a Set with generic types.
var gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapterFactory(new SetTypeAdapterFactory());
var gson = gsonBuilder.create();
The serialized Set has then the following structure:
[
{
"#type":<class_name_first_element>,
"#data":<first_element_as_json>
},
...
]
I have following code:
public static <T> T jsonToObject(String json, Class<T> object) {
ObjectMapper mapper = new ObjectMapper();
try {
return mapper.readerFor(object).readValue(json);
} catch (JsonProcessingException e) {
e.printStackTrace();
}
return null;
}
What can I do to not return null?
Caller will have to deal with this being null/missing anyway so may as well specify that your method throws a JsonProcessingException and do exception handling in the caller. Then the caller can do whatever it needs for the type it needs.
Otherwise you're just handling the same issue twice.
public static <T> T jsonToObject(String json, Class<T> object) throws JsonProcessingException {
ObjectMapper mapper = new ObjectMapper();
return mapper.readerFor(object).readValue(json);
}
Considering that:
ObjectReader readerFor(Class<?> type) - Factory method for
constructing ObjectReader that will read or update instances of
specified type
<T> T readValue(String content, TypeReference valueTypeRef) - Method
to deserialize JSON content from given JSON content String.
So the method is returning an instance of the generic type Class<T>, so you can use:
public static <T> T jsonToObject(String json, Class<T> object) {
ObjectMapper mapper = new ObjectMapper();
T instance=null;
try {
instance = mapper.readerFor(object).readValue(json);
} catch (JsonProcessingException e) {
e.printStackTrace();
}
if (instance == null) {
instance = ((Class) ((ParameterizedType) this.getClass().
getGenericSuperclass()).getActualTypeArguments()[0]).newInstance();
}
return instance;
}
Now you will get a new instance if the result is null.
I'm trying to generalise this method:
public EventStream<Greeting> deserialize(String value){
ObjectMapper mapper = new ObjectMapper();
EventStream<Greeting> data = null;
try {
data = new ObjectMapper().readValue(value, new TypeReference<EventStream<Greeting>>() {});
} catch (IOException e) {
e.printStackTrace();
}
return data;
}
where EventStream is:
public class EventStream<T> {
private EventHeaders headers;
#JsonDeserialize
private T payload;
}
What I'd like to have is replace the specific Object Greeting with a generic, in the deserialize method.
I tried with this:
public <T> EventStream<T> deserialize(String value){
ObjectMapper mapper = new ObjectMapper();
EventStream<T> data = null;
try {
data = new ObjectMapper().readValue(value, new TypeReference<EventStream<T>>() {});
} catch (IOException e) {
e.printStackTrace();
}
return data;
}
But the payload inside the EventStream result is deserialized as LinkedHashMap. It seems like TypeReference ignored the generic type.
Any idea?
Thanks
What you encountered here is a common problem caused by something called type erasure, the way java implements generics.
Type erasure can be explained as the process of enforcing type
constraints only at compile time and discarding the element type
information at runtime. [1]
So at the time you try to deserialize your object, the type T is not known and it is just treated as Object and the deserialization result will default default to Map (LinkedHashMap to be precise).
You could make your method generic by passing your targetClass as an additional argument to the function call like so:
public <T> EventStream<T> deserialize(String value, Class<T> targetClass)
Then you use the TypeFactory of your mapper to create a type of this targetClass
JavaType type = mapper.getTypeFactory().constructParametricType(EventStream.class, targetClass);
which you can pass to the readValue method:
data = mapper.readValue(value, type);
Complete code:
public <T> EventStream<T> deserialize(String value, Class<T> targetClass){
ObjectMapper mapper = new ObjectMapper();
JavaType type = mapper.getTypeFactory()
.constructParametricType(EventStream.class, targetClass);
try {
return mapper.readValue(value, type);
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
[1] https://www.baeldung.com/java-type-erasure
Does Gson have a way to read in non-standard JSON files?
Instead of a typical file like:
[{obj1},{objN}]
I have a file like this:
{obj1}
{objN}
Where there are no square brackets or commas and each object is separated by a newline character.
Yes, it has. Gson supports lenient reading. For example, the following JSON document (non-standard.json):
{
"foo": 1
}
{
"bar": 1
}
you can use the following reading way:
private static final Gson gson = new Gson();
private static final TypeAdapter<JsonElement> jsonElementTypeAdapter = gson.getAdapter(JsonElement.class);
public static void main(final String... args)
throws IOException {
try ( final Reader reader = getPackageResourceReader(Q43528208.class, "non-standard.json") ) {
final JsonReader jsonReader = new JsonReader(reader);
jsonReader.setLenient(true); // this makes it work
while ( jsonReader.peek() != END_DOCUMENT ) {
final JsonElement jsonElement = jsonElementTypeAdapter.read(jsonReader);
System.out.println(jsonElement);
}
}
}
Output:
{"foo":1}
{"bar":1}
I'm not sure if you can write a robust deserializer this way though.
Update
In order to simplify the Gson support, we can implement a few convenient reading methods:
// A shortcut method for the below implementation: aggregates the whole result into a single list
private static <T> List<T> parseToListLenient(final JsonReader jsonReader, final IMapper<? super JsonReader, ? extends T> mapper)
throws IOException {
final List<T> list = new ArrayList<>();
parseLenient(jsonReader, in -> list.add(mapper.map(in)));
return list;
}
// A convenient strategy-accepting method to configure a JsonReader instance to make it lenient and do read
// The consumer defines the strategy what to do with the current JsonReader token
private static void parseLenient(final JsonReader jsonReader, final IConsumer<? super JsonReader> consumer)
throws IOException {
final boolean isLenient = jsonReader.isLenient();
try {
jsonReader.setLenient(true);
while ( jsonReader.peek() != END_DOCUMENT ) {
consumer.accept(jsonReader);
}
} finally {
jsonReader.setLenient(isLenient);
}
}
// Since Java 8 Consumer inteface does not allow checked exceptions to be rethrown
private interface IConsumer<T> {
void accept(T value)
throws IOException;
}
private interface IMapper<T, R> {
R map(T value)
throws IOException;
}
Then simple reading is really simple, and we can just use the methods above:
final Gson gson = new Gson();
final TypeToken<Map<String, Integer>> typeToken = new TypeToken<Map<String, Integer>>() {
};
final TypeAdapter<Map<String, Integer>> typeAdapter = gson.getAdapter(typeToken);
try ( final JsonReader jsonReader = getPackageResourceJsonReader(Q43528208.class, "non-standard.json") ) {
final List<Map<String, Integer>> maps = parseToListLenient(jsonReader, typeAdapter::read);
System.out.println(maps);
}
Deserialization via Gson directly would require more complicated implementation:
// This is just a marker not meant to be instantiated but to create a sort of "gateway" to dispatch types in Gson
#SuppressWarnings("unused")
private static final class LenientListMarker<T> {
private LenientListMarker() {
throw new AssertionError("must not be instantiated");
}
}
private static void doDeserialize()
throws IOException {
final Gson gson = new GsonBuilder()
.registerTypeAdapterFactory(new TypeAdapterFactory() {
#Override
public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> typeToken) {
// Check if the given type is the lenient list marker class
if ( !LenientListMarker.class.isAssignableFrom(typeToken.getRawType()) ) {
// Not the case? Just delegate the job to Gson
return null;
}
final Type listElementType = getTypeParameter0(typeToken.getType());
final TypeAdapter<?> listElementAdapter = gson.getAdapter(TypeToken.get(listElementType));
#SuppressWarnings("unchecked")
final TypeToken<List<?>> listTypeToken = (TypeToken<List<?>>) TypeToken.getParameterized(List.class, listElementType);
final TypeAdapter<List<?>> listAdapter = gson.getAdapter(listTypeToken);
final TypeAdapter<List<?>> typeAdapter = new TypeAdapter<List<?>>() {
#Override
public void write(final JsonWriter out, final List<?> value)
throws IOException {
// Always write a well-formed list
listAdapter.write(out, value);
}
#Override
public List<?> read(final JsonReader in)
throws IOException {
// Delegate the job to the reading method - we only have to tell how to obtain the list values
return parseToListLenient(in, listElementAdapter::read);
}
};
#SuppressWarnings("unchecked")
final TypeAdapter<T> castTypeAdapter = (TypeAdapter<T>) typeAdapter;
return castTypeAdapter;
}
// A simple method to resolve actual type parameter
private Type getTypeParameter0(final Type type) {
if ( !(type instanceof ParameterizedType) ) {
// List or List<?>
return Object.class;
}
return ((ParameterizedType) type).getActualTypeArguments()[0];
}
})
.create();
// This type declares a marker specialization to be used during deserialization
final Type type = new TypeToken<LenientListMarker<Map<String, Integer>>>() {
}.getType();
try ( final JsonReader jsonReader = getPackageResourceJsonReader(Q43528208.class, "non-standard.json") ) {
// This is where we're a sort of cheating:
// We tell Gson to deserialize LenientListMarker<Map<String, Integer>> but the type adapter above will return a list
final List<Map<String, Integer>> maps = gson.fromJson(jsonReader, type);
System.out.println(maps);
}
}
The output is now for Map<String, Integer>s, not JsonElements:
[{foo=1}, {bar=1}]
Update 2
TypeToken.getParameterized workaround:
#SuppressWarnings("unchecked")
final TypeToken<List<?>> listTypeToken = (TypeToken<List<?>>) TypeToken.get(new ParameterizedType() {
#Override
public Type getRawType() {
return List.class;
}
#Override
public Type[] getActualTypeArguments() {
return new Type[]{ listElementType };
}
#Override
public Type getOwnerType() {
return null;
}
});
We can have one more program to introduce comma(,) and construct a well formed JSON
With spark 2, we can add multiline as read option.
spark.df.option("multiline","true").json("data.json")
When using an ObjectMapper to transform a json String into an entity, I can make it generic as:
public <E> E getConvertedAs(String body, Class<E> type) throws IOException {
return mapper.readValue(body, type);
}
Now let's say I want to read collections. I can do:
List<SomeEntity> someEntityList = asList(mapper.readValue(body, SomeEntity[].class));
List<SomeOtherEntity> someOtherEntityList = asList(mapper.readValue(body, SomeOtherEntity[].class));
I would like to write an equivalent method of the above, but for collections. Since you can't have generic arrays in java, something like this won't work:
public <E> List<E> getConvertedListAs(String body, Class<E> type) {
return mapper.readValue(body, type[].class);
}
Here there is a solution that almost works:
mapper.readValue(jsonString, new TypeReference<List<EntryType>>() {});
The problem is that it doesn't deserialize into a list of E, but of LinkedHashMap.Entry. Is there any way of going going a step further, something like the following?
public <E> List<E> getConvertedListAs(String body, Class<E> type) {
mapper.readValue(body, new TypeReference<List<type>>() {}); // Doesn't compile
}
This method can help to read json to an object or collections:
public class JsonUtil {
private static final ObjectMapper mapper = new ObjectMapper();
public static <T>T toObject(String json, TypeReference<T> typeRef){
T t = null;
try {
t = mapper.readValue(json, typeRef);
} catch (IOException e) {
e.printStackTrace();
}
return t;
}
}
Read json to list:
List<Device> devices= JsonUtil.toObject(jsonString,
new TypeReference<List<Device>>() {});
Read json to object:
Device device= JsonUtil.toObject(jsonString,
new TypeReference<Device>() {});
public static <E> List<E> fromJson(String in_string, Class<E> in_type) throws JsonParseException, JsonMappingException, IOException{
return new ObjectMapper().readValue(in_string, new TypeReference<List<E>>() {});
}
Compiles on my computer.
Note that I haven't tested it, though.