JSON - show output as <key> : <value> pair [duplicate] - java

I have a class
class Foo {
String key;
String value;
}
and want to serialize this into "<content of key>":"<content of value>"
How can I achieve this (and how to deserialize "myKey":"myVal" into a Fooobject?
I was trying to use
#JsonValue
public String toString() {
return "\"" + key + "\":\"" + value + "\"";
}
But clearly end up with too many quotes.
#JsonValue
public String toString() {
return key + ":" + value;
}
also does not work, as it does not create enough quotes.

I found one way, which is using a JsonSerializer like this:
public class PropertyValueSerializer extends JsonSerializer<Foo> {
#Override
public void serialize(Foo property_value, JsonGenerator jsonGenerator,
SerializerProvider serializerProvider) throws IOException, JsonProcessingException {
jsonGenerator.writeStartObject();
jsonGenerator.writeFieldName(property_value.getKey());
jsonGenerator.writeString(property_value.getValue());
jsonGenerator.writeEndObject();
}
The Foo class needs to know about this:
#JsonSerialize(using = PropertyValueSerializer.class)
public class Foo {
Deserializing is very similar:
public class PropertyValueDeserializer extends JsonDeserializer<PROPERTY_VALUE> {
#Override
public Foo deserialize(JsonParser jsonParser,
DeserializationContext deserializationContext) throws IOException, JsonProcessingException {
String tmp = jsonParser.getText(); // {
jsonParser.nextToken();
String key = jsonParser.getText();
jsonParser.nextToken();
String value = jsonParser.getText();
jsonParser.nextToken();
tmp = jsonParser.getText(); // }
Foo pv = new Foo(key,value);
return pv;
}
And this also needs to be annotated on the Foo class:
#JsonSerialize(using = PropertyValueSerializer.class)
#JsonDeserialize(using = PropertyValueDeserializer.class)
public class Foo implements Serializable{

Related

Generic Enum Json Deserialization

I needed a better hibernate enum mapping and this page served me well (Except I used char type instead of int).
Next question is how can I serialize/deserialize an enum in a generic way?
Think of a Gender enum:
#JsonSerialize(using = PersistentEnumSerializer.class)
#JsonDeserialize(using = PersistentEnumDeserializer.class)
public enum Gender implements PersistentEnum {
MALE("M", "Male"), FEMALE("F", "Female");
private String code;
private String display;
Gender(String code, String display) {
this.code = code;
this.display = display;
}
public String getName() {
return name();
}
public String getCode() {
return code;
}
public String getDisplay() {
return display;
}
public String toString() {
return display;
}
}
which implements getName(), getCode() and getDisplay() methods of PersistentEnum interface. Serializing is easy:
public class PersistentEnumSerializer extends JsonSerializer<PersistentEnum> {
#Override
public void serialize(PersistentEnum object, JsonGenerator generator, SerializerProvider provider) throws IOException, JsonProcessingException {
generator.writeStartObject();
generator.writeFieldName("name");
generator.writeString(object.getName());
generator.writeFieldName("code");
generator.writeString(object.getCode());
generator.writeFieldName("display");
generator.writeString(object.getDisplay());
generator.writeEndObject();
}
}
but how can I deserialize in java 6? In java 8, I would add a static method to PersistentEnum interface.
public class PersistentEnumDeserializer extends JsonDeserializer<PersistentEnum> {
#Override
public PersistentEnum deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
JsonNode node = jp.getCodec().readTree(jp);
//String value = node.get("name").asText();
//TODO Somehow I need to get Gender.MALE if the json is {"name":"MALE","code":"M","display":"Male"}
return null;
}
}
One possible solution is to add a new method getType() to PersistentEnum which will identify the type of Enum.
#JsonSerialize(using = PersistentEnumSerializer.class)
#JsonDeserialize(using = PersistentEnumDeserializer.class)
public enum Gender implements PersistentEnum {
#Override
public String getType() {
return "gender";
}
}
Serializer should also be modified to include type while serialization.
public class PersistentEnumSerializer extends JsonSerializer<PersistentEnum> {
#Override
public void serialize(PersistentEnum object, JsonGenerator generator, SerializerProvider provider) throws IOException, JsonProcessingException {
generator.writeStartObject();
generator.writeFieldName("name");
generator.writeString(object.getName());
generator.writeFieldName("code");
generator.writeString(object.getCode());
generator.writeFieldName("display");
generator.writeString(object.getDisplay());
generator.writeFieldName("type");
generator.writeString(object.getType());
generator.writeEndObject();
}
}
Deserializer can be written as shown below.
public class PersistentEnumDeserializer extends JsonDeserializer<PersistentEnum> {
#Override
public PersistentEnum deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
JsonNode node = jp.getCodec().readTree(jp);
return findEnum(node.get("type").asText(), node.get("name").asText());
}
private PersistentEnum findEnum(String type, String name) {
switch (type) {
case "gender":
return Gender.valueOf(name);
// handle other types here.
default:
return null;
}
}
}
While #Justin Jose's solution is not the one I'm looking for (because for each enum we need to add to findEnum method), it gave me a good hint.
If getType is implemented like this:
#Override
public String getType() {
return getClass().getSimpleName();
}
and findEnum like this
private PersistentEnum findEnum(String type, String name) {
Class<?> c = null;
try {
c = Class.forName("enums." + type); //Assuming all PersistentEnum's are in "enums" package
if (PersistentEnum.class.isAssignableFrom(c)) {
Method method = c.getMethod("name");
for (Object object : c.getEnumConstants()) {
Object enumName = method.invoke(object);
if (name.equals(enumName))
return (PersistentEnum) object;
}
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
it may work. Not tested and possibly vulnerable.

How to create array field in by jackson based on json property prefix? [duplicate]

This question already has answers here:
How to parse a JSON string to an array using Jackson
(4 answers)
Closed 6 years ago.
Say I have the following json string:
{
"name": "Foo"
"pic1": "some pic 1",
"pic2": "some pic 2",
"pic3": "some pic 3",
...
"picn": "some pic n"
}
I need to create following POJO:
class Foo {
String name;
String[] pics;
}
from this string.
The trick is I need to map pic* to String[] pics somehow.
How can I do that using Jackson?
Add your own custom serializer and deserializer.
#JsonSerialize(using = MySerializer.class)
#JsonDeserialize(using = MyDeSerializer.class)
public class Foo {
private String name;
private String[] pics;
Serializer:
public class MySerializer extends JsonSerializer<Foo> {
#Override
public void serialize(Foo value, JsonGenerator jgen, SerializerProvider serializers) throws IOException {
int i = 1;
String fieldName = "pics";
jgen.writeStartObject();
jgen.writeStringField("name", value.getName());
for (String stringValue : value.getPics()) {
jgen.writeStringField(fieldName + i, stringValue);
i++;
}
jgen.writeEndObject();
}
}
Deserializer:
public class MyDeSerializer extends JsonDeserializer<Foo> {
#Override
public Foo deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException {
ObjectCodec objectCodec = jp.getCodec();
Foo foo = new Foo();
JsonNode node = objectCodec.readTree(jp);
Iterator<Entry<String, JsonNode>> fields = node.fields();
String[] pics = new String[node.size() - 1];
int i = 0;
while (fields.hasNext()) {
Entry<String, JsonNode> next = fields.next();
if (next.getKey().equalsIgnoreCase("name"))
foo.setName(node.get("name").asText());
else {
pics[i] = node.get(next.getKey()).asText();
i++;
}
}
foo.setPics(pics);
return foo;
}
}

Serialize/Deserialize custom Map<Key, Object> in Jackson

I have a pretty simple Map I want to serialize and deserialize in Jackson, but I can't get it to work.
I have tried the following:
#JsonSerialize(keyUsing=TurnKeySerializer.class)
#JsonDeserialize(keyUsing = TurnKeyDeserializer.class)
Map<TurnKey, PlayerTurn> publicTurns = new TreeMap<>();
#JsonIgnoreProperties(ignoreUnknown = true)
#Data //Creates Getter/Setter etc
public class TurnKey implements Comparable<TurnKey> {
private final int turnNumber;
private final String username;
public TurnKey(int turnNumber, String username) {
this.turnNumber = turnNumber;
this.username = username;
}
#Override
public int compareTo(TurnKey o) {
int v = Integer.valueOf(turnNumber).compareTo(o.getTurnNumber());
if (v != 0) {
return v;
}
return username.compareTo(o.getUsername());
}
#Override
public String toString() {
return "{" +
"turnNumber:" + turnNumber +
", username:'" + username + "'" +
"}";
}
public class TurnKeySerializer extends JsonSerializer<TurnKey> {
#Override
public void serialize(TurnKey value, JsonGenerator gen, SerializerProvider serializers) throws IOException, JsonProcessingException {
if (null == value) {
throw new IOException("Could not serialize object to json, input object to serialize is null");
}
StringWriter writer = new StringWriter();
ObjectMapper mapper = new ObjectMapper();
mapper.writeValue(writer, value);
gen.writeFieldName(writer.toString());
}
}
public class TurnKeyDeserializer extends KeyDeserializer {
private static final ObjectMapper mapper = new ObjectMapper();
#Override
public TurnKey deserializeKey(String key, DeserializationContext ctxt) throws IOException, JsonProcessingException {
return mapper.readValue(key, TurnKey.class);
}
}
But I get an exception
com.fasterxml.jackson.databind.JsonMappingException: Can not deserialize instance of java.util.LinkedHashMap out of START_ARRAY token
You need to define/override the fromString() method in TurnKey. Jackson will use toString() to serialize and fromString() to deserialize. That's what "Can not find a (Map) Key deserializer" means in the error message Caused by: com.fasterxml.jackson.databind.JsonMappingException: Can not find a (Map) Key deserializer for type [simple type, class no.asgari.civilization.server.model.TurnKey] at com.fasterxml.jackson.databind.deser.DeserializerCache._handleUnknownKeyDeserializer(DeserializerCache.java:584).
A custom KeyDeserializer is not needed.

Java binary serializing fails because of jackson

I use jackson 2 to convert json into a java object. So far so good. But I also use hazelcast to distribute the objects in a cluster. Therefore all beans have to be java.io.Serializable. When I read the Object from json like so:
ObjectMapper mapper = new ObjectMapper();
mapper.addMixInAnnotations(AbstractBean.class, MongoIdMixIn.class);
// this is to prevent from failing on missing type class property: #JsonProperty("#class")
Object tgtObject = targetClass.newInstance();
mapper.readerForUpdating(tgtObject).readValue(dbo.toString());
// put into hazelcast map
target.put(dbo.get(keyColumn), tgtObject);
I will get an exception from hazelcast:
java.io.NotSerializableException: com.fasterxml.jackson.databind.deser.impl.TypeWrappedDeserializer
I am wondering where the com.fasterxml.jackson.databind.deser.impl.TypeWrappedDeserializer is coming from since the Object is a plain java bean (but using inheritance).
My Abstract class is:
#JsonSerialize(include=JsonSerialize.Inclusion.NON_NULL)
#JsonIgnoreProperties(ignoreUnknown = true)
#JsonTypeInfo(use=JsonTypeInfo.Id.CLASS, include=JsonTypeInfo.As.PROPERTY, property="#javaClass")
public abstract class AbstractBean implements Serializable {
#JsonIgnore public static final transient IMarkupParser MARKUP_PARSER = new WikiMarkupParser();
#JsonProperty("id")
private String id;
#JsonProperty("#class")
private String clazz;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getClazz() {
return this.getClass().getSimpleName();
}
}
And my child is:
public class Posting extends AbstractBean {
private String postingSource;
private String languageCode;
public String getPostingSource() {
return postingSource;
}
public void setPostingSource(String postingSource) {
this.postingSource = postingSource;
}
public String getLanguageCode() {
return languageCode;
}
public void setLanguageCode(String languageCode) {
this.languageCode = languageCode;
}
}
I have no Idea why the serailizer would even try to serialize the mixins since the are not part of the bean but here they are (yes I have tried to make them serializable too, just as a test, no luck):
public interface IdMixins extends Serializable {
}
public interface MongoIdMixIn extends IdMixins {
#JsonProperty("_id")
#JsonSerialize(using = MongoIdSerializer.class)
public String getId();
#JsonProperty("_id")
#JsonDeserialize(using = MongoIdDeserializer.class)
public void setId(String id);
}
public class MongoIdDeserializer extends JsonDeserializer<String> implements Serializable {
private static final long serialVersionUID = -5404276857799190647L;
#Override
public String deserialize(JsonParser jp, DeserializationContext deserializationContext) throws IOException, JsonProcessingException {
String value = null;
String tmp = jp.getText(); // {
validate(jp, tmp,"{");
int curly = 1;
while (jp.nextToken() != null) {
String v = jp.getText();
if (v.equals("{")) curly++;
if (v.equals("$oid")) {
jp.nextToken();
value = jp.getText();
}
if (v.equals("}")) curly--;
if (curly<=0) return value;
}
return null;
}
private void validate(JsonParser jsonParser, String input, String expected) throws JsonProcessingException {
if (!input.equals(expected)) {
throw new JsonParseException("Unexpected token: " + input, jsonParser.getTokenLocation());
}
}
}
public class MongoIdSerializer extends JsonSerializer<String> implements Serializable {
private static final long serialVersionUID = 3435689991839324194L;
#Override
public void serialize(String s, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException, JsonProcessingException {
jsonGenerator.writeStartObject();
jsonGenerator.writeFieldName("$oid");
jsonGenerator.writeString(s);
jsonGenerator.writeEndObject();
}
}
Stupid me! Somewhere in the serialization chain was a completely unnecessary ObjectMapper object. But it was hard to find because not the Posting object was the real reason, instead it was another object. But the Stacktrace and the com.fasterxml.jackson.databind.deser.impl.TypeWrappedDeserializer Exception were completely miss leading! ... clustered software is sometimes really painful to debug :-)
I'm 1 Rep. Point away from being able to comment. So I have to make a suggestion as an answer ;-).
Perhaps one of the Annotations do inject an instance of TypeWrappedDeserializer as a private property into the AbstractBean. Maybe as hint for the deserialization mechanism.
Could you inspect the created object with reflection to verify?
for (Field field : tgtObject.getClass().getDeclaredFields() )
{
// you can replace this by your logging method
System.out.println("Field: " + field.getName() + ":" + field.getType());
}
for (Field field : tgtObject.getClass().getSuperclass().getDeclaredFields() )
{
// you can replace this by your logging method
System.out.println("Field: " + field.getName() + ":" + field.getType());
}
If you find the apropriate type in the listing the Class was added by Byte Code Enhancement.

Jackson cannot deserialize enum as object even if I add customized deserializer

I want to use Jackson JSON to serialize/deserialize a class containing an enum object. My class is:
class Bar {
#JsonProperty("rateType")
#JsonDeserialize(using = ReturnedRateTypeDeserializer.class)
private ReturnedRateType rateType;
public ReturnedRateType getRateType() {
return rateType;
}
public void setRateType(ReturnedRateType rateType) {
this.rateType = rateType;
}
}
The enum class ReturnedRateType is defined as:
#JsonFormat(shape = JsonFormat.Shape.OBJECT)
public enum ReturnedRateType {
AA("AA"),
BB("BB"),
CC("CC");
#JsonProperty("value")
private String value;
ReturnedRateType(String value) {
this.value = value;
}
#JsonCreator
public static ReturnedRateType fromValue(final String value) {
if (value != null) {
for (ReturnedRateType type : ReturnedRateType.values()) {
if (value.equalsIgnoreCase(type.value)) {
return type;
}
}
}
return null;
}
}
As you see, I added #JsonFormat annotation to tell Jackson to serialize this enum as POJO, and added #JsonCreator annotation to get a static factory method from given string to enum object. Since Jackson can only serialize but can't deserialize from object representation to enum, I added the following customized deserializer for the enum ReturnedRateType:
public class ReturnedRateTypeDeserializer extends JsonDeserializer<ReturnedRateType> {
#Override
public ReturnedRateType deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
ReturnedRateType type = ReturnedRateType.fromValue(jp.getValueAsString());
if(type != null)
return type;
throw new JsonMappingException("invalid value for ReturnedRateType");
}
}
But when I tested deserialization from a JSON string to enum, I got the error. The JSON string is:
{"rateType": {"value": "AA"}}
My test code is:
#Test
public void RateTypeToEnum() {
String json = "{\"rateType\": {\"value\": \"AA\"}}";
System.out.println(json);
ObjectMapper mapper = new ObjectMapper();
Bar bar = null;
try {
bar = mapper.readValue(json, Bar.class);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(bar.getRateType());
}
I expect to see the output should be AA. But jp.getValueAsString() in my customized deserializer ReturnedRateTypeDeserializer is null during the execution:
ReturnedRateType type = ReturnedRateType.fromValue(jp.getValueAsString()); //jp.getValueAsString() is null here!
Thus it returns error. So what is wrong here?
According to the Jackson 2.5.X documentation on the JsonFormat annotation the Shape.Object does not work for the enum deserialisation:
Enums: Shapes JsonFormat.Shape.STRING and JsonFormat.Shape.NUMBER can
be used to change between numeric (index) and textual (name or
toString()); but it is also possible to use JsonFormat.Shape.OBJECT
to serialize (but not deserialize).
I'd make the JsonCreator static method accept a JsonNode and read the string value from it.
Note that this would work since 2.5.X. In early versions you would need to write a custom deserialiser. Here is an example:
public class JacksonEnumObjectShape {
#JsonFormat(shape = JsonFormat.Shape.OBJECT)
#JsonDeserialize(using = ReturnedRateTypeDeserializer.class)
public enum ReturnedRateType {
AA("AA"),
BB("BB"),
CC("CC");
#JsonProperty("value")
private String value;
ReturnedRateType(String value) {
this.value = value;
}
#JsonCreator
public static ReturnedRateType fromValue(final JsonNode jsonNode) {
for (ReturnedRateType type : ReturnedRateType.values()) {
if (type.value.equals(jsonNode.get("value").asText())) {
return type;
}
}
return null;
}
}
// can be avoided since 2.5
public static class ReturnedRateTypeDeserializer extends JsonDeserializer<ReturnedRateType> {
#Override
public ReturnedRateType deserialize(
final JsonParser jp,
final DeserializationContext ctxt) throws IOException {
final JsonNode jsonNode = jp.readValueAsTree();
return ReturnedRateType.fromValue(jsonNode);
}
}
public static void main(String[] args) throws IOException {
final ObjectMapper mapper = new ObjectMapper();
final String json = mapper.writeValueAsString(ReturnedRateType.AA);
System.out.println(json);
System.out.println(mapper.readValue(json, ReturnedRateType.class));
}
}
Output:
{"value":"AA"}
AA

Categories