I have the following json content:
{
"arguments": {
"key" : "value"
}
}
But, it can dynamically change depending on the specified configuration of the json file.
For example once can be like above, but sometimes it can be empty arguments: {} or something it may be configured something like this:
"arguments": {
"someKeyName" : "someValue"
}
or even:
"arguments": {
"someKeyName": "someKeyValue",
"someKeyName2": "someKeyValue2"
}
My question is, how can I handle the deserialization with Jackson's object mapper or some other alternative, without knowing what would the properties be of the arguments object each time I have to handle the deserialization ?
You can de-serialize it into a Map<String, Object> and you will always get a map but its internal content would be different each time. you can do something like this:
Map<String, Object> data = objectReader.forType(Map.class).readValue(jsonString);
BTW, I wrote my own JsonUtils class which is a thin wrapper over JSON-Jackson library that simplifies a bit serializing and deserializing JSON strings. reading a simple map would look like:
Map<String, Object> map = JsonUtils.readObjectFromJsonString("{\"a\": \"b\"}", Map.class);
But with my class you don't need to instantiate and configure ObjectMapper instance. Here is JsonUtils Javadoc. If you want to use it this class is part of Open source MgntUtils library written and maintained by me. You can get MgntUtils library as Maven artifact or on Github (including source code and Javadoc)
Assume (spring-boot-starter+):
package com.example.demo;
import java.util.HashMap;
import java.util.Map;
public class Dto {
final Map<String, Object> arguments = new HashMap<>();
public Map<String, Object> getArguments() {
return arguments;
}
}
..then we can (already) test:
package com.example.demo;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import static org.junit.jupiter.api.Assertions.assertEquals;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
#SpringBootTest
class DemoJacksonApplicationTests {
#Autowired
ObjectMapper objectMapper;
#Test
void test1() throws JsonProcessingException {
Dto dto = objectMapper.readValue(
"""
{
"arguments": {
"key" : "value"
}
}
""", Dto.class);
assertEquals("value", dto.getArguments().get("key"));
}
#Test
void test2() throws JsonProcessingException {
Dto dto = objectMapper.readValue(
"""
{
"arguments": {
"someKeyName" : "someValue"
}
}
""", Dto.class);
assertEquals("someValue", dto.getArguments().get("someKeyName"));
}
#Test
void test3() throws JsonProcessingException {
Dto dto = objectMapper.readValue(
"""
{
"arguments": {
"someKeyName": "someKeyValue",
"someKeyName2": "someKeyValue2"
}
}
""", Dto.class);
assertEquals("someKeyValue", dto.getArguments().get("someKeyName"));
assertEquals("someKeyValue2", dto.getArguments().get("someKeyName2"));
}
}
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'm using Jackson to deserialise a class which has Optional member variables, so it looks like
class Test{
Optional<String> testString;
}
but in serialised form it looks like, which is legit
{
"value": {
"testString": "hi"
}
How can I deserialise it back to my Test class?, because when I try to do so it says unknown field "value". Can it be possible without changing my test class.
You need to register Jdk8Module. Belowe you can find example, how to do that:
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
import java.io.File;
import java.util.Optional;
public class JsonApp {
public static void main(String[] args) throws Exception {
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new Jdk8Module());
Test test = new Test();
test.setTestString(Optional.of("str"));
String json = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(test);
System.out.println(json);
System.out.println(mapper.readValue(json, Test.class));
}
}
Above code prints:
{
"testString" : "str"
}
Test{testString=Optional[str]}
See also:
jackson-modules-java8
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 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
}
}
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;
}