Нow can i serialize #ConfigMapping - java

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.

Related

How to Deserialize json for class having Optional fields using Jackson

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

how to use Google Gson instead of the default Jackson of the Spring in swagger?

I'm working in a project and I would like to documentation it using Swagger. Is a project implemented with Spring Boot and we use the library "com.google.code.gson" instead of default of SpringBoot "Jackson", but with Gson Swagger not works.
The problem is with the pattern of return.
Gson return as follow:
{"value":"{\"swagger\":\"2.0\",\"info\":{\"description\":
Jackson return as follow:
{"swagger":"2.0","info":{"description"
Does anyone have any idea how can I make Gson works?
Spring Boot uses Jackson by default in order to serialize and deserialize request and response objects in the REST APIs.
If you want to use GSON instead of Jackson then you can add Gson dependency in your pom.xml or build.gradle file and specify a property in the application.properties file to tell Spring Boot to use Gson as your preferred json mapper.
# Preferred JSON mapper to use for HTTP message conversion.
spring.http.converters.preferred-json-mapper=gson
that's all you need to do!
Swagger constructs it's own Json class to communicate with frontend (see springfox.documentation.spring.web.json.Json), which is defined as follow:
import com.fasterxml.jackson.annotation.JsonRawValue;
import com.fasterxml.jackson.annotation.JsonValue;
public class Json {
private final String value;
public Json(String value) {
this.value = value;
}
#JsonValue. // NOTICE THIS
#JsonRawValue // NOTICE THIS
public String value() {
return value;
}
}
We can see that it use annotation #JsonRawValue defined by Jackson to indicate that Jackson should use the return value of the method value() as serializing result of Json object, however, this annotation is not recognized by Gson, and serialized result becomes
{
"value": "{\"swagger\":\"2.0\"...."
}
instead of the correct response format:
{
"swagger": "2.0",
"info":[...],
...
}
the solution is customize a TypeAdapter or JsonSerializer for your Gson bean
import com.google.gson.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.spring.web.json.Json;
#Configuration
public class GsonConfig {
#Bean
public Gson gson() {
return new GsonBuilder()
.registerTypeAdapter(Json.class, new SwaggerJsonTypeAdapter())
.create();
}
public static class SwaggerJsonTypeAdapter implements JsonSerializer<Json> {
#Override
public JsonElement serialize(Json json, Type type, JsonSerializationContext context) {
return JsonParser.parseString(json.value());
}
}
}
Old question but this is my solution, using Spring Boot + OpenAPI + Swagger + Gson:
Configuration Component:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.acepta.signerservices.core.gson.SwaggerJsonSerializer;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
#Configuration
public class GsonConfiguration {
#Bean
public Gson gson() {
return new GsonBuilder()
.registerTypeAdapter(String.class, new SwaggerJsonSerializer())
.create();
}
}
Gson Adapter:
import java.lang.reflect.Type;
import com.google.gson.JsonElement;
import com.google.gson.JsonParser;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;
public class SwaggerJsonSerializer implements JsonSerializer<String> {
#Override
public JsonElement serialize(String json, Type typeOfSrc, JsonSerializationContext context) {
if (json.contains("openapi")) {
return JsonParser.parseString(json.replace("\r", ""));
} else {
return new Gson().toJsonTree(json, typeOfSrc);
}
}
}
I hope that it would be useful to someone.

Java Jackson 2.8.3 serializing list containing abstract objects with circular dependencies

I try to serialize some data with jackson which works pretty well for most cases but now I have an issue with a list. The list is of type A which is an abstract class and may contain circular dependencies. I can't figure out how to serialize this construct with jackson. The combination of identityInformation and typeInformation doesn't seem to properly work.
Below is Examplecode which produces the issue I am facing.
I am using Jackson version 2.8.3. Am I missing something? Is there a good solution to serialize and deserialize this kind of list?
Thanks in advance for any help!
Code:
import java.util.ArrayList;
import java.util.List;
import com.fasterxml.jackson.annotation.JsonIdentityInfo;
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.ObjectIdGenerators;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.type.CollectionType;
public class JacksonTest {
#JsonTypeInfo(
use = JsonTypeInfo.Id.CLASS,
include = JsonTypeInfo.As.PROPERTY,
property = "#class")
#JsonSubTypes({ #JsonSubTypes.Type(value = B.class) })
public static abstract class A {
public A member;
}
#JsonIdentityInfo(generator = ObjectIdGenerators.IntSequenceGenerator.class, property = "#id")
public static class B extends A {
}
public static void main(String[] args) {
List<A> list = new ArrayList<A>();
ObjectMapper mapper = new ObjectMapper();
B instance1 = new B();
B instance2 = new B();
instance1.member = instance2;
list.add(instance1);
list.add(instance2);
CollectionType listType = mapper.getTypeFactory().constructCollectionType(list.getClass(), A.class);
try{
String serialized = mapper.writerFor(listType).writeValueAsString(list);
System.out.println(serialized);
list = mapper.readValue(serialized, listType);
} catch (Exception e){
e.printStackTrace();
}
}
}
Generated json String:
[{"#class":"JacksonTest$B","#id":1,"member":{"#class":"JacksonTest$B","#id":2,"member":null}},2]
Error when trying to read the String:
com.fasterxml.jackson.databind.JsonMappingException: Unexpected token (VALUE_NUMBER_INT), expected FIELD_NAME: missing property '#class' that is to contain type id (for class JacksonTest$A)
It seems like jackson expects the second entry to also be a json object containing the #class field and doesn't recognize that the second array element is a reference to an already existing object.
Type of field "A.member" is "A" and you missed out #JsonIdentityInfo for "A" type. Due to this, Jackson consider the object from list is a normal object( only id was serialized) and due to lack of identityinfo, jackson is not able to apply the object reference lookup mechanism during de-serializing and hence you see the error.
To fix your issue you need to add "#JsonIdentityInfo for type "A" too or better you can move #JsonIdentityInfo declaration from type "B" to "A".
code:
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.sameInstance;
import static org.junit.Assert.*;
import java.util.ArrayList;
import java.util.List;
import org.hamcrest.core.IsSame;
import org.junit.Test;
import com.fasterxml.jackson.annotation.JsonIdentityInfo;
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.ObjectIdGenerators;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.type.CollectionType;
public class JacksonTest {
#JsonTypeInfo(
use = JsonTypeInfo.Id.CLASS,
include = JsonTypeInfo.As.PROPERTY,
property = "#class")
#JsonSubTypes({ #JsonSubTypes.Type(value = B.class) })
#JsonIdentityInfo(generator = ObjectIdGenerators.IntSequenceGenerator.class, property = "#id")
public static abstract class A {
public A member;
}
public static class B extends A {
}
#Test
public void testCirularPolymorphicSerialization() throws Exception {
ObjectMapper mapper = new ObjectMapper();
// set up fixture
List<A> list = new ArrayList<A>();
B instance1 = new B();
B instance2 = new B();
instance1.member = instance2;
list.add(instance1);
list.add(instance2);
CollectionType listType = mapper.getTypeFactory().constructCollectionType(list.getClass(), A.class);
String serialized = mapper.writerFor(listType).writeValueAsString(list);
list = mapper.readValue(serialized, listType);
assertThat(list.size(), is(2));
assertThat(list.get(0).member, sameInstance(list.get(1)));
}
}

JsonInclude.Include.NON_DEFAULT not working with custom serializer

I want to both use a custom serializer and have the JsonInclude.Include.NON_DEFAULT designation be honored. When I don't use a custom serializer it is honored but when I do use a custom serializer it is not.
This is Jackson 2.2.2. I do not presently have the option to switch to a newer version of Jackson.
Here's a simple example that shows the problem:
import com.fasterxml.jackson.annotation.JsonGetter;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import java.io.IOException;
import java.util.EnumSet;
import java.util.Set;
import java.util.stream.Collectors;
public class JacksonSerialization
{
public static void main(String[] args) throws Exception {
ObjectMapper serializer = new ObjectMapper();
Foo foo = new Foo();
foo.setFlags(EnumSet.of(Flag.CC, Flag.BB));
System.out.println(serializer.writeValueAsString(foo));
foo = new Foo();
System.out.println(serializer.writeValueAsString(foo));
}
public static enum Flag
{
AA,
BB,
CC
}
#JsonInclude(JsonInclude.Include.NON_DEFAULT)
public static class Foo
{
private Set<Flag> flags;
public Foo() {
flags = EnumSet.of(Flag.AA);
}
#JsonGetter("f")
#JsonSerialize(using = FlagSetSerializer.class)
public Set<Flag> getFlags() {
return flags;
}
public void setFlags(Set<Flag> theFlags) {
flags = theFlags;
}
}
public static class FlagSetSerializer extends JsonSerializer<Set<Flag>>
{
#Override
public void serialize(Set<Flag> value,
JsonGenerator jsonGenerator,
SerializerProvider serializerProvider) throws IOException {
String csv = value.stream()
.map(Flag::toString)
.collect(Collectors.joining(","));
jsonGenerator.writeString(csv);
}
}
}
And here's the output:
{"f":"BB,CC"}
{"f":"AA"}
Note that f is being serialized even when it has the default value. If I comment out the #JsonSerialize annotation then I get the following output:
{"f":["BB","CC"]}
{}
Then f properly does not get serialized. But of course things are not being serialized in the format I want.
So how do I get the custom serializer to honor the class's #JsonInclude annotation?
You probably want to implement public boolean isEmpty(SerializerProvider provider, T value) as per the documentation, which says:
public boolean isEmpty(SerializerProvider provider, T value)
Method called to check whether given serializable value is considered
"empty" value (for purposes of suppressing serialization of empty
values).
Default implementation will consider only null values to be
empty.
As per https://github.com/FasterXML/jackson-databind/issues/730
Another possible source of trouble is that you talk about NON_EMPTY but you code uses NON_DEFAULT.
And rather too much digging in the debugger leads me to suggest
#JsonSerialize(using = FlagSetSerializer.class, include = JsonSerialize.Inclusion.NON_DEFAULT)
Which seems to pass your tests.
The problem seems to be in JacksonAnnotationInspector#findSerializationInclusion, which first looks for a #JsonInclude attribute on the property, and when it fails to find that, it looks for a #JsonSerialize annotation. #JsonSerialize includes a deprecated include property, which has a default value of ALWAYS.
I've not looked into it too deeply, but I suspect a deprecation/refactor managed to slice off some functionality. C'est la vie.

How to make a fieldName to fieldValue map deserializing a json string

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;
}

Categories