I have an interface, which I want to use for serialize/deserialize. I want to omit some of the fields. Code below is not working so far.
#JsonAutoDetect(fieldVisibility = Visibility.NONE)
public interface MyWrapper {
//no annotation to not serialize
String getMyField();
//annotation to deserialize
#JsonProperty("my_field")
void setMyField();
}
You can either specify #JsonIgnore annotation on the method, or #JsonIgnoreProperties(value = {"myfield"}) annotation on the class.
see examples here
EDIT:
which version of Jackson are you using? becuase in the one I am using (2.5) the use of #JsonIgnore together with #JsonProperty works perfectly.
also, notice that the setter needs to receive an argument to actually be used by Jackson
interface with fixed setter:
#JsonAutoDetect(fieldVisibility = Visibility.NONE)
public interface MyWrapper {
#JsonIgnore
String getMyField();
// annotation to deserialize
#JsonProperty("my_field")
void setMyField(String f);
}
implementation (nothing exciting here)
public class Foo implements MyWrapper {
private String myField;
public Foo() {}
public Foo(String f) {
setMyField(f);
}
#Override
public String getMyField() {
return myField;
}
#Override
public void setMyField(String f) {
myField = f;
}
}
testing :
public static void main(String[] args) {
ObjectMapper mapper = new ObjectMapper();
// serialization - ignore field
try {
MyWrapper w = new Foo("value");
String json = mapper.writeValueAsString(w);
System.out.println("serialized MyWrapper: " + json);
} catch (Exception e) {
e.printStackTrace();
}
// de-serialization - read field
String json = "{\"my_field\":\"value\"}";
try (InputStream is = new ByteArrayInputStream(json.getBytes("UTF-8"))) {
MyWrapper w = (MyWrapper)mapper.readValue(is, Foo.class);
System.out.println("deserialized MyWrapper: input: " + json + " ; w.getMyField(): " + w.getMyField());
} catch (Exception e) {
e.printStackTrace();
}
}
output:
serialized MyWrapper: {}
deserialized MyWrapper: input: {"my_field":"value"} ; w.getMyField(): value
Related
I'm getting the error below when I try to POST or PUT to my rest resource. The GET and DELETE (for individual Starlinks) requests works just fine FWIW. I have other resources which basically follow the same pattern of classes—some with entities with EmbeddedIds and some without, and all their REST methods work properly. The only difference is that in this instance, I introduced an entity relationship (#ManyToOne) between my StarLink class and a Star class which allowed me to access my StarLinks from a Star resource through HATEOAS—but that seems to have thrown a wrench in things. Tried looking for solutions but, I'm beat.
org.springframework.core.convert.ConverterNotFoundException: No converter found capable of converting from type [java.lang.String] to type [com.beezassistant.configurator.models.StarLinkId]
...
Here are the relevant classes:
StarLinkRepository.java
#RepositoryRestResource(exported=true, path="starlinks")
public interface StarLinkRepository extends JpaRepository<StarLink, StarLinkId> {}
StarLink.java
#Entity
#Table(name="starlink")
public class StarLink implements Serializable {
// ...
#EmbeddedId
private StarLinkId starLinkId;
#ManyToOne
#MapsId("starName")
private Star star;
private String linkName;
public StarLink() {
super();
starLinkId = new StarLinkId();
}
// Getters and setters
}
StarLinkId
#Embeddable
public class StarLinkId implements Serializable {
// ...
private String starName;
private String link;
public StarLinkId() {
super();
}
// Getters and setters
// equals and hashCode
}
StarLinkIdConverter
#Component
public class StarLinkIdConverter implements BackendIdConverter {
#Override
public boolean supports(Class<?> delimiter) {
return StarLink.class.equals(delimiter);
}
#Override
public Serializable fromRequestId(String id, Class<?> entityType) {
String[] parts = id.split("__");
StarLinkId starLinkId = new StarLinkId();
starLinkId.setStarName(parts[0]);
try {
starLinkId.setLink(
URLDecoder.decode(
parts[1],
StandardCharsets.UTF_8.toString()
)
);
}
catch (UnsupportedEncodingException e) {
starLinkId = null;
}
return starLinkId;
}
#Override
public String toRequestId(Serializable id, Class<?> entityType) {
StarLinkId starLinkId = (StarLinkId) id;
try {
return String.format(
"%s__%s",
starLinkId.getStarName(),
URLEncoder.encode(
starLinkId.getLink(),
StandardCharsets.UTF_8.toString()
)
);
}
catch (UnsupportedEncodingException e) {
return null;
}
}
}
I had the same issue and I found a related issue here.
Implementing a BackendIdConverter will not work as it's for Spring WebMVC controllers.
You need to implement a Converter<String, StarLinkId>, as follows:
public class StarLinkIdConverter implements Converter<String, StarLinkId> {
#Override
public StarLinkId convert(String source) {
String[] parts = source.split("__");
StarLinkId starLinkId = new StarLinkId();
starLinkId.setStarName(parts[0]);
try {
starLinkId.setLink(
URLDecoder.decode(
parts[1],
StandardCharsets.UTF_8.toString()
)
);
}
catch (UnsupportedEncodingException e) {
starLinkId = null;
}
return starLinkId;
}
}
Then you have to register this converter in your configuration, in your class where you extend RepositoryRestConfigurer, as follows:
#Override
protected void configureConversionService(ConfigurableConversionService conversionService) {
super.configureConversionService(conversionService);
conversionService.addConverter(new StarLinkIdConverter());
}
This will give you the conversion from the request URI to your StarLinkId, and this will make requests work as long as you assemble the request URI yourself, but to correctly do HATEOAS you still need to make sure you return the correctly formatted URIs, by overriding toString in your StarLinkId class:
#Override
public String toString() {
try {
return String.format(
"%s__%s",
getStarName(),
URLEncoder.encode(
getLink(),
StandardCharsets.UTF_8.toString()
)
);
}
catch (UnsupportedEncodingException e) {
return null;
}
}
I haven't tested the above code, but these steps fixed this issue for me.
I'm writing a network class and want to be able to parse different responses to different classes (there's still one-to-one relationship but I want to have a single parseResponse() that will deal with all responses from different endpoints, and endpoint.className has the expected classType that I should map to):
private Class<?> parseResponse(StringBuilder responseContent, Endpoint endpoint) {
ObjectMapper mapper = new ObjectMapper();
try {
Class<?> object = mapper.readValue(responseContent.toString(), endpoint.className);
// endpoint.className has Class<?> type
if (object instanceof endpoint.className) {
}
} catch (IOException e) {
// handle errors
}
}
But there's an error if I write if (object instanceof endpoint.className)
Update: probably the better option is to add parse() method to Endpoint class:
public Class<?> parseResponse(String responseContent) {
// this.className has Class<?> type (e.g., Foo.class).
}
public enum Endpoint {
FOO (Foo.class),
BAR (Bar.class);
private Class<?> classType;
}
But there're still the same type errors.
You should separate JSON deserialisation from other parts of your app. You can not implement one method for all responses but you probably have a limited number of responses and you can declare some simple methods for each class. Generally, you could have only one method with declaration like below:
public <T> T deserialise(String payload, Class<T> expectedClass) {
Objects.requireNonNull(payload);
Objects.requireNonNull(expectedClass);
try {
return mapper.readValue(payload, expectedClass);
} catch (IOException e) {
throw new IllegalStateException("JSON is not valid!", e);
}
}
And now, you can deserialise all payloads you want. You need to provide JSON payload and POJO class you want to receive back.
Simple working solution which shows that concept:
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import java.io.IOException;
import java.util.Objects;
public class JsonMapper {
private final ObjectMapper mapper = new ObjectMapper();
public JsonMapper() {
// configure mapper instance if required
mapper.enable(SerializationFeature.INDENT_OUTPUT);
mapper.enable(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT);
// etc...
}
public String serialise(Object value) {
try {
return mapper.writeValueAsString(value);
} catch (JsonProcessingException e) {
throw new IllegalStateException("Could not generate JSON!", e);
}
}
public <T> T deserialise(String payload, Class<T> expectedClass) {
Objects.requireNonNull(payload);
Objects.requireNonNull(expectedClass);
try {
return mapper.readValue(payload, expectedClass);
} catch (IOException e) {
throw new IllegalStateException("JSON is not valid!", e);
}
}
public Foo parseResponseFoo(String payload) {
return deserialise(payload, Foo.class);
}
public Bar parseResponseBar(String payload) {
return deserialise(payload, Bar.class);
}
public static void main(String[] args) {
JsonMapper jsonMapper = new JsonMapper();
String bar = "{\"bar\" : 2}";
System.out.println(jsonMapper.parseResponseBar(bar));
String foo = "{\"foo\" : 1}";
System.out.println(jsonMapper.parseResponseFoo(foo));
System.out.println("General method:");
System.out.println(jsonMapper.deserialise(foo, Foo.class));
System.out.println(jsonMapper.deserialise(bar, Bar.class));
}
}
class Foo {
public int foo;
#Override
public String toString() {
return "Foo{" +
"foo=" + foo +
'}';
}
}
class Bar {
public int bar;
#Override
public String toString() {
return "Bar{" +
"bar=" + bar +
'}';
}
}
See also:
Deserializing or serializing any type of object using Jackson ObjectMapper and handling exceptions
What are Reified Generics? How do they solve Type Erasure problems and why can't they be added without major changes?
How to use jackson to deserialize to Kotlin collections
I am creating java app which will allow storing objects in database. What I want to do is generic implementation so it could load json and create java class from it. This is what a code should look like:
SomeClass someObject= data.getValue(SomeClass.class);
Lets say that data would be a json object. How should I implement getValue() method so it will allow me to create class from it. I don't want SomeClass to extend anything other then Object. I think that this should be done using generic classes but so far I have not worked with generic classes like this. Can you please point to a best way on how to acomplish this? Example code would be best.
Many thanks
You can consult the source code of Jackson library and look inside (or debug) the method BeanDeserializer#vanillaDeserialize(), there you'll find the loop which traverse through all json tokens, finds the corresponding fields and sets their values.
As a proof of concept, I've extracted part of the logic from Jacskson and wrapped it inside a naive (and fragile) object mapper and a naive (and fragile) json parser:
public static class NaiveObjectMapper {
private Map<String, Object> fieldsAndMethods;
private NaiveJsonParser parser;
public <T> T readValue(String content, Class<T> valueType) {
parser = new NaiveJsonParser(content);
try {
// aggregate all value type fields and methods inside a map
fieldsAndMethods = new HashMap<>();
for (Field field : valueType.getDeclaredFields()) {
fieldsAndMethods.put(field.getName(), field);
}
for (Method method : valueType.getMethods()) {
fieldsAndMethods.put(method.getName(), method);
}
// create an instance of value type by calling its default constructor
Constructor<T> constructor = valueType.getConstructor();
Object bean = constructor.newInstance(new Object[0]);
// loop through all json nodes
String propName;
while ((propName = parser.nextFieldName()) != null) {
// find the corresponding field
Field prop = (Field) fieldsAndMethods.get(propName);
// get and set field value
deserializeAndSet(prop, bean);
}
return (T) bean;
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
return null;
}
private void deserializeAndSet(Field prop, Object bean) {
Class<?> propType = prop.getType();
Method setter = (Method) fieldsAndMethods.get(getFieldSetterName(prop));
try {
if (propType.isPrimitive()) {
if (propType.getName().equals("int")) {
setter.invoke(bean, parser.getIntValue());
}
} else if (propType == String.class) {
setter.invoke(bean, parser.getTextValue());
}
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
private String getFieldSetterName(Field prop) {
String propName = prop.getName();
return "set" + propName.substring(0, 1).toUpperCase() + propName.substring(1);
}
}
class NaiveJsonParser {
String[] nodes;
int currentNodeIdx = -1;
String currentProperty;
String currentValueStr;
public NaiveJsonParser(String content) {
// split the content into 'property:value' nodes
nodes = content.replaceAll("[{}]", "").split(",");
}
public String nextFieldName() {
if ((++currentNodeIdx) >= nodes.length) {
return null;
}
String[] propertyAndValue = nodes[currentNodeIdx].split(":");
currentProperty = propertyAndValue[0].replace("\"", "").trim();
currentValueStr = propertyAndValue[1].replace("\"", "").trim();
return currentProperty;
}
public String getTextValue() {
return String.valueOf(currentValueStr);
}
public int getIntValue() {
return Integer.valueOf(currentValueStr).intValue();
}
}
public static class User {
private int id;
private String name;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
#Override
public String toString() {
return "id = " + id + ", name = \"" + name + "\"";
}
}
To see the deserialization in action run:
String json = "{\"id\":1, \"name\":\"jsmith\"}";
NaiveObjectMapper objectMapper = new NaiveObjectMapper();
User user = objectMapper.readValue(json, User.class);
System.out.println(user);
Or try online.
However I recommend not to reinvent the wheel and use Jackson and in case you need some custom actions you can use custom deserialization, see here and here.
I am using Jackson to deserialize a number of different implementations of the Product interface. These product implementations have different fields, but all have an InsuredAmount field. This InsuredAmount class has a value field and an IAType field. The IAType is a marker interface with different enums as implementations.
Now here's the problem: The enum implementations of the IAType interface correspond to a certain implementation of the Product interface. How can I make a generic implementation and tell Jackson to find the correct implementation of thee IAType? Should I use a generic parameter on the Product and the IAType interface identifying the product implementation? Should I use a Productable functional interface on the classes identifying the product implementation? How can I tell Jackson to use that implementation?
I hope the code below clarifies the problem, I chose to implement a Productable interface here, but a bettere structure to handle this problem would also be welcome.
#JsonPropertyOrder({"type", "someInfo"})
public class InsuredAmount implements Productable, Serializable {
private static final long serialVersionUID = 1L;
private IAType type;
private String someInfo;
public InsuredAmount() {
}
public InsuredAmount(IAType typeA, String someInfo) {
this.type = typeA;
this.someInfo = someInfo;
}
/* This should be on the product level, but if I can solve this problem,
the next level will just be more of the same.
*/
#JsonIgnore
#Override
public Product getProduct() {
return Product.PROD_A;
}
// Getters, setters, equals, etc. omitted.
}
--
public interface Productable {
public Product getProduct();
}
--
public enum Product {
PROD_A, PROD_B;
}
--
#JsonDeserialize(using = IATypeDeserializer.class)
public interface IAType extends Productable {
}
--
public enum IATypeA implements IAType {
FOO, BAR;
#Override
public Product getProduct() {
return Product.PROD_A;
}
}
--
public class IATypeDeserializer extends StdDeserializer<IAType> {
private static final long serialVersionUID = 1L;
public IATypeDeserializer() {
this(null);
}
public IATypeDeserializer(Class<?> vc) {
super(vc);
}
#Override
public IAType deserialize(JsonParser parser, DeserializationContext context)
throws IOException, JsonProcessingException {
JsonNode node = parser.getCodec().readTree(parser);
/* How to find out that the class calling the deserialization is InsuredAmountA, which
has getProduct() method that returns PROD_A, and matches the IATypeA that also returns
PROD_A, so I know to deserialize IATypeA, instead of other implementations of the IAType
interface?
*/
return IATypeA.valueOf(node.asText());
}
}
--
public class InsuredAmountTest {
private final ObjectMapper mapper = new ObjectMapper();
#Test
public void test01() throws IOException {
InsuredAmount iaA = new InsuredAmount(IATypeA.FOO, "test it");
String json = mapper.writeValueAsString(iaA);
assertThat(json, is("{\"type\":\"FOO\",\"someInfo\":\"test it\"}"));
InsuredAmount iaA2 = mapper.readValue(json, InsuredAmount.class);
IAType type = iaA2.getType();
assertThat(type, is(IATypeA.FOO));
assertThat(type.getProduct(), is(Product.PROD_A));
assertThat(iaA, is(iaA2));
}
#Test
public void test02() throws IOException {
InsuredAmount iaA = new InsuredAmount(IATypeA.BAR, "test it");
String json = mapper.writeValueAsString(iaA);
assertThat(json, is("{\"type\":\"BAR\",\"someInfo\":\"test it\"}"));
InsuredAmount iaA2 = mapper.readValue(json, InsuredAmount.class);
assertThat(iaA, is(iaA2));
}
}
Jackson handles the serialization of enums with minimal fuss, so all you need to do is annotate the IAType field with #JsonTypeInfo:
#JsonTypeInfo(use = JsonTypeInfo.Id.MINIMAL_CLASS)
private IAType type;
Then a test:
public static void main(String[] args) throws IOException {
ObjectMapper mapper = new ObjectMapper();
String json = mapper.writeValueAsString(new InsuredAmount(IATypeA.FOO, "info"));
System.out.println(json);
InsuredAmount ia = mapper.readValue(json, InsuredAmount.class);
System.out.println("Type is: " + ia.getType());
}
results in the output:
{"type":[".IATypeA","FOO"],"someInfo":"info"}
Type is: FOO
To get a more compact representation you will have to use custom serialization. Assuming that there are no overlaps in your enum namespace, you can serialize the type field as the enum name.
The deserializer will need to know which types are available for construction, either by class path discovery or, as in the following example, simply hard-coding the references:
public class IATest {
public static class IATypeSerializer extends JsonSerializer<IAType> {
#Override
public void serialize(IAType value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
gen.writeString(((Enum) value).name());
}
}
public static class IATypeDeserializer extends JsonDeserializer<IAType> {
#Override
public IAType deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
String value = p.readValueAs(String.class);
try {
return IATypeA.valueOf(value);
} catch (IllegalArgumentException e) {
// fall through
}
try {
return IATypeB.valueOf(value);
} catch (IllegalArgumentException e) {
// fall through
}
throw new JsonMappingException(p, "Unknown type '" + value + "'");
}
}
public static void main(String[] args) throws IOException {
ObjectMapper mapper = new ObjectMapper();
// Register a module to handle serialization of IAType implementations
SimpleModule module = new SimpleModule();
module.addSerializer(IAType.class, new IATypeSerializer());
module.addDeserializer(IAType.class, new IATypeDeserializer());
mapper.registerModule(module);
// Test
String json = mapper.writeValueAsString(new InsuredAmount(IATypeA.FOO, "info"));
System.out.println(json);
InsuredAmount ia = mapper.readValue(json, InsuredAmount.class);
System.out.println("Type is: " + ia.getType());
}
}
Which outputs:
{"type":"FOO","someInfo":"info"}
Type is: FOO
I ended up with using JsonCreator annotation on a special constructor.
#JsonCreator
public InsuredAmountA(
#JsonProperty("type") String type,
#JsonProperty("someInfo") String someInfo) throws IOException {
switch (getProduct()) {
case PROD_A:
try {
this.type = IATypeA.valueOf(type);
break;
} catch (IllegalArgumentException ex) {
// Throw IOException in the default.
}
// case PROD_B:
// this.type = (IATypeB) typeA;
// break;
default:
throw new IOException(String.format("Cannot parse value %s as type.", type));
}
this.someInfo = someInfo;
}
You may look into direction of polymorphic deserialisation:
http://wiki.fasterxml.com/JacksonPolymorphicDeserialization
unsing custom type resolver
I have a JSON:
{"evaluationPart": {
"generatedId": "48D5181DB8704F8AB5FC998964AD9075",
"evaluationQuestionPartOption": {
"generatedId": "48D5181DB8704F8AB5FC998964AD9075"
}
}}
I've created java classes for it to represent it:
The root class:
#JsonIgnoreProperties(value = {"evaluationPart"})
public class JsonEvaluationPart {
#JsonProperty("generatedId")
private String generatedId;
#JsonProperty("evaluationQuestionPartOption")
private JsonQuestionOption questionOption;
public String getGeneratedId() {
return generatedId;
}
public void setGeneratedId(String generatedId) {
this.generatedId = generatedId;
}
public JsonQuestionOption getQuestionOption() {
return questionOption;
}
public void setQuestionOption(JsonQuestionOption questionOption) {
this.questionOption = questionOption;
}
}
And the JsonQuestionOption class:
public class JsonQuestionOption {
#JsonProperty("generatedId")
private String generatedId;
public String getGeneratedId() {
return generatedId;
}
public void setGeneratedId(String generatedId) {
this.generatedId = generatedId;
}
}
I have written a small JUnit test to check how it goes:
public class JsonReaderTest {
/**
* Logger for this class.
*/
private static final Logger LOGGER = LoggerFactory.getLogger(JsonReaderTest.class);
private ObjectMapper objectMapper;
private static final String JSON = "{\"evaluationPart\": {\n" +
" \"generatedId\": \"48D5181DB8704F8AB5FC998964AD9075\",\n" +
" \"evaluationQuestionPartOption\": {\n" +
" \"generatedId\": \"48D5181DB8704F8AB5FC998964AD9075\"\n" +
" }\n" +
"}}";
#Before
public void setUp()
throws Exception {
LOGGER.debug("Creating the object mapper.");
objectMapper = new ObjectMapper();
LOGGER.debug("Object mapper successfully created. {}", objectMapper);
}
#Test
public void testJsonReader()
throws Exception {
JsonEvaluationPart partType = objectMapper.readValue(JSON, JsonEvaluationPart.class);
assertNotNull(partType);
LOGGER.debug("Part: {}.", partType);
assertEquals(partType.getGeneratedId(), "48D5181DB8704F8AB5FC998964AD9075");
assertEquals(partType.getQuestionOption().getGeneratedId(), "48D5181DB8704F8AB5FC998964AD9075");
}
}
The problem is that when I am reading my JSON like this:
JsonEvaluationPart partType = objectMapper.readValue(JSON, JsonEvaluationPart.class);
All the properties in partType are null. What I am doing wrong here and how to solve this?
According to the documentation JsonIgnoreProperties means:
Annotation that can be used to either suppress serialization of properties
(during serialization), or ignore processing of JSON properties read (during
deserialization).
Just try replacing:
#JsonIgnoreProperties(value = {"evaluationPart"})
with:
#JsonTypeName("evaluationPart")
Your Json Headers is wrong.
use
#JsonTypeName("evaluationPart")
instead of
#JsonIgnoreProperties(value = {"evaluationPart"})