I have a class having trivial string typed fields and one map only:
class MyClass {
#SerializedName("handle");
String nickName;
Map randomDetails;
}
My requirement is to create a map of fieldName to fieldValue (Map) but the fieldNames should be the same as #SerializedName rather than Myclass's field name. I realize that for a complex type like MyClass I may have to do some low-level deserialization myself. Has anyone come across this?
If you use a library, you shouldn't need to do any low-level work.
I haven't used it (yet) but Jackson looks like it'll do what you need.
It would be especially easy if you're not required to use that #SerializedName annotation, as Jackson provides a suite of its own annotations which do exactly what you need - (see the #JsonProperty annotation).
If you use the Jackson Tree Model mode of operation, you should get something like the map-based results you're looking for.
(I think I understand that the question concerns how to use Gson to deserialize a JSON map structure to a Java Map.)
Gson currently needs a little bit more type information about the Map than the Java class structure in the original question provides. Instead of declaring that randomDetails is a plain old Map, let Gson know that it's a Map<String, String>. Then, the following example JSON and simple deserialization code runs as expected.
input.json Contents:
{
"handle":"the handle",
"random_details":{"one":1,"too":"B","3":false,"for":5.32}
}
Foo.java:
import java.io.FileReader;
import java.util.Map;
import com.google.gson.FieldNamingPolicy;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.annotations.SerializedName;
public class Foo
{
public static void main(String[] args) throws Exception
{
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES);
Gson gson = gsonBuilder.create();
MyClass myObject = gson.fromJson(new FileReader("input.json"), MyClass.class);
System.out.println(gson.toJson(myObject));
}
}
class MyClass
{
#SerializedName("handle")
String nickName;
Map<String, String> randomDetails;
}
Note that this converts all values in the Map into Strings. If you wanted something more generic, like a Map<String, Object>, or if randomDetails must be a plain old Map without additional type information, then it's necessary to implement custom deserialization processing, as described in the user guide. (This is a situation where Gson unfortunately does not currently automatically generate Java values of String or primitive type from JSON primitives, if the declared Java type is simply Object. Thus it's necessary to implement the custom deserialization.)
Here's one such example.
import java.io.FileReader;
import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import com.google.gson.FieldNamingPolicy;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.google.gson.JsonPrimitive;
import com.google.gson.annotations.SerializedName;
public class Foo
{
public static void main(String[] args) throws Exception
{
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES);
gsonBuilder.registerTypeAdapter(MyClass.class, new MyClassDeserializer());
Gson gson = gsonBuilder.create();
MyClass myObject = gson.fromJson(new FileReader("input.json"), MyClass.class);
System.out.println(gson.toJson(myObject));
}
}
class MyClassDeserializer implements JsonDeserializer<MyClass>
{
#Override
public MyClass deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
throws JsonParseException
{
JsonObject object = json.getAsJsonObject();
String nickName = object.get("handle").getAsString();
Set<Map.Entry<String, JsonElement>> mapEntries = object.get("random_details").getAsJsonObject().entrySet();
Map randomDetails = new HashMap(mapEntries.size());
for (Map.Entry<String, JsonElement> mapEntry : mapEntries)
{
String key = mapEntry.getKey();
Object value;
JsonPrimitive jsonPrimitive = mapEntry.getValue().getAsJsonPrimitive();
if (jsonPrimitive.isNumber()) value = jsonPrimitive.getAsNumber();
else if (jsonPrimitive.isBoolean()) value = jsonPrimitive.getAsBoolean();
else value = jsonPrimitive.getAsString();
randomDetails.put(key, value);
}
MyClass myObject = new MyClass();
myObject.nickName = nickName;
myObject.randomDetails = randomDetails;
return myObject;
}
}
class MyClass
{
#SerializedName("handle")
String nickName;
Map randomDetails;
}
Related
I have a project with 3 files in Quarkus
application.properties
conf.obj[0].name=name0
conf.obj[0].code=code0
conf.obj[0].versions[0].number=1
conf.obj[1].name=name1
conf.obj[1].code=code1
conf.obj[1].versions[0].number=1
conf.obj[2].name=name2
conf.obj[2].code=code2
conf.obj[2].versions[0].number=1
conf.obj[2].versions[1].number=2
AvailableConfig.java
package com.example;
import io.quarkus.runtime.annotations.StaticInitSafe;
import io.smallrye.config.ConfigMapping;
import java.util.List;
#StaticInitSafe
#ConfigMapping(prefix = "conf")
public interface AvailableConfig {
List<listObject> obj();
interface listObject {
String name();
String code();
List<Version> versions();
interface Version {
Integer number();
}
}
}
MainService.java
package com.example;
import io.quarkus.runtime.StartupEvent;
import javax.enterprise.context.ApplicationScoped;
import javax.enterprise.event.Observes;
import javax.inject.Inject;
import javax.json.bind.Jsonb;
import java.util.List;
#ApplicationScoped
public class MainService {
#Inject
AvailableConfig availableConfig;
#Inject
Jsonb jsonb;
void onStart(#Observes StartupEvent ev) {
List<AvailableConfig.listObject> config = availableConfig.obj();
String result = jsonb.toJson(config);
}
}
As a result of execution, the correct object "config" is created.
But when it is serialized, an empty json "[{},{},{}]" is obtained.
How do I properly serialize things like this?
I don't know why jsonb behaves like this, but I found several solutions:
1. use Gson
Gson gson = new Gson();
String resultGson = gson.toJson(config);
2. use jackson
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY);
String resultJackson = objectMapper.writeValueAsString(config);
But I'm not sure if these options are ready for AOT-compilation in Quarkus environment.
3. Therefore, the best way is not to store such things in the config format, it is better to put them in your json-file.
I am trying to retreive using RestTemplate object from service.
HttpHeaders headers = new HttpHeaders();
headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON));
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<BusinessPartnerRequest> entity = new HttpEntity<>(request, headers);
ResponseEntity<Analysis> result = restTemplate.exchange(url, HttpMethod.POST, entity, Analysis.class);
Unfortunately I got Exception each time. This is the exception:
Could not extract response: no suitable HttpMessageConverter found for response type [class com.abb.bttr.validator.Analysis] and content type [application/json;charset=UTF-8]
I know that this is general exception and mapper return it every time there will be any Exception.
So I found real cause:
Cannot find a deserializer for non-concrete Map type [map type; class org.apache.commons.collections4.MultiMap, [simple type, class java.lang.String] -> [simple type, class java.lang.Object]]
My Analysis object:
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.MapSerializer;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
import org.apache.commons.collections4.MultiMap;
import org.apache.commons.collections4.map.MultiValueMap;
#JacksonXmlRootElement
public class Analysis {
private Boolean error = false;
#JsonSerialize(keyUsing = MapSerializer.class)
private MultiMap<String, String> errorDetails = new MultiValueMap<>();
//getter, setters, constructors, equals and hashcode
}
Do you know a way to deserialize ApacheCommons MultiMap in quick way? I can use guava, but I don't want to add Guava library just for Multimap.
In lieu of Apache conman APP you can utilize the Spring predicated Map.
Import verbalization like below.
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
And utilize the multi Map like below.
MultiValueMap<String, Integer> multiValueMap=new LinkedMultiValueMap<>();
You can instruct which type you want to use for MultiMap by using SimpleModule class. See below code:
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import java.io.IOException;
import org.apache.commons.collections4.MultiMap;
import org.apache.commons.collections4.map.MultiValueMap;
public class JsonApp {
public static void main(String[] args) throws IOException {
MultiMap<String, String> multiMap = new MultiValueMap<>();
multiMap.put("a", "AA");
multiMap.put("a", "AAA");
multiMap.put("b", "B");
multiMap.put("b", "BB");
SimpleModule collections4Module = new SimpleModule();
collections4Module.addAbstractTypeMapping(MultiMap.class, MultiValueMap.class);
ObjectMapper jsonMapper = new ObjectMapper();
jsonMapper.registerModule(collections4Module);
String json = jsonMapper.writeValueAsString(multiMap);
System.out.println(json);
System.out.println(jsonMapper.readValue(json, MultiMap.class));
}
}
Above code prints:
{"a":["AA","AAA"],"b":["B","BB"]}
{a=[[AA, AAA]], b=[[B, BB]]}
When running the following test app
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.jsontype.impl.StdTypeResolverBuilder;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
public class Main {
public static void main(String[] args) throws IOException {
// Create test data
Data data = new Data();
data.key = 1;
Map<String, Object> mapData = new HashMap<>();
mapData.put("longInMap", 2L);
mapData.put("longAsField", data);
// Configure Jackson to preserve types
JsonFactory factory = new JsonFactory();
ObjectMapper mapper = new ObjectMapper(factory);
StdTypeResolverBuilder resolver = new StdTypeResolverBuilder();
resolver.init(JsonTypeInfo.Id.CLASS, null);
resolver.inclusion(JsonTypeInfo.As.PROPERTY);
resolver.typeProperty("__t");
mapper.setDefaultTyping(resolver);
mapper.enable(SerializationFeature.INDENT_OUTPUT);
// Serialize
String json = mapper.writeValueAsString(mapData);
System.out.println("json = " + json);
// Deserialize
Map deserializedData = mapper.readValue(json, Map.class);
}
static class Data {
public long key;
}
}
I get this output and exception
json = {
"__t" : "java.util.HashMap",
"longInMap" : [ "java.lang.Long", 2 ],
"longAsField" : {
"__t" : "com.pinkmatter.bean.serialization.Main$Data",
"key" : [ "java.lang.Long", 1 ]
}
}
Exception in thread "main" com.fasterxml.jackson.databind.JsonMappingException: Class java.lang.Long not subtype of [simple type, class long] (through reference chain: java.util.HashMap["longAsField"]->com.pinkmatter.bean.serialization.Data["key"])
at com.fasterxml.jackson.databind.JsonMappingException.wrapWithPath(JsonMappingException.java:379)
at com.fasterxml.jackson.databind.JsonMappingException.wrapWithPath(JsonMappingException.java:339)
at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.wrapAndThrow(BeanDeserializerBase.java:1591)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.vanillaDeserialize(BeanDeserializer.java:278)
...
Caused by: java.lang.IllegalArgumentException: Class java.lang.Long not subtype of [simple type, class long]
at com.fasterxml.jackson.databind.type.TypeFactory.constructSpecializedType(TypeFactory.java:359)
at com.fasterxml.jackson.databind.jsontype.impl.ClassNameIdResolver._typeFromId(ClassNameIdResolver.java:72)
at com.fasterxml.jackson.databind.jsontype.impl.ClassNameIdResolver.typeFromId(ClassNameIdResolver.java:42)
...
I am trying to serialize a bunch of basic plain old java objects from libraries we are using (so we cannot modify the classes or add annotations), while also trying to preserve the types of values in collections ("longInMap" in the above example must stay a Long object).
The problem is that Jackson throws the above exception when trying to deserialize the primitive public long key in the Data class. If I change the type to public int key then no exception is thrown and deserialization works.
Also, since there are many different types of objects and I don't know at compile time exactly what will be serialized I don't think using mix-ins will work.
Please advise about what I might be doing wrong or of a possible workaround for getting primitive long fields deserialized while also maintaining the types of objects in collections.
I am using Jackson 2.8.3.
A workaround was provided here https://github.com/FasterXML/jackson-databind/issues/1395
The workaround is to replace
StdTypeResolverBuilder resolver = new StdTypeResolverBuilder();
with
StdTypeResolverBuilder resolver = new StdTypeResolverBuilder() {
#Override
public TypeSerializer buildTypeSerializer(SerializationConfig config, JavaType baseType, Collection<NamedType> subtypes) {
if (baseType.isPrimitive()) {
return null;
}
return super.buildTypeSerializer(config, baseType, subtypes);
}
#Override
public TypeDeserializer buildTypeDeserializer(DeserializationConfig config, JavaType baseType, Collection<NamedType> subtypes) {
if (baseType.isPrimitive()) {
return null;
}
return super.buildTypeDeserializer(config, baseType, subtypes);
}
};
A fix was implemented and will be available in jackson-databind 2.8.4 after which the workaround will no longer be required.
I try to have Jackson property type info serialized, even when my type is referenced by a map.
With this simple sample:
import java.util.HashMap;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.JsonTypeName;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
public class DemoJackson {
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY)
public static abstract class Animal {}
#JsonTypeName("cat")
public static class Cat extends Animal {}
public static void main(String[] args) throws JsonProcessingException {
ObjectMapper objectMapper = new ObjectMapper();
Cat cat = new Cat();
System.out.println(objectMapper.writeValueAsString(cat));
HashMap<String, Object> map = new HashMap<String, Object>();
map.put("data", cat);
System.out.println(objectMapper.writeValueAsString(map));
}
}
I get:
{"#type":"cat"}
{"data":{}}
But I would like to have:
{"#type":"cat"}
{"data":{"#type":"cat"}}
Is it possible? How to do it?
I have tried with enableDefaultTyping but I get:
{"#type":"cat"}
{"data":["DemoJackson$Cat",{}]}
When you are serializing a map directly, as you do, the object mapper needs the type information about the map contents because of generic typing. Please refer to chapter 5.1 of Polymorphic Type Handling wiki page.
An example of passing the type reference information when serializing the map of Animals.
objectMapper.writerWithType(new TypeReference<Map<String, Animal>>() {})
.writeValueAsString(map)
Output:
{"#type":"cat"}
{"data":{"#type":"cat"}}
I have a a Map<String,Foo> foosMap that I want to serialize through Jackson . Now I want following two settings on the serialization process:
The Map can have have plenty of null values and null keys and I don't want nulls to be serialized.
For all those Foos that are getting serialized, I do not want to serialize null objects referenced inside Foo.
What is the best way to achieve this ? I am using jackson-core1.9 and jackson-mapper1.9 jars in my project.
If it's reasonable to alter the original Map data structure to be serialized to better represent the actual value wanted to be serialized, that's probably a decent approach, which would possibly reduce the amount of Jackson configuration necessary. For example, just remove the null key entries, if possible, before calling Jackson. That said...
To suppress serializing Map entries with null values:
Before Jackson 2.9
you can still make use of WRITE_NULL_MAP_VALUES, but note that it's moved to SerializationFeature:
mapper.configure(SerializationFeature.WRITE_NULL_MAP_VALUES, false);
Since Jackson 2.9
The WRITE_NULL_MAP_VALUES is deprecated, you can use the below equivalent:
mapper.setDefaultPropertyInclusion(
JsonInclude.Value.construct(Include.ALWAYS, Include.NON_NULL))
To suppress serializing properties with null values, you can configure the ObjectMapper directly, or make use of the #JsonInclude annotation:
mapper.setSerializationInclusion(Include.NON_NULL);
or:
#JsonInclude(Include.NON_NULL)
class Foo
{
public String bar;
Foo(String bar)
{
this.bar = bar;
}
}
To handle null Map keys, some custom serialization is necessary, as best I understand.
A simple approach to serialize null keys as empty strings (including complete examples of the two previously mentioned configurations):
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.SerializerProvider;
public class JacksonFoo
{
public static void main(String[] args) throws Exception
{
Map<String, Foo> foos = new HashMap<String, Foo>();
foos.put("foo1", new Foo("foo1"));
foos.put("foo2", new Foo(null));
foos.put("foo3", null);
foos.put(null, new Foo("foo4"));
// System.out.println(new ObjectMapper().writeValueAsString(foos));
// Exception: Null key for a Map not allowed in JSON (use a converting NullKeySerializer?)
ObjectMapper mapper = new ObjectMapper();
mapper.configure(SerializationFeature.WRITE_NULL_MAP_VALUES, false);
mapper.setSerializationInclusion(Include.NON_NULL);
mapper.getSerializerProvider().setNullKeySerializer(new MyNullKeySerializer());
System.out.println(mapper.writeValueAsString(foos));
// output:
// {"":{"bar":"foo4"},"foo2":{},"foo1":{"bar":"foo1"}}
}
}
class MyNullKeySerializer extends JsonSerializer<Object>
{
#Override
public void serialize(Object nullKey, JsonGenerator jsonGenerator, SerializerProvider unused)
throws IOException, JsonProcessingException
{
jsonGenerator.writeFieldName("");
}
}
class Foo
{
public String bar;
Foo(String bar)
{
this.bar = bar;
}
}
To suppress serializing Map entries with null keys, further custom serialization processing would be necessary.
For Jackson versions < 2.0 use this annotation on the class being serialized:
#JsonSerialize(include=JsonSerialize.Inclusion.NON_NULL)
Answer seems to be a little old, What I did was to use this mapper to convert a MAP
ObjectMapper mapper = new ObjectMapper().configure(SerializationConfig.Feature.WRITE_NULL_MAP_VALUES, false);
a simple Map:
Map<String, Object> user = new HashMap<String,Object>();
user.put( "id", teklif.getAccount().getId() );
user.put( "fname", teklif.getAccount().getFname());
user.put( "lname", teklif.getAccount().getLname());
user.put( "email", teklif.getAccount().getEmail());
user.put( "test", null);
Use it like this for example:
String json = mapper.writeValueAsString(user);
my solution, hope help
custom ObjectMapper and config to spring xml(register message conveters)
public class PyResponseConfigObjectMapper extends ObjectMapper {
public PyResponseConfigObjectMapper() {
disable(SerializationFeature.WRITE_NULL_MAP_VALUES); //map no_null
setSerializationInclusion(JsonInclude.Include.NON_NULL); // bean no_null
}
}