Jackson: Serialize an EER specialization - java

I'm using spring boot with jackson to provide an API.
In my DB there is a generalization/specialization field. That means, I have two fields: a value and a field that defines the data type of the value.
So my entity looks like this:
public class Example{
private String value;
private String type;
// getters and setters
}
For example with these three entries:
"1234";"Integer"
"example string";"String"
"true";"Boolean"
Jackson serialize this as follows:
[{
"value": "1234",
"type": "Integer"
},
{
"value": "example string",
"type": "String"
},
{
"value": "true",
"type": "Boolean"
}]
This is what I expteced, but, I need this:
[{
"value": 1234,
"type": "Integer"
},
{
"value": "example string",
"type": "String"
},
{
"value": true,
"type": "Boolean"
}]
So, the values should be serialized according to the value of the "type" field.
I know that I can attach a serializer to the "value" field, but I cannot access the "type" field there to know how to serialize the value.

One option is to change your Example class like this if you can :
public static class Example {
private String value;
private String type;
#JsonIgnore
public String getValue() {
return value;
}
#JsonProperty("value")
#JsonRawValue
public Object getRawValue() {
if("String" .equals(type)){
return '"'+ value +'"';
}
return value;
}
public void setValue(String value) {
this.value = value;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
}
Or you could use deserializer on Example class
public static void main(String[] args) throws JsonProcessingException {
ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.addSerializer(Example.class, new MySerializer());
mapper.registerModule(module);
System.out.println(mapper.writeValueAsString(list));
}
private static class MySerializer extends JsonSerializer<Example> {
#Override
public void serialize(Example value, JsonGenerator gen, SerializerProvider serializers)
throws IOException {
gen.writeStartObject();
gen.writeFieldName("value");
if (value.getType().equals("String")) {
gen.writeString( value.getValue());
} else {
gen.writeRawValue(value.getValue());
}
gen.writeStringField("type", value.getType());
gen.writeEndObject();
}
}
Result:
[{"value":1234,"type":"Integer"},{"value":"example string","type":"String"},{"value":true,"type":"Boolean"}]
Update
Here is one way how you could use custom deserializer and default one for every other field:
private static class MySerializer extends JsonSerializer<Example> {
private static final ObjectMapper internalMapper = new ObjectMapper();
#Override
public void serialize(Example value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
ObjectNode node = internalMapper.valueToTree(value);
if (value.getType().equals("String")) {
node.put("value", value.getValue());
} else {
node.putRawValue("value", new RawValue(value.getValue()));
}
gen.writeObject(node);
}
}
Will need to have #JsonIgnore in your public String getValue()

Related

can create custom json string with instantiated data class in java

There is a class defined follows:
#Data // lombok
public class MyData {
#Required // my custom annotation
String testValue1;
Integer testValue2;
}
And myData is instantiated like that:
MyData myData = new MyData();
myData.setTestValue1("test1");
myData.setTestValue2(123);
I want to serialize myData as json string as follows:
{
"testValue1": {
"type": "String",
"isRequired": "true",
"value": "test1"
},
"testValue2": {
"type": "Integer",
"isRequired": "false",
"value": "123"
},
}
Is there a good way to create json string?
edit|
I put quotes on json string that to be able to valid.
I want to set key as field name and create additional field information.
set field type on "type" key and
if field has #Required annotation, set true on "isRequired" and
set instantiated field value on "value".
So I played a bit around with Jackson Serialization and came to this result (certainly unfinished and not fully tested, but works with your given object).:
Module to make Spring / Jackson known of the new Serializer.
#JsonComponent
public class TestSerializerModule extends SimpleModule {
#Override
public String getModuleName() {
return TestSerializerModule.class.getSimpleName();
}
#Override
public Version version() {
return new Version(
1,
0,
0,
"",
TestSerializerModule.class.getPackage().getName(),
"TestModule"
);
}
#Override
public void setupModule(SetupContext context) {
context.addBeanSerializerModifier(new BeanSerializerModifier() {
#Override
public JsonSerializer<?> modifySerializer(SerializationConfig config, BeanDescription beanDesc, JsonSerializer<?> serializer) {
if (beanDesc.getBeanClass().equals(MyData.class)) { //Add some smart logic here to identify your objects
return new TestSerializer();
}
return serializer;
}
});
}
}
Then the Serialisier itself:
public class TestSerializer extends StdSerializer<Object> {
protected TestSerializer() {
super(Object.class);
}
#Override
public void serialize(Object value, JsonGenerator gen, SerializerProvider provider) throws IOException {
ClassIntrospector classIntrospector = provider.getConfig().getClassIntrospector();
BasicBeanDescription beanDescription = (BasicBeanDescription) classIntrospector.forSerialization(provider.getConfig(), provider.constructType(value.getClass()), null);
// Start of the MyValue Object
gen.writeStartObject();
beanDescription.findProperties().forEach(p -> {
// Requiered if Annoation is present
boolean required = p.getField().hasAnnotation(Required.class);
try {
// Write all the wanted fields
gen.writeFieldName(p.getName());
gen.writeStartObject();
gen.writeBooleanField("isRequired", required);
gen.writeStringField("type", p.getField().getRawType().getSimpleName());
gen.writeFieldName("value");
Object value1 = p.getGetter().getValue(value);
// Use existing serializer for the value provider.findValueSerializer(value1.getClass()).serialize(value1, gen, provider);
gen.writeEndObject();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
);
gen.writeEndObject();
}
}
Running this test :
#JsonTest
class TestSerializerTest {
#Autowired
ObjectMapper objectMapper;
#Test
public void testSerializer() throws Exception {
MyData value = new MyData();
value.setTestValue1("test1");
value.setTestValue2(123);
String s = objectMapper.writeValueAsString(value);
System.out.println(s);
}
}
gives me this output:
{"testValue1":{"isRequired":false,"type":"String","value":"test1"},"testValue2":{"isRequired":false,"type":"Integer","value":123}}
Hope that gives you an idea where to start and how to proceed from here!

Change value of specific annotated fields of an object which is used as a type for multiple fields in some object - Jackson Object Mapper Serialize

I have a Person class:
public class Person {
private String name;
private String address;
}
Now this Person class is used in another class as:
public class Group {
private Person nicePerson;
private Person badPerson;
private Person secretPerson;
}
Now, I want to serialize my Group object to JSON using Jackson ObjectMapper. I'm using a custom serializer based on some annotation.
When I serialize the Group object, I'm looking for the following output:
{
"nicePerson": {
"name": "John",
"address": "usa"
},
"badPerson": {
"name": "Harold",
"address": "canada"
},
"secretPerson": {
"name": "XXXXXXX",
"address": "london"
}
}
Here, I want to change the name of 'secretPerson' to "XXXXXXX".
But I'm having some problems.
I have made following annotation:
#Retention(RetentionPolicy.RUNTIME)
#Target({ElementType.METHOD, ElementType.FIELD})
public #interface SecretInfo {
String maskedValue = "XXXXXXX";
String value() default maskedValue;
}
I have annotated my Person class following way:
public class Person {
#SecretInfo
private String name;
private String address;
}
I have made the following serializer which looks for this annotation and then changes the value. It is using some generics now for convenience.
public class SecretInfoSerializer<T> extends StdSerializer<T> implements ContextualSerializer {
private SecretInfo secretInfo;
public SecretInfoSerializer() {
super((Class<T>) String.class);
}
public SecretInfoSerializer(SecretInfo secretInfo,
JavaType clazz) {
super(clazz);
this.secretInfo = secretInfo;
}
#Override
public void serialize(T value, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
if (secretInfo != null && value != null) {
jsonGenerator.writeString(secretInfo.value());
} else jsonGenerator.writeString(getValue(value));
}
#Override
public JsonSerializer<?> createContextual(SerializerProvider serializerProvider, BeanProperty property) {
if (property != null) {
return new SecretInfoSerializer<>(
property.getAnnotation(SecretInfo.class),
property.getType());
} else {
return new SecretInfoSerializer<>();
}
}
private String getValue(T value) {
if (value == null) {
return "null";
} else {
return value.toString();
}
}
}
I have associated this with Annotation Introspector and made the ObjectMapper.
Then I serialized my 'Group' class above. But I get the following output:
{
"nicePerson": {
"name": "XXXXXXX",
"address": "usa"
},
"badPerson": {
"name": "XXXXXXX",
"address": "canada"
},
"secretPerson": {
"name": "XXXXXXX",
"address": "london"
}
}
As you can see, since I annotated the 'name' field in Person class, I'm getting "XXXXXXX" for every Person class there is. But this is not what I wanted. I just want to change the value for 'secretPerson' only.
I don't know what to do:
Maybe there is some way such as following:
public class Group {
private Person nicePerson;
private Person badPerson;
#HasSecretInfo
private Person secretPerson;
}
Then the serializer would check if it #HasSecretInfo, only then look for #SecretInfo ...etc. something like this.
So I'm looking to solve this problem. If you have any solution, that'd be great.

How to deserialize a JSON file ( using Google JSON) consisting of same key name but uses different type?

Consider the following JSON File:
{
"version": "1.0",
"firstData": {
"meta": "this is string",
"version": "1"
},
"SecondData": {
"meta": ["string1", "string2", "string3"],
"version": "1"
},
"ThirdData": {
"meta": true,
"version": "1"
},
"FourthData": {
"meta": [true, false, false, true],
"version": "1"
},
"FifthData": {
"meta": [{
"meta": "string",
"version": "2"
},
{
"meta": ["string1","string2"],
"version": "2"
}]
"version": "1"
}
}
As seen, The "meta" attribute has different data type, sometimes it is String, sometimes it is ArrayOfString, sometimes Boolean etc.
Since my JSON file has several data,
I want it to follow the following Structure :
class information
{
String version;
HashMap<String,Data> details;
}
class Data
{
variable meta;
String version;
}
How do I create a corresponding POJO and deserialize it using Google GSON?
Just define your meta as JsonElement. Then you will have sort methods like: getAsString, getAsBoolean, getAsJsonObject, getAsJsonArray, ..., and also you are able to deserialize it again after you find out what is the type.
So your class could look like:
public class SomeClass {
private int version;
private JsonElement meta;
//getters and setters and other stuff
}
Edit: More elaboration and implementation
Define two classes: GeneralItem and GeneralData
class GeneralItem
{
public final int version;
public final JsonElement meta;
}
class GeneralData
{
public final String version;
public final Map<String, GeneralItem> items;
public GeneralData(String version, Map<String, GeneralItem> items)
{
this.version = version;
this.items = items;
}
}
And then we define a custom deserializer for our GeneralData:
class GeneralDataDeserializer implements JsonDeserializer<GeneralData>
{
#Override
public GeneralData deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException
{
final JsonObject object = json.getAsJsonObject();
final String version = object.get("version").getAsString();
object.remove("version");
HashMap<String, GeneralItem> items = new HashMap<>(object.size());
for (Map.Entry<String, JsonElement> item : object.entrySet())
items.put(item.getKey(), context.deserialize(item.getValue(), GeneralItem.class));
return new GeneralData(version, items);
}
}
Finally registering the deserializer to our gson instance and getting the data:
final Gson gson = new GsonBuilder()
.registerTypeAdapter(GeneralData.class, new GeneralDataDeserializer())
.create();
final String json = "your json here";
final GeneralData data = gson.fromJson(json, GeneralData.class);
System.out.println(data.items.get("firstData").meta.getAsString());
//other parts you want
(Note that constructors, getter and setters, error checking, etc. are removed for the sake of brevity)

Omitting HashMap name during gson serialization

I am looking to serialize my class using gson but I would like to omit the hashmap name. Is this possible with gson?
I have tried writing my own TypeAdapter but the map name is still written as the parent object.
I have a class which looks like
public class myClass {
#Expose
public Long timestamp;
#Expose
public String id;
#Expose
public HashMap<String, someOtherClass> myMap = new HashMap<>();
#Override
public String toString() {
Gson gson = new GsonBuilder()
.excludeFieldsWithoutExposeAnnotation()
.create();
return gson.toJson(this);
}
}
current output :
{
"timestamp": 1517245340000,
"id": "01",
"myMap": {
"mapKey1": {
"otherClassId": "100", // works as expected
}
"mapKey2": {
"otherClassId": "101", // works as expected
}
}
}
What I am hoping to get :
{
"timestamp": 1517245340000,
"id": "01",
"mapKey1": {
"otherClassId": "100", // works as expected
},
"mapKey2": {
"otherClassId": "100", // works as expected
}
}
Write your own TypeAdapter. See javadoc for example.
Specify it with #JsonAdapter annotation, or register it with GsonBuilder.
#JsonAdapter(MyClassAdapter.class)
public class MyClass {
public Long timestamp;
public String id;
public HashMap<String, SomeOtherClass> myMap = new HashMap<>();
}
public class MyClassAdapter extends TypeAdapter<MyClass> {
#Override public void write(JsonWriter out, MyClass myClass) throws IOException {
// implement the write method
}
#Override public MyClass read(JsonReader in) throws IOException {
// implement the read method
return ...;
}
}

Can not deserialize instance of out of START_OBJECT token

My Json looks something like (and its unmodifiable)
{
....
"Sale": [
"SaleLines": {
"SaleLine": [
{
"Item": {
"Prices": {
"ItemPrice": [
{
"amount": "100",
"useType": "Default"
},
{
"amount": "100",
"useType": "MSRP"
}
]
},
}
......
......
}
]
"calcDiscount": "0",
"calcSubtotal": "500",
}
]
}
The java POJO code looks like
public static class SaleLines {
#JsonProperty("SaleLine")
private SaleLineObject[] saleLineObject;
public SaleLineObject[] getSaleLineObject() { return saleLineObject; }
public void setSaleLineObject(SaleLineObject[] saleLineObject) { this.saleLineObject = saleLineObject; }
}
public static class SaleLineObject {
private SaleLine saleLine;
public SaleLine getSaleLine() {
return saleLine;
}
public void setSaleLine(SaleLine saleLine) {
this.saleLine = saleLine;
}
}
public static class SaleLine {
#JsonProperty("itemID")
private String itemId; //line_item_nk
#JsonProperty("unitPrice")
private String unitPrice;
....
}
#JsonPropertyOrder({"total", "calcSubTotal", "calcDiscount"})
public static class Sale {
private String saleTotal, calcSubtotal, calcDiscount;
private int salesValueWOVat;
#JsonProperty("SaleLines")
SaleLines saleLine;
#JsonCreator
public Sale (#JsonProperty("total")String saleTotal,
#JsonProperty("calcSubtotal")String calcSubtotal,
#JsonProperty("calcDiscount")String calcDiscount,
#JsonProperty("SaleLines")SaleLines saleLine,
) {
this.saleTotal = saleTotal;
this.calcSubtotal = calcSubtotal;
this.calcDiscount = calcDiscount;
this.saleLine = saleLine;
setSalesValueWOVat();
}
// getter and setters
}
#SuppressWarnings({ "rawtypes" })
public static <E, T extends Collection> T readFromJsonAndFillType (
String json,
Modules module,
Class <T> collectionType,
Class <E> elementType)
throws JsonParseException, JsonMappingException, IOException {
ObjectMapper objMapper = new ObjectMapper()
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
TypeFactory tf = objMapper.getTypeFactory();
JsonNode node = objMapper.readTree(json).get(module.jsonFieldName);
return objMapper.readValue(node.toString(),
tf.constructCollectionType(collectionType, elementType));
}
In main
ArrayList<Sale> saleList = readFromJsonAndFillType(
saleJSON,
Modules.SALE,
ArrayList.class,
Sale.class);
for (Sale sale: saleList) {
System.out.println(sale.toString());
}
I know this question has been asked multiple times and even I took help from for eg
Can not deserialize instance of java.util.ArrayList out of START_OBJECT token
But still I cannot get through this error
I know this question has been asked multiple times & everyone getting resolved there problems with different ways. Whenever you find "Can not deserialized instance of out of START_OBJECT token". it's generally occur when you trying to get object which is not actually same in json format (means json starting object is different not as you guys are converting).
For Ex:- Json returning first object is Boolean but unfortunately you are converting is to List<Object> then you will having this error.
I would suggest to have a look to read format using below code than convert it as per the object returning.
ObjectMapper objectMapper = new ObjectMapper();
Map<?,?> empMap = objectMapper.readValue(new FileInputStream("employee.json"),Map.class);
for (Map.Entry<?,?> entry : empMap.entrySet())
{
System.out.println("\n----------------------------\n"+entry.getKey() + "=" + entry.getValue()+"\n");
}
Get the key & convert the value as per the object returning.
For reference:- https://dzone.com/articles/processing-json-with-jackson

Categories

Resources