I want to serialize a java map into json and I can definitely use either jackson or gson.
But when I serialize, I want to ignore specify key. Is it possible?
Map is . I don't have/want my Map backed by a POJO because the keys are very generic and could be anything. I understand if it was a POJO, we can use Ignore annotation to achieve.
You can create a custom class that extends HashMap:
public class MyMap extends HashMap<String, Object> {
}
and then register a custom serializer, like:
public class MyMapSerializer extends JsonSerializer<MyMap> {
#Override
public JsonElement serialize(MyMap arg0, Type arg1, JsonSerializationContext arg2) {
JsonObject result = new JsonObject();
for (String k : arg0.keySet()) {
if (whatever your condition is) {
result.add(k, arg0.get(k));
}
}
return result;
}
}
Then when you create the Gson object you need to initialize it by passing an instance of the serializer:
Gson gson = new GsonBuilder().registerTypeAdapter(MyMap.class, new MyMapSerializer()).create();
Related
I need to deserialize a Json file that has an array. I know how to deserialize it so that I get a List object, but in the framework I am using a custom list object that does not implement the Java List interface. My question is, how do I write a deserializer for my custom list object?
EDIT: I want the deserializer to be universal, meaning that I want it ot work for every kind of list, like CustomList<Integer>, CustomList<String>, CustomList<CustomModel> not just a specific kind of list since it would be annoying to make deserializer for every kind I use.
This is what I came up with:
class CustomListConverter implements JsonDeserializer<CustomList<?>> {
public CustomList deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext ctx) {
Type valueType = ((ParameterizedType) typeOfT).getActualTypeArguments()[0];
CustomList<Object> list = new CustomList<Object>();
for (JsonElement item : json.getAsJsonArray()) {
list.add(ctx.deserialize(item, valueType));
}
return list;
}
}
Register it like this:
Gson gson = new GsonBuilder()
.registerTypeAdapter(CustomList.class, new CustomListConverter())
.create();
I've searched a lot and only find questions about polymorphic deserialization on the content inside a map. Is it possible to polymorphic deserializing the map itself?
For example, I have a Book class contains a Map as a member variable.
public class Book {
#JsonProperty
private Map<String, Object> reviews;
#JsonCreator
public Book(Map<String, Object> map) {
this.reviews = map;
}
}
Another class have a list of Book class.
public class Shelf {
#JsonProperty
private List<Book> books = new LinkedList<>();
public void setBooks(List<Book> books) {
this.books = books;
}
public List<Book> getBooks() {
return this.books;
}
}
And a test class. One book's review map is a Hashtable and another book's review map is a HashMap.
public class Test {
private Shelf shelf;
#BeforeClass
public void init() {
Map<String, Object> review1 = new Hashtable<>(); // Hashtable here
review1.put("test1", "review1");
Map<String, Object> review2 = new HashMap<>(); // HashMap here
review2.put("test2", "review2");
List<Book> books = new LinkedList<>();
books.add(new Book(review1));
books.add(new Book(review2));
shelf = new Shelf();
shelf.setBooks(books);
}
#Test
public void test() throws IOException{
ObjectMapper mapper = new ObjectMapper();
mapper.configure(SerializationFeature.INDENT_OUTPUT, true);
// mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
String json = mapper.writeValueAsString(shelf);
System.out.println(json);
Shelf sh = mapper.readValue(json, Shelf.class);
for (Book b : sh.getBooks()) {
System.out.println(b.getReviews().getClass());
}
}
}
The test output
{
"name" : "TestShelf",
"books" : [ {
"reviews" : {
"test1" : "review1"
}
}, {
"reviews" : {
"test2" : "review2"
}
} ]
}
class java.util.LinkedHashMap
class java.util.LinkedHashMap
The serialization works fine. But after deserialization, both review1 and review2 are LinkedHashMap. I want review1 and review2 to be their actual types which are Hashtable to review1 and HashMap to review2. Is there any way to achieve this?
I don't want to use mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); because it will add the type info for all json properties in the json message. And if there is any better way to do it I don't want to use customized deserializer either. Thanks in advance.
I posted the question on Jackson user forum and they suggest to customized the TypeResolverBuilder and set it in the ObjectMapper instance.
ObjectMapper.setDefaultTyping(...)
My customized TypeResolverBuilder is below and it solved my problem.
public class MapTypeIdResolverBuilder extends StdTypeResolverBuilder {
public MapTypeIdResolverBuilder() {
}
#Override
public TypeDeserializer buildTypeDeserializer(DeserializationConfig config,
JavaType baseType, Collection<NamedType> subtypes) {
return useForType(baseType) ? super.buildTypeDeserializer(config, baseType, subtypes) : null;
}
#Override
public TypeSerializer buildTypeSerializer(SerializationConfig config,
JavaType baseType, Collection<namedtype> subtypes) {
return useForType(baseType) ? super.buildTypeSerializer(config, baseType, subtypes) : null;
}
/**
* Method called to check if the default type handler should be
* used for given type.
* Note: "natural types" (String, Boolean, Integer, Double) will never
* use typing; that is both due to them being concrete and final,
* and since actual serializers and deserializers will also ignore any
* attempts to enforce typing.
*/
public boolean useForType(JavaType t) {
return t.isMapLikeType() || t.isJavaLangObject();
}
}
This solution requires both server side and client side to use the customized TypeResolverBuilder. I know it is not ideal, but it is the best solution I found so far. The details of the solution can be found in this post on my blog.
The readValue call has no idea where the input JSON came from. It doesn't know that it was generated from a Hashtable or a HashMap or a TreeMap or any other type of Map. All it has to work with is the target type, Shelf, and its nested Book. The only thing Jackson can introspect from Book is that it has a field of type Map.
Map is an interface. Since you can't instantiate an interface, Jackson has to make a decision on the implementation type of Map that it wants to use. By default, it uses LinkedHashMap. You can change the default by following the solution posted here.
An alternative is to declare the field with the concrete type you want
private HashMap<String, Object> reviews;
Now Jackson knows to deserialize the JSON into a HashMap. Obviously, this will only work with a single type.
The actual solution is for you not to care about the actual implementation class. You decided it was going to be a Map. You shouldn't care what implementation it uses under the covers. Use the power of polymorphism.
(Note that the use of Hashtable has long been discouraged.)
I need to convert a java object (called org) to json format.
The object (DTO ) is a bit complex, because it contains a list of objects of the same class and which in turn can also contain more objects of the same class ( built recursively). When I passing the object to gson.toJsonTree method it seems to fail (there isnt any error), but it seems that the method does not like complex objects). If I set to null the list of objects of the first object everything works fine. I can not modify the class, only the method that makes json.
JsonElement jsonUO = null;
jsonUO = gson.toJsonTree(org,OrgDTO.class);
jsonObject.add("ORG", jsonUO)
public class OrgDTO implements Serializable{
private String id;
......
private List sucesores;
public OrgDTO(){
this.sucesores = new ArrayList();
}
.....
}
It might be a little bit late for the questioner, however I share my answer in case someone else face similar issue:
You'll need to create a helper class that does the json serialization. It should implement the JsonDeserializer:
public class OrgDTOJsonSerializer implements JsonDeserializer<OrgDTO> {
#Override
public JsonElement serialize(OrgDTO src, Type type, JsonSerializationContext jsc) {
JsonObject jsonObject = new JsonObject();
jsonObject.addProperty("id", src.getId());
/// Build the array of sucesores (whatever it means!)
JsonArray sucesoresArray = new JsonArray();
for (final OrgDTO obj: src.getSucesores()) {
JsonObject succJsonObj = serialize(obj, type, jsc);
sucesoresArray.add(succJsonObj);
}
jsonObject.add("sucesores", sucesoresArray);
return jsonObject;
}
}
Then you'll need to register it in gson before attempting to serialize any object of that type:
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapter(OrgDTO.class, new OrgDTOJsonSerializer());
Is it possible to deserialize JSON using Jackson into one of two types based on the content of the JSON?
For example, I have the following Java (technically Groovy, but that's not important) interfaces and classes:
interface Id {
Thing toThing()
}
class NaturalId implements Id {
final String packageId
final String thingId
Thing toThing() {
new PackageIdentifiedThing(packageId, thingId)
}
}
class AlternateId implements Id {
final String key
Thing toThing() {
new AlternatelyIdentifiedThing(key)
}
}
The JSON I will receive will look like either of the following:
This JSON should map to NaturalId {"packageId": "SomePackage", "thingId": "SomeEntity"}
This JSON should map to AlternateId {"key": "SomeUniqueKey"}
Does anyone know how I can accomplish this with Jackson 2.x WITHOUT including type id's?
Are these the only two classes that implement Id? If so, you could write an IdDeserializer class and put #JsonDeserialize(using = IdDeserializer.class) on the Id interface, and the deserializer would look at the JSON and determine which object to deserialize into.
EDIT: The JsonParser is streaming so it should look something like this:
public Id deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException {
ObjectNode node = jp.readValueAsTree();
Class<? extends Id> concreteType = determineConcreteType(node); //Implement
return jp.getCodec().treeToValue(node, concreteType);
}
Annotate your methods with #JsonIgnore
#JsonIgnore
Thing toThing() {
new PackageIdentifiedThing(packageId, thingId)
}
With Jackson2, you can easily marshall to different classes using generics:
private <T> T json2Object(String jsonString, String type, Class<T> clazz) {
JsonNode jsonObjectNode = getChildNode(jsonString, type);
T typeObject = null;
try {
typeObject = jacksonMapper.treeToValue(jsonObjectNode, clazz);
} catch (JsonProcessingException jsonProcessingException) {
LOGGER.severe(jsonProcessingException);
}
return typeObject;
}
Using Jersey I'm defining a service like:
#Path("/studentIds")
public void writeList(JsonArray<Long> studentIds){
//iterate over studentIds and save them
}
Where JsonArray is:
public class JsonArray<T> extends ArrayList<T> {
public JsonArray(String v) throws IOException {
ObjectMapper objectMapper = new ObjectMapper(new MappingJsonFactory());
TypeReference<ArrayList<T>> typeRef = new TypeReference<ArrayList<T>>() {};
ArrayList<T> list = objectMapper.readValue(v, typeRef);
for (T x : list) {
this.add((T) x);
}
}
}
This works just fine, but when I do something more complicated:
#Path("/studentIds")
public void writeList(JsonArray<TypeIdentifier> studentIds){
//iterate over studentIds and save them by type
}
Where the Bean is a simple POJO such as
public class TypeIdentifier {
private String type;
private Long id;
//getters/setters
}
The whole thing breaks horribly. It converts everything to LinkedHashMap instead of the actual object. I can get it to work if I manually create a class like:
public class JsonArrayTypeIdentifier extends ArrayList<TypeIdentifier> {
public JsonArrayTypeIdentifier(String v) throws IOException {
ObjectMapper objectMapper = new ObjectMapper(new MappingJsonFactory());
TypeReference<ArrayList<TypeIdentifier>> typeRef = new TypeReference<ArrayList<TypeIdentifier>>(){};
ArrayList<TypeIdentifier> list = objectMapper.readValue(v, typeRef);
for(TypeIdentifier x : list){
this.add((TypeIdentifier) x);
}
}
}
But I'm trying to keep this nice and generic without adding extra classes all over. Any leads on why this is happening with the generic version only?
First of all, it works with Longs because that is sort of native type, and as such default binding for JSON integral numbers.
But as to why generic type information is not properly passed: this is most likely due to problems with the way JAX-RS API passes type to MessageBodyReaders and MessageBodyWriters -- passing java.lang.reflect.Type is not (unfortunately!) enough to pass actual generic declarations (for more info on this, read this blog entry).
One easy work-around is to create helper types like:
class MyTypeIdentifierArray extends JsonArray<TypeIdentifier> { }
and use that type -- things will "just work", since super-type generic information is always retained.