Encode/decode JSON keys? - java

I want to send a minified version of my JSON by minifying the keys.
The Input JSON string obtained after marshalling my POJO to JSON:
{
"stateTag" : 1,
"contentSize" : 10,
"content" : {
"type" : "string",
"value" : "Sid"
}
}
Desired JSON STRING which I want to send over the network to minimize payload:
{
"st" : 1,
"cs" : 10,
"ct" : {
"ty" : "string",
"val" : "Sid"
}
}
Is there any standard way in java to achieve this ??
PS: My json string can be nested with other objects which too I will have to minify.
EDIT:
I cannot change my POJOs to provide annotations. I have XSD files from which I generate my java classes. So changing anything there is not an option.

You can achieve this in Jackson by using #JsonProperty annotation.
public class Pojo {
#JsonProperty(value = "st")
private long stateTag;
#JsonProperty(value = "cs")
private long contentSize;
#JsonProperty(value = "ct")
private Content content;
//getters setters
}
public class Content {
#JsonProperty(value = "ty")
private String type;
#JsonProperty(value = "val")
private String value;
}
public class App {
public static void main(String... args) throws JsonProcessingException, IOException {
ObjectMapper om = new ObjectMapper();
Pojo myPojo = new Pojo(1, 10, new Content("string", "sid"));
System.out.print(om.writerWithDefaultPrettyPrinter().writeValueAsString(myPojo));
}
Outputs:
{
"st" : 1,
"cs" : 10,
"ct" : {
"ty" : "string",
"val" : "sid"
}
}
SOLUTION 2 (Using Custom Serializer):
This solution is specific to your pojo, it means for every pojo you will need a new serializer.
public class PojoSerializer extends JsonSerializer<Pojo> {
#Override
public void serialize(Pojo pojo, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException {
/* your pojo */
jgen.writeStartObject();
jgen.writeNumberField("st", pojo.getStateTag());
jgen.writeNumberField("cs", pojo.getContentSize());
/* inner object */
jgen.writeStartObject();
jgen.writeStringField("ty", pojo.getContent().getType());
jgen.writeStringField("val", pojo.getContent().getValue());
jgen.writeEndObject();
jgen.writeEndObject();
}
#Override
public Class<Pojo> handledType() {
return Pojo.class;
}
}
ObjectMapper om = new ObjectMapper();
Pojo myPojo = new Pojo(1, 10, new Content("string", "sid"));
SimpleModule sm = new SimpleModule();
sm.addSerializer(new PojoSerializer());
System.out.print(om.registerModule(sm).writerWithDefaultPrettyPrinter().writeValueAsString(myPojo));
SOLUTION 3 (Using a naming strategy):
This solution is a general solution.
public class CustomNamingStrategy extends PropertyNamingStrategyBase {
#Override
public String translate(String propertyName) {
// find a naming strategy here
return propertyName;
}
}
ObjectMapper om = new ObjectMapper();
Pojo myPojo = new Pojo(1, 10, new Content("string", "sid"));
om.setPropertyNamingStrategy(new CustomNamingStrategy());
System.out.print(om.writerWithDefaultPrettyPrinter().writeValueAsString(myPojo));

Use the annotations...
with gson:
adding #SerializedName("st") over the Class Member will serialize the variable stateTag as "st" : 1, it doesnt matter how deep in the json you are going to nest the objects.

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!

Jackson forwarding attribute instead of outer object

I have an object that is used as a return type in a REST web service (jax-rs). The object contains a field which is an array of another type. Example:
#Name("PARAMETER_DEFINITION_TABTYPE")
#TableOfDefinition
#XmlType(
name = "parameter_DEFINITION_TABTYPE"
)
class PARAMETER_DEFINITION_TABTYPE {
#XmlElement(
name = "parameter_definition_rectype",
nillable = true
)
public PARAMETER_DEFINITION_RECTYPE[] ELEMENTS;
#Override
public String toString() {
return ELEMENTS == null ? null : java.util.Arrays.toString(ELEMENTS);
}
}
I use all the existing annotations to create my SOAP web services and don't want to touch the class or the existing annotations. The REST service I create uses the same class and generates the following json:
{"parameter_definition_rectype": [
{
"name": "abc"
},
{
"name": "abss"
}
]}
I would like to get the following output (basically ignore the outer element and use only the "ELEMENTS" field):
[
{
"name": "abc"
},
{
"name": "abss"
}
]
I also want to ignore the outer object when the PARAMETER_DEFINITION_TABTYPE is nested in another Object.
Is there a way I can achieve this by using annotations?
Thanks!
You could define a custom serializer and deserializer for PARAMETER_DEFINITION_TABTYPE class which will change the way it's processed by ObjectMapper.
This is explained in the wiki Jackson How-To: Custom Serializers, if you can't add new annotations to PARAMETER_DEFINITION_TABTYPE class you should use a custom module.
It's more or less the below code. The problem here is that you have to provide a lot of custom code to get desired behavior. It would be cleaner to have a separate class to represent the JSON object because it's different than XML object:
public static void main(String[] args) throws Exception {
SimpleModule module = new SimpleModule("WrapperModule", new Version(1, 0, 0, null));
module.addSerializer(Wrapper.class, new WrapperSerializer(Wrapper.class));
module.addDeserializer(Wrapper.class, new WrapperDeserializer(Wrapper.class));
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(module);
Wrapper in = new Wrapper();
in.elements = new String[]{"a", "b", "c"};
String json = mapper.writeValueAsString(in);
System.out.println(json);
Wrapper out = mapper.readValue(json, Wrapper.class);
System.out.println(Arrays.toString(out.elements));
}
public static class Wrapper {
public String[] elements;
}
public static class WrapperSerializer extends StdSerializer<Wrapper> {
public WrapperSerializer(Class<Wrapper> t) {
super(t);
}
#Override
public void serialize(Wrapper w, JsonGenerator gen, SerializerProvider provider) throws IOException {
provider.defaultSerializeValue(w.elements, gen);
}
}
public static class WrapperDeserializer extends StdDeserializer<Wrapper> {
public WrapperDeserializer(Class<Wrapper> c) {
super(c);
}
#Override
public Wrapper deserialize(JsonParser p, DeserializationContext ctx) throws IOException {
Wrapper w = new Wrapper();
w.elements = ctx.readValue(p, String[].class);
return w;
}
}

json to java hashmap using complex key

I am currently working on an app in which I need to serialize a
HashMap<Object1, Object2> into JSON and then deserialize from JSON to the same `HashMap'.
I am able to serialize it using the usual mapper and overriding the toString() method for Object1.
public String toString(){
String res = Object1.elem1 + ";" + Object1.elem2;
return res
}
I am then able to serialize and get the expected json (where res is the String I defined before easier not to write it all back).*
{res : Object2JsonRepresentation}
Then I want to deserialize, so I use a custom keyDeserializer :
#XmlElement(name="myMap")
#JsonDeserialize(keyUsing = Object1KeyDeserializer.class)
public HashMap <Object1,Object2> myMap = new HashMap <>();
And the Object1KeyDeserializer:
public class Object1KeyDeserializer extends KeyDeserializer{
#Override
public Object1 deserializeKey(String key, DeserializationContext ctxt) throws IOException, JsonProcessingException {
String[] parts = key.split(";");
System.out.println(key);
Elem elem1 = new Elem(parts[1]);
Elem elem2 = new Elem(parts[2]);
Object1 obj = new Object1(elem1,elem2);
return obj;
}
}
Nonetheless, the keyDeserializer never seems to be called, can you explain me the reason. I'm quite new to JSON and would be glad if answers could be detailed.
Instead of using toString() you can create your own serialization format. If you have non primitive key in Map then you can serialize Map as
[
{
"key": <serialized key>,
"value: <serialized value>
},
....
]
In this case your Serializer and Deserializer will be following:
public class CustomSerializer extends StdSerializer<Map<Object1, Object2>> {
protected CustomSerializer() {
super(Map.class, true);
}
#Override
public void serialize(Map<Object1, Object2> map,
JsonGenerator jsonGenerator,
SerializerProvider serializerProvider) throws IOException{
jsonGenerator.writeStartArray();
for (Map.Entry<Object1,Object2> element: map.entrySet()) {
jsonGenerator.writeStartObject();
jsonGenerator.writeObjectField("key", element.getKey());
jsonGenerator.writeObjectField("value", element.getValue());
jsonGenerator.writeEndObject();
}
jsonGenerator.writeEndArray();
}
}
and
public class CustomDeserializer extends StdDeserializer<Map<Object1, Object2>> {
protected CustomDeserializer() {
super(Map.class);
}
#Override
public Map<Object1, Object2> deserialize(JsonParser jsonParser,
DeserializationContext deserializationContext) throws IOException {
Map<Object1, Object2> result = new HashMap<>();
JsonNode node = jsonParser.getCodec().readTree(jsonParser);
for (JsonNode element : node) {
result.put(
jsonParser.getCodec().treeToValue(element.get("key"), Object1.class),
jsonParser.getCodec().treeToValue(element.get("value"), Object2.class)
);
}
return result;
}
}
So you can create class with your field and another Map (for checking that maps with different types works as usual):
public class MapWrapper {
#JsonSerialize(using = CustomSerializer.class)
#JsonDeserialize(using = CustomDeserializer.class)
private Map<Object1, Object2> map = new HashMap<>();
private Map<String, String> someMap = new HashMap<>();
// default constructor, getters, setters
}
Serialized value can be following:
{
"map": [
{
"key": {
"elem1": "qqq",
"elem2": "rrr"
},
"value": {
"fieldFromValue": "xxx"
}
},
{
"key": {
"elem1": "qqq_two",
"elem2": "rrr_two"
},
"value": {
"fieldFromValue": "yyy"
}
}
],
"someMap": {
"key1": "value1"
}
}

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

How can I serialize a Java object that has one member which is a json string

I have a Pojo that contains one member displayPropsJsonwhich is a clientside json string. It is validated with a JSON schema before storing on the server.
i.e.
public class Item {
Long id; //23
String name; //"itemsName"
String displayPropsJson; // "{\"bold\" : true, \"htmlAllowed\" : true, \"icon\" :\"star.jpg\" }"
}
I'd like the serialized version of this to output the displayPropsJson as displayProps sub object for example:
{
"id" :23,
"name: : "itemsName",
"displayProps" : {
"bold" : true,
"htmlAllowed" : true,
"icon" : "star.jpg"
}
}
How can I do this with a Jackson serializer that outputs elements and the json string as json?
The displayPropsJson will vary but is always valid json.
You can consider two options apart of creating a custom serializer.
Use the #JsonRawString annotation to mark a String field that
should be serialized as is without quoting of characters.
Make the ObjectMapper available inside your object instance (consider value injection) and provide a getter method that returns JsonNode deserialized from your json string value.
Here is an example demonstrating both:
public class JacksonRawString {
public static class Item {
final private ObjectMapper mapper;
public Long id = 23l;
public String name = "itemsName";
#JsonRawValue
public String displayPropsJson = "{\"bold\" : true, \"htmlAllowed\" : true, " +
"\"icon\" :\"star.jpg\" }";
public JsonNode getDisplayPropsJson2() throws IOException {
return mapper.readTree(displayPropsJson);
}
public Item(ObjectMapper mapper) {
this.mapper = mapper;
}
}
public static void main(String[] args) throws JsonProcessingException {
ObjectMapper mapper = new ObjectMapper();
System.out.println(
mapper.writerWithDefaultPrettyPrinter().writeValueAsString(new Item(mapper)));
}
}
Output:
{
"id" : 23,
"name" : "itemsName",
"displayPropsJson" : {"bold" : true, "htmlAllowed" : true, "icon" :"star.jpg" },
"displayPropsJson2" : {
"bold" : true,
"htmlAllowed" : true,
"icon" : "star.jpg"
}
}
Note that the displayPropsJson2 get pretty output since it was serialized as JsonNode
Yes I am sure this can be done with custom Jackson serializer. Another thing you could do is implement JsonSerializable,
}
Yet another possibility is to implement the JsonSerializable interface
A final possibility would be to switch libraries and use Google's GSON, which makes it easy to serialize objects into and out of json.

Categories

Resources