I have a POJO which has a field:
public class Media {
private Asset asset;
}
Everything works perfectly when parsing a json response into this asset POJO. but however there is a slight difference with the key this asset comes with. It can either be:
#JsonProperty("cover_asset")
or
#JsonProperty("asset")
Is there a way to annotate the POJO to recognize this case and de-serialize into the same field. Its not possible for both of them to appear in the same response.
Well, as only deserialization is your concern, #JsonAlias introduced in 2.9 is perfect for this situation. You can do something like this:
#JsonAlias({"cover_asset", "asset"})
private Asset asset;
#JsonAlias docs:
Annotation that can be used to define one or more alternative names
for a property, accepted during deserialization as alternative to the
official name. Alias information is also exposed during POJO
introspection, but has no effect during serialization where primary
name is always used.
Note: Make sure you update all related dependencies(annotations, core, databind) if you are using them. Updating just annotations without others threw me runtime error.
More succinctly, I would suggest using two separate #JsonSetter annotations for this. Here's a working example. This means that your java class will only have one getter method to use for the property instead of two. You can also make the setter you don't want exposed to clients of Media private and treat one of the json keys in a special manner.
import com.fasterxml.jackson.annotation.JsonGetter;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonSetter;
import com.fasterxml.jackson.databind.ObjectMapper;
#SuppressWarnings("unused")
public class Media {
private Asset asset;
#JsonGetter("asset")
public Asset getAsset() {
return asset;
}
#JsonSetter("asset")
public void setAsset(Asset asset) {
this.asset = asset;
}
#JsonSetter("cover_asset")
private void setMediaAsset(Asset asset) {
if (this.asset == null) {
setAsset(asset);
}
}
private static class Asset {
#JsonProperty("foo")
private String foo;
}
public static void main(String[] args) throws Exception {
String withAsset = "{'asset': {'foo':'bar'}}";
String withCoverAsset = "{'cover_asset': {'foo':'bar'}}";
ObjectMapper mapper = new ObjectMapper();
Media mediaFromAsset = mapper.readValue(withAsset.replace('\'','"'), Media.class);
Media mediaFromCoverAsset = mapper.readValue(withCoverAsset.replace('\'','"'), Media.class);
System.out.println(mediaFromAsset.asset.foo.equals(mediaFromCoverAsset.asset.foo));
}
}
Great answer By Vikas with JsonAlias.
Just adding that you can also benefit from both of the worlds (JsonProperty&Alias) [Since jackson 2.9]:
#JsonProperty("cover_asset")
#JsonAlias({"asset", "cover_asset","amazing_asset"})
private Asset asset;
Reference.
I'd propose to use getters/setters, for both property names, which are referring to the same POJO field.
public class Media {
private Asset asset;
#JsonProperty("cover_asset")
public Asset getCoverAsset() {
return asset;
}
public void setCoverAsset(Asset asset) {
this.asset= asset;
}
#JsonProperty("asset")
public Asset getAsset() {
return asset;
}
public void setAsset(Asset asset) {
this.asset= asset;
}
}
See also my answer to possible duplicate question:
Different names of JSON property during serialization and deserialization
Related
I have a DTO class which has a property like:
#JsonIgnoreProperties(ignoreUnknown = true)
public class WPPostResponse {
#JsonProperty("featuredMedia")
Long featured_media;
public Long getFeatured_media() {
return featured_media;
}
public void setFeatured_media(Long featured_media) {
this.featured_media = featured_media;
}
}
The input JSON has the key featured_media. I convert the JSON string to the object and then sends it to the client response as JSON. I want the final response JSON to have featuredMedia as the key. I am however getting null as the value. If I remove the JsonProperty, it gives the value, but the key is having underscore. How to fix this? Thanks.
Always respect the Java naming conventions in your Java code. Use annotations to deal with Json not respecting them.
In this case, use JsonAlias
Annotation that can be used to define one or more alternative names for a property, accepted during deserialization as alternative to the official name
public class WPPostResponse {
#JsonAlias("featured_media")
Long featuredMedia;
public Long getFeaturedMedia() {
return featuredMedia;
}
public void setFeaturedMedia(Long featuredMedia) {
this.featuredMedia = featuredMedia;
}
}
You can use the JsonProperty on setters and getters to have different namings during serialization and deserialization
#JsonIgnoreProperties(ignoreUnknown = true)
public class WPPostResponse {
Long featuredMedia;
#JsonProperty("featuredMedia") // output will be featuredMedia
public Long getFeatured_media() {
return featuredMedia;
}
#JsonProperty("featured_media") // input should be featured_media
public void setFeatured_media(Long featured_media) {
this.featuredMedia = featured_media;
}
}
And also you set access level to #JsonProperty annotation
#JsonProperty(value = "featured_media", access = JsonProperty.Access.WRITE_ONLY)
I'm trying to serialise an object with a Long id to JSON, using Genson.
It works well if I serialise to JSON and back into Java. But I'm deserialising in JavaScript.
JavaScript can't support a full 64-bit unsigned int as a Number (I'm finding the last few bits of my id are being zeroed in JavaScript), so I need to convert the Long id to a String during serialisation.
I don't want to convert all the Longs in the Object, so I'm trying to use a Converter for just the id field.
import com.owlike.genson.annotation.JsonConverter;
import javax.persistence.Id;
import lombok.Getter;
import lombok.Setter;
...
/** the local database ID for this order */
#JsonConverter(LongToStringConverter.class)
#Id
#Setter
#Getter
private Long id;
/** The unique ID provided by the client */
#Setter
#Getter
private Long clientKey;
My converter code looks like this:
public class LongToStringConverter implements Converter<Long> {
/** Default no-arg constructor required by Genson */
public LongToStringConverter() {
}
#Override
public Long deserialize(ObjectReader reader, Context ctx) {
return reader.valueAsLong();
}
#Override
public void serialize(Long obj, ObjectWriter writer, Context ctx) {
if (obj != null) {
writer.writeString(obj.toString());
}
}
}
I'm not doing anything special when invoking serialisation itself:
Genson genson = new GensonBuilder().useIndentation(true).create();
String json = genson.serialize( order );
This doesn't work. Output still looks like this:
{
"clientKey":9923001278,
"id":1040012110000000002
}
What I'm trying to achieve is this:
{
"clientKey":9923001278,
"id":"1040012110000000002" // value is quoted
}
I did also try to pass my Converter into a GensonBuilder but that hits all the Longs in the object, which isn't what I need.
Any advice?
Well, I'm not clear on WHY but it looks like Genson just doesn't get presented with the annotation. It may be down to the use of Hibernate or Lombok.
The solution seems to be to force Genson to consider the annotated field.
I did this by using the GensonBuilder:
Genson genson = new GensonBuilder().useIndentation(true).include("id").create();
String json = genson.serialize( order );
EDIT:
Incorporating Eugen's answer above, this also works because it instructs Genson to look at the private field instead of relying on the getter/setter:
Genson genson2 = new GensonBuilder().useFields(true, VisibilityFilter.PRIVATE).useMethods(true).create();
I am trying to implement a JSON serialization in Java with Genson 1.3 for polymorphic types, including:
Numbers
Arrays
Enum classes
The SSCCE below demonstrates roughly what I am trying to achieve:
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import com.owlike.genson.Genson;
import com.owlike.genson.GensonBuilder;
/**
* A Short, Self Contained, Compilable, Example for polymorphic serialization
* and deserialization.
*/
public class GensonPolymoprhicRoundTrip {
// our example enum
public static enum RainState {
NO_RAIN,
LIGHT_RAIN,
MODERATE_RAIN,
HEAVY_RAIN,
LIGHT_SNOW,
MODERATE_SNOW,
HEAVY_SNOW;
}
public static class Measurement<T> {
public T value;
public int qualityValue;
public String source;
public Measurement() {
}
public Measurement(T value, int qualityValue, String source) {
this.value = value;
this.qualityValue = qualityValue;
this.source = source;
}
}
public static class DTO {
public List<Measurement<?>> measurements;
public DTO(List<Measurement<?>> measurements) {
this.measurements = measurements;
}
}
public static void main(String... args) {
Genson genson = new GensonBuilder()
.useIndentation(true)
.useRuntimeType(true)
.useClassMetadataWithStaticType(false)
.addAlias("RainState", RainState.class)
.useClassMetadata(true)
.create();
DTO dto = new DTO(
new ArrayList(Arrays.asList(
new Measurement<Double>(15.5, 8500, "TEMP_SENSOR"),
new Measurement<double[]>(new double[] {
2.5,
1.5,
2.0
}, 8500, "WIND_SPEED"),
new Measurement<RainState>(RainState.LIGHT_RAIN, 8500, "RAIN_SENSOR")
)));
String json = genson.serialize(dto);
System.out.println(json);
DTO deserialized = genson.deserialize(json, DTO.class);
}
}
Numbers and Arrays worked well out-of-the-box, but the enum class is providing a bit of a challenge. In this case the serialized JSON form would have to be IMO a JSON object including a:
type member
value member
Looking at the EnumConverter class I see that I would need to provide a custom Converter. However I can't quite grasp how to properly register the Converter so that it would be called during deserialization. How should this serialization be solved using Genson?
Great for providing a complete example!
First problem is that DTO doesn't have a no arg constructor, but Genson supports classes even with constructors that have arguments. You just have to enable it via the builder with 'useConstructorWithArguments(true)'.
However this will not solve the complete problem. For the moment Genson has full polymorphic support only for types that are serialized as a json object. Because Genson will add a property called '#class' to it. There is an open issue for that.
Probably the best solution that should work with most situations would be to define a converter that automatically wraps all the values in json objects, so the converter that handles class metadata will be able to generate it. This can be a "good enough" solution while waiting for it to be officially supported by Genson.
So first define the wrapping converter
public static class LiteralAsObjectConverter<T> implements Converter<T> {
private final Converter<T> concreteConverter;
public LiteralAsObjectConverter(Converter<T> concreteConverter) {
this.concreteConverter = concreteConverter;
}
#Override
public void serialize(T object, ObjectWriter writer, Context ctx) throws Exception {
writer.beginObject().writeName("value");
concreteConverter.serialize(object, writer, ctx);
writer.endObject();
}
#Override
public T deserialize(ObjectReader reader, Context ctx) throws Exception {
reader.beginObject();
T instance = null;
while (reader.hasNext()) {
reader.next();
if (reader.name().equals("value")) instance = concreteConverter.deserialize(reader, ctx);
else throw new IllegalStateException(String.format("Encountered unexpected property named '%s'", reader.name()));
}
reader.endObject();
return instance;
}
}
Then you need to register it with a ChainedFactory which would allow you to delegate to the default converter (this way it works automatically with any other type).
Genson genson = new GensonBuilder()
.useIndentation(true)
.useConstructorWithArguments(true)
.useRuntimeType(true)
.addAlias("RainState", RainState.class)
.useClassMetadata(true)
.withConverterFactory(new ChainedFactory() {
#Override
protected Converter<?> create(Type type, Genson genson, Converter<?> nextConverter) {
if (Wrapper.toAnnotatedElement(nextConverter).isAnnotationPresent(HandleClassMetadata.class)) {
return new LiteralAsObjectConverter(nextConverter);
} else {
return nextConverter;
}
}
}).create();
The downside with this solution is that useClassMetadataWithStaticType needs to be set to true...but well I guess it is acceptable as it's an optim and can be fixed but would imply some changes in Gensons code, the rest still works.
If you are feeling interested by this problem it would be great you attempted to give a shot to that issue and open a PR to provide this feature as part of Genson.
I was experimenting with Jackson 2.0 mixins to serialize a class with no annotations.
Simplified source code below. Note that I'm not using getters/setters, but it seemed like I should still be able to use mixins according to the documentation.
public class NoAnnotation {
private Date created;
private String name;
// make one with some data in it for the test
static NoAnnotation make() {
NoAnnotation na= new NoAnnotation();
na.created = new Date();
na.name = "FooBear";
return na;
}
// my Mixin "class"
static class JacksonMixIn {
JacksonMixIn(#JsonProperty("created") Date created,
#JsonProperty("name") String name)
{ /* do nothing */ }
}
// test code
public static void main(String[] args) throws Exception {
NoAnnotation na = NoAnnotation.make();
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.addMixInAnnotations(NoAnnotation.class, JacksonMixIn.class);
String jsonText = objectMapper.writeValueAsString(na);
System.out.println(jsonText);
}
}
When I run main I get
Exception in thread "main" com.fasterxml.jackson.databind.JsonMappingException: No serializer found for class com.flyingspaniel.so.NoAnnotation and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationConfig.SerializationFeature.FAIL_ON_EMPTY_BEANS) )
at com.fasterxml.jackson.databind.ser.impl.UnknownSerializer.failForEmpty(UnknownSerializer.java:51)
at com.fasterxml.jackson.databind.ser.impl.UnknownSerializer.serialize(UnknownSerializer.java:25)
at com.fasterxml.jackson.databind.ser.DefaultSerializerProvider.serializeValue(DefaultSerializerProvider.java:108)
at com.fasterxml.jackson.databind.ObjectMapper._configAndWriteValue(ObjectMapper.java:2407)
at com.fasterxml.jackson.databind.ObjectMapper.writeValueAsString(ObjectMapper.java:1983)
at com.flyingspaniel.so.NoAnnotation.main(NoAnnotation.java:49)
When I follow the instructions in the Exception and add a line
objectMapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
I no longer get an exception, but the result is an empty JSON object, {}.
If I make the fields public it works, but that is not something I want to do, as it's not a reasonable object design.
I'm guessing that I am leaving out a basic "setThis" step somewhere, but don't know what. How can I get mixins to work in this situation?
I figured it out. If you want to access private fields, you need to play with the Visibility by adding the following line:
objectMapper.setVisibilityChecker(VisibilityChecker.Std.defaultInstance()
.withFieldVisibility(Visibility.ANY));
For protected fields, you could also use Visibility.PROTECTED_AND_PUBLIC.
Full example
// test code
public static void main(String[] args) throws Exception {
NoAnnotation na = NoAnnotation.make();
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.addMixInAnnotations(NoAnnotation.class, JacksonMixIn.class);
objectMapper.setVisibilityChecker(VisibilityChecker.Std.defaultInstance()
.withFieldVisibility(Visibility.ANY));
String jsonText = objectMapper.writeValueAsString(na);
System.out.println(jsonText);
}
If you want use the annotation mixin the correct way to declare it is:
static class JacksonMixIn {
#JsonProperty Date created;
#JsonProperty String name;
}
When done in this way you can control the fields to serialize simply including/excluding them from the mix in.
As mentioned in your self-answer, changing the field visibility checker will resolve this situation. As an alternative to modifying the ObjectMapper, this can be done with a purely annotation-based solution by using the #JsonAutoDetect annotation:
#JsonAutoDetect(fieldVisibility = Visibility.ANY)
static class JacksonMixIn {
JacksonMixIn(#JsonProperty("created") Date created,
#JsonProperty("id") int id)
{ /* do nothing */ }
}
I am using SimpleXml 2.6.1 in my android app. Eventhough the documentation (http://simple.sourceforge.net/download/stream/doc/javadoc/index.html?org/simpleframework/xml/Order.html) says the order of the elements in the xml are same as the way they have defined in the class file, I am always getting the order to be random in the xml. If I add few more variables, the order of the elements again changes.
Adding #Order notation works, but since the class is complex with 100s of variables, I do not want to add order. Is this a known bug for android versions? It works fine in java console programs.
p.s: I opened the .class file disassembled and found the variables declared in the same order as java file, so I don't think it's a class file issue.
import org.simpleframework.xml.Element;
import org.simpleframework.xml.Order;
#Order(elements = {"name", "isTrue"})
public class SimpleXml {
public static final String NAME = "$NAME$";
public static final String IS_TRUE = "$IS_TRUE$";
#Element
private String name;
#Element
private Boolean isTrue;
...
Since there is no answer, I'll try to save precious time to anyone who gets here.
I found no cause, and since I don't have time to analyze Simple libraries, I came up with a "workaroud". It's more of an advice, actually - don't use it for (marshaling)creating xml if you have a large xml definition and the order matters(a rule more than an exception). The order is mostly used for marshaling anyway so just save yourself some time and do it manually.
The template:
<document>
<name>$NAME$</name>
<isTrue>$IS_TRUE$</isTrue>
</document>
The class:
import org.apache.commons.io.IOUtils;
import java.io.IOException;
import java.io.InputStream;
/**
* User: ksaric
*/
public class SimpleXml {
public static final String NAME = "$NAME$";
public static final String IS_TRUE = "$IS_TRUE$";
private String name;
private Boolean isTrue;
public SimpleXml() {
}
public Boolean getTrue() {
return isTrue;
}
public void setTrue(Boolean aTrue) {
isTrue = aTrue;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
#Override
public String toString() {
String template = null;
try {
template = getTemplate();
} catch (IOException e) {
e.printStackTrace();
}
/* GUAVA - checkNotNull() */
if (null == template) return null;
template = template.replace(NAME, getName());
/* OR CONVERT IN THE GETTER METHOD */
template = template.replace(IS_TRUE, getTrue().toString());
return template;
}
/* SINGLETON? Performance(IO) loss... */
public String getTemplate() throws IOException {
InputStream templateStream = getClass().getResourceAsStream("/template.xml");
/* APACHE IO COMMONS */
/*
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-io</artifactId>
<version>1.3.2</version>
</dependency>
*/
final String stringTemplate = IOUtils.toString(templateStream);
return stringTemplate;
}
}
The test:
import org.junit.Test;
import static junit.framework.Assert.*;
/**
* User: ksaric
*/
public class SimpleXmlTest {
#Test
public void test() throws Exception {
//Before
/* Use standard instantiation, factory method recommended for immutability */
SimpleXml simpleXml = new SimpleXml();
simpleXml.setName("This is a name");
simpleXml.setTrue(false);
//When
String result = simpleXml.toString();
//Then
assertNotNull(result);
System.out.println(result);
}
}
Not really an answer, but save yourself some time and don't use Simple(which is a great library) on Android...
Simple Xml doesn't preserve Order on Android. Based on pfh's answer, here is my recommendation:
I would prefer to use JAXB in the case where you want the order to be preserved than manual string/template parsing. JAXB is slightly complex to use than SimpleXml but comes with similar set of annotations based xml serialization and deserialization.