Java: Protobuf byte to Json string to Pojo fast - java

I am receiving messages in protobuf format. I need to convert it to json format fast as all my business logic is written to handle json based POJO objects.
byte[] request = ..; // msg received
// convert to intermediate POJO
AdxOpenRtb.BidRequest bidRequestProto = AdxOpenRtb.BidRequest.parseFrom(request, reg);
// convert intermediate POJO to json string.
// THIS STEP IS VERY SLOW
Printer printer = JsonFormat.printer().printingEnumsAsInts().omittingInsignificantWhitespace();
String jsonBody = printer.print(bidRequestProto);
// convert json string to final POJO format
BidRequest bidRequest = super.parse(jsonBody.getBytes());
Proto object to json conversion step is very slow. Is there any faster approach for it?
can i reuse printer object? is it thread-safe?
Note: This POJO class (AdxOpenRtb.BidRequest & BidRequest) is very complex having many hierarchy and fields but contains similar data with slightly different fields name and data types.

I ran into some performance issues as well and ended up writing the QuickBuffers library. It generates dedicated JSON serialization methods (i.e. no reflection) and should give you a 10-30x speedup. It can be used side-by-side with Google's implementation. The code should look something like this:
// Initialization (objects can be reused if desired)
AdxOpenRtb.BidRequest bidRequestProto = AdxOpenRtb.BidRequest.newInstance();
ProtoSource protoSource = ProtoSource.newArraySource();
JsonSink jsonSink = JsonSink.newInstance().setWriteEnumsAsInts(true);
// Convert Protobuf to JSON
bidRequestProto.clearQuick() // or ::parseFrom if you want a new object
.mergeFrom(protoSource.setInput(request))
.writeTo(jsonSink.clear());
// Use the raw json bytes
RepeatedByte jsonBytes = jsonSink.getBytes();
JsonSinkBenchmark has some sample code for replacing the built-in JSON encoder with more battle-tested Gson/Jackson backends.
Edit: if you're doing this within a single process and are worried about performance, you're better off writing or generating code to convert the Java objects directly. JSON is not a very efficient format to go through.

I end up using MapStruct as suggested by some of you (#M.Deinum).
new code:
byte[] request = ..; // msg received
// convert to intermediate POJO
AdxOpenRtb.BidRequest bidRequestProto = AdxOpenRtb.BidRequest.parseFrom(request, reg);
// direct conversion from protobuf Pojo to my custom Pojo
BidRequest bidRequest = BidRequestMapper.INSTANCE.adxOpenRtbToBidRequest(bidRequestProto);
Code snippet of BidRequestMapper:
#Mapper(
collectionMappingStrategy = CollectionMappingStrategy.ADDER_PREFERRED, nullValueCheckStrategy = NullValueCheckStrategy.ALWAYS,
unmappedSourcePolicy = ReportingPolicy.WARN, unmappedTargetPolicy = ReportingPolicy.WARN)
#DecoratedWith(BidRequestMapperDecorator.class)
public abstract class BidRequestMapper {
public static final BidRequestMapper INSTANCE = Mappers.getMapper(BidRequestMapper.class);
#Mapping(source = "impList", target = "imp")
#Mapping(target = "impOverride", ignore = true)
#Mapping(target = "ext", ignore = true)
public abstract BidRequest adxOpenRtbToBidRequest(AdxOpenRtb.BidRequest adxOpenRtb);
...
...
}
// manage proto extensions
abstract class BidRequestMapperDecorator extends BidRequestMapper {
private final BidRequestMapper delegate;
BidRequestMapperDecorator(BidRequestMapper delegate) {
this.delegate = delegate;
}
#Override
public BidRequest adxOpenRtbToBidRequest(AdxOpenRtb.BidRequest bidRequestProto) {
// Covert protobuf msg to basic bid request object
BidRequest bidRequest = delegate.adxOpenRtbToBidRequest(bidRequestProto);
...
...
}
}
The new approach is 20-50x faster in my local test environment.
It's worth mentioning that MapStruct is an annotation processor which makes it much faster than other similar libraries which use reflection and it also has very good support for customization.

Related

Converting malformed json array string to Java object

I have a malformed json array string which I get from an API call as follows:
[{\"ResponseCode\":1,\"ResponseMsg\":\"[{\"Code\":\"CA2305181\",\"Message\":\"Processed successfully\"}]\"}]
There is a double quote before open square bracket in the value of Response Msg property.
Is there a way to convert this into Java object ?
What I have tried so far:
I have used Jackson to parse it as follows but it gives error
ObjectMapper mapper = new ObjectMapper();
mapper.setPropertyNamingStrategy(new ResponseNameStrategy());
Response[] response = mapper.readValue(strOutput1, Response[].class);
Error: Can not deserialize instance of java.util.ArrayList out of VALUE_STRING token
I have also tried using Gson to parse it but it also gives error
Gson gson = new GsonBuilder()
.setFieldNamingPolicy(FieldNamingPolicy.UPPER_CAMEL_CASE)
.create();
Response[] response = gson.fromJson(strOutput1, Response[].class);
Error: Expected BEGIN_ARRAY but was STRING at line 1 column 35 path $[0].ResponseMsg
I have gone through the following links on StackOverflow but none of them has addressed my issue:
How to Convert String Array JSON in a Java Object
Convert a JSON string to object in Java ME?
JSON Array to Java objects
Convert json String to array of Objects
converting 'malformed' java json object to javascript
I think the answer is in the comments, you appear to be trying to solve the issue on the wrong place.
You are receiving json which you wish to parse into java objects, unfortunately the json is malformed so will not parse.
As a general rule you should never be trying to solve the symptom, but should look for the root cause and fix that, it may sound trivial but fixing symptoms leads to messy, unpredictable, and unmaintainable systems.
So the answer is fix the json where it is being broken. If this is something or of your control, while you wait for the fix, you could put a hack in to fix the json before you parse it.
This way you won't compromise your parsing, and only have a small piece of string replacement to remove when the third party has fixed the issue. But do not go live with the hack, it should only be used during development.
As i mentioned in the comment, you should prepare your service response in order to parse it.
I implemented an example:
public class JsonTest {
public static void main(String args[]) throws JsonProcessingException, IOException{
String rawJson =
"[{\"ResponseCode\":1,\"ResponseMsg\":\"[{\"Code\":\"CA2305181\",\"Message\":\"Processed successfully\"}]\"}]";
String goodJson = "{"+rawJson.split("[{{.}]")[2]+"}";
ObjectMapper mapper = new ObjectMapper();
final ObjectNode node = mapper.readValue(goodJson, ObjectNode.class);
System.out.println("Pretty Print: " + mapper.writerWithDefaultPrettyPrinter().writeValueAsString(node));
System.out.println("Just code: " + node.get("Code"));
}
}
Which returns:
This is how I finally solved my issue:
String inputJsonStr = "[{\"ResponseCode\":1,\"ResponseMsg\":\"[{\"Code\":\"CA2305181\",\"Message\":\"Claim has been added successfully.\"}"
+ "]\"}]";
int indexOfRes = inputJsonStr.indexOf("ResponseMsg");
if(inputJsonStr.substring(indexOfRes+13,indexOfRes+14).equals("\""))
{
inputJsonStr = inputJsonStr.substring(0,indexOfRes+13) + inputJsonStr.substring(indexOfRes+14);
}
int indexOfFirstClosingSquare = inputJsonStr.indexOf("]");
if(inputJsonStr.substring(indexOfFirstClosingSquare+1, indexOfFirstClosingSquare+2).equals("\"")) {
inputJsonStr = inputJsonStr.substring(0, indexOfFirstClosingSquare+1)+inputJsonStr.substring(indexOfFirstClosingSquare+2);
}
Now inputJsonStr contains a valid json array which can be parsed into Java custom object array easily with gson as given in this SO link:
Convert json String to array of Objects

Convert nested JSON string to list of java object

I have this json String
{"data":"[Level [key=LevelKey [keyEnd=0], Description=abc], Level [key=levelKey [keyEnd=1], Description=xyz]", "id":"123"}
And corresponding java classes are
public class Level {
public LevelKey key;
public String id;
}
public class LevelKey{
public String keyEnd;
}
I want to convert this data json string to list of Level object using Jackson
ObjectMapper mapper = new ObjectMapper();
List<Level> arr = mapper.readValue(data, new TypeReference<List<Level>>(){});
But I am getting below error
com.fasterxml.jackson.core.JsonParseException: Unrecognized token 'Level': was expecting ('true', 'false' or 'null')
Is there any other method to parse it?
The below does not look like a proper JSON for the purpose (except for a standard fixed string)
"[Level [key=LevelKey [keyEnd=0], Description=abc], Level [key=levelKey [keyEnd=1], Description=xyz]"
You could correct the data part of your JSON to something like below (Closest to your JSON in question) :
[\"Level [key=LevelKey [keyEnd=0], Description=abc]\",\" Level [key=levelKey [keyEnd=1], Description=xyz]\"]
Is there any other method to parse it?
You could use a direct class reference of ArrayList instead of having to instantiate TypeReference like below to parse the above (corrected) json string :
List<Level> arr = mapper.readValue(data, (new ArrayList<Level>()).getClass());
This was an interesting one I must say. Take a look at code snippet I think I got it correct :
String data ="{\"data\":\"[Level [key=LevelKey [keyEnd=0], Description=abc], Level [key=levelKey [keyEnd=1], Description=xyz]\", \"id\":\"123\"}";
ObjectMapper mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);
List<Level> arr = mapper.readValue(data, (new ArrayList<Level>()).getClass());
System.out.println(arr);
I got the following output :
[{data=[Level [key=LevelKey [keyEnd=0], Description=abc], Level [key=levelKey [keyEnd=1], Description=xyz], id=123}]
Also if you encountered any JsonParseException which according to documentation means :
Exception type for parsing problems, used when non-well-formed content
(content that does not conform to JSON syntax as per specification) is
encountered.
So while hacking the JSON you can update the ObjectMapper object like this :
mapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);
Also as mentioned by Exception_al using a direct class reference of ArrayList instead of having to instantiate TypeReference like below to parse the above (corrected) json string.
List<Level> arr = mapper.readValue(data, (new ArrayList<Level>()).getClass());
Hope this helped.
The String is not the JSON representation of what you expect you get deserialized into JAVA.
This is a JSON String:
"{"data":[{"key":{"keyEnd":0},"Description":"abc"},{"key":{"keyEnd":1},"Description":"abc"}],"id":"123"}"
So, there is either a problem with the String or you need to do the parsing yourself.

Converting a single CSV/TSV string into a Java object?

Instead of converting an entire CSV file to an object, is there a simple API that takes in one csv or tsv string, and converts it to an object? The api's I've found so far are geared towards csv/tsv FIlE to list of objects.
Obviously I could just split the String and call a constructor, but was wondering if there was a clean api I could use.
You can do this with Jackson. It looks pretty similar to the other answers but seems to perform better than SuperCSV according to their tests.
Define your POJO (both the annotation and constructor seems to be necessary):
#JsonPropertyOrder({ "foo", "bar" })
public class FooBar {
private String foo;
private String bar;
public FooBar() {
}
// Setters, getters, toString()
}
Then parse it:
String input = "1,2\n3,4";
StringReader reader = new StringReader(input);
CsvMapper m = new CsvMapper();
CsvSchema schema = m.schemaFor(FooBar.class).withoutHeader().withLineSeparator("\n").withColumnSeparator(',');
try {
MappingIterator<FooBar> r = m.reader(FooBar.class).with(schema).readValues(reader);
while (r.hasNext()) {
System.out.println(r.nextValue());
}
} catch (JsonProcessingException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
Go with uniVocity-parsers as it is at least twice as fast than SuperCSV and has way more features.
For example, let's say your bean is:
class TestBean {
// if the value parsed in the quantity column is "?" or "-", it will be replaced by null.
#NullString(nulls = { "?", "-" })
// if a value resolves to null, it will be converted to the String "0".
#Parsed(defaultNullRead = "0")
private Integer quantity; // The attribute type defines which conversion will be executed when processing the value.
// In this case, IntegerConversion will be used.
// The attribute name will be matched against the column header in the file automatically.
#Trim
#LowerCase
// the value for the comments attribute is in the column at index 4 (0 is the first column, so this means fifth column in the file)
#Parsed(index = 4)
private String comments;
// you can also explicitly give the name of a column in the file.
#Parsed(field = "amount")
private BigDecimal amount;
#Trim
#LowerCase
// values "no", "n" and "null" will be converted to false; values "yes" and "y" will be converted to true
#BooleanString(falseStrings = { "no", "n", "null" }, trueStrings = { "yes", "y" })
#Parsed
private Boolean pending;
Now, to read your input as a list of TestBean
// BeanListProcessor converts each parsed row to an instance of a given class, then stores each instance into a list.
BeanListProcessor<TestBean> rowProcessor = new BeanListProcessor<TestBean>(TestBean.class);
CsvParserSettings parserSettings = new CsvParserSettings();
parserSettings.setRowProcessor(rowProcessor);
parserSettings.setHeaderExtractionEnabled(true);
CsvParser parser = new CsvParser(parserSettings);
parser.parse(getReader("/examples/bean_test.csv"));
// The BeanListProcessor provides a list of objects extracted from the input.
List<TestBean> beans = rowProcessor.getBeans();
To parse TSV files, just change the combination of CsvParserSettings & CsvParser to TsvParserSettings & TsvParser.
Disclosure: I am the author of this library. It's open-source and free (Apache V2.0 license).
I'm using this Api:
http://jsefa.sourceforge.net/
You can use annotations to convert your entities in CSV.
In the case of SuperCSV which you mentioned in a comment, you could pass it a String wrapped in a StringReader, i.e.
CsvBeanReader beanReader=new CsvBeanReader(new StringReader(theString), preferences);
beanReader.read(theBean, nameMapping);
I was currently dealing with a similar issue. in my case I wanted to import a single csv row at a time into a single pojo as I was getting my data in the form of discrete single line websocket updates. at the end jackson worked best for me as I didnt have to put everything into a list of pojos first.
here the code
String csvString="rick|sanchez|99"
private CsvMapper mapper=new CsvMapper();
private CsvSchema schema = mapper.schemaFor(Pojo.class).withColumnSeparator('|');
private ObjectReader r=mapper.readerFor(Pojo.class).with(schema);
Pojo pojo=r.readValue(csvString);
for this to work you also ned to add the following annotation to your pojo
#JsonPropertyOrder({"firstName","lastName","age"})
as far as I know its the only one that easily lets you parse a single csv line into a single pojo instance. obviously you could also do this over a constructor by hand but these libraries deal with with type conversions for you so its particularly useful if your pojo contains lots of different attributes

Serialize Pojos to JSON using new standard javax.json

I like the idea of having a standard for JSON serialization in Java, javax.json is a great step forward you can do an object graph like this:
JsonObject jsonObject3 =
Json.createObjectBuilder()
.add("name", "Ersin")
.add("surname", "Çetinkaya")
.add("age", 25)
.add("address",
Json.createObjectBuilder()
.add("city", "Bursa")
.add("country", "Türkiye")
.add("zipCode", "33444"))
.add("phones",
Json.createArrayBuilder()
.add("234234242")
.add("345345354"))
.build();
That's it, but how can I serialize a pojo or simple Java object(like a Map) direct to JSON?, something like I do in Gson:
Person person = new Person();
String jsonStr = new Gson().toJson(person);
How can I do this with the new standard API?
Java API for JSON Processing (JSR-353) does not cover object binding. This will be covered in a separate JSR.
See JSR-367, Java API for JSON Binding (JSON-B), a headline feature in Java™ EE 8.
Document: Json Binding 1.0 Users Guide
// Create Jsonb and serialize
Jsonb jsonb = JsonbBuilder.create();
String result = jsonb.toJson(dog);
// Deserialize back
dog = jsonb.fromJson("{name:\"Falco\", age:4, bitable:false}", Dog.class);
Maybe it's because this question is almost 5 years old (I didn't check which java release has these classes) but there is a standard way with javax.json.* classes:
JsonObject json = Json.createObjectBuilder()
.add("key", "value")
.build();
try(JsonWriter writer = Json.createWriter(outputStream)) {
writer.write(json);
}

In XStream is there a better way to marshall/unmarshall List<Object>'s in JSON and Java

I'm using XStream and JETTISON's Stax JSON serializer to send/receive messages to/from JSON javascripts clients and Java web applications.
I want to be able to create a list of objects to send to the server and be properly marshalled into Java but the format that XStream and JSON expect it in is very non-intuitive and requires our javascript libraries to jump through hoops.
[EDIT Update issues using GSON library]
I attempted to use the GSON library but it cannot deserialize concrete objects when I only have it expect generic super classes (XStream and Jettison handles this because type information is baked into the serialization).
GSON FAQ states Collection Limitation:
Collections Limitations
Can serialize collection of arbitrary objects but can not deserialize from it
Because there is no way for the user to indicate the type of the resulting object
While deserializing, Collection must be of a specific generic type
Maybe I'm using bad java practices but how would I go about building a JSON to Java messaging framework that sent/received various concrete Message objects in JSON format?
For example this fails:
public static void main(String[] args) {
Gson gson = new Gson();
MockMessage mock1 = new MockMessage();
MockMessage mock2 = new MockMessage();
MockMessageOther mock3 = new MockMessageOther();
List<MockMessage> messages = new ArrayList<MockMessage>();
messages.add(mock1);
messages.add(mock2);
messages.add(mock3);
String jsonString = gson.toJson(messages);
//JSON list format is non-intuitive single element array with class name fields
System.out.println(jsonString);
List gsonJSONUnmarshalledMessages = (List)gson.fromJson(jsonString, List.class);
//This will print 3 messages unmarshalled
System.out.println("XStream format JSON Number of messages unmarshalled: " + gsonJSONUnmarshalledMessages.size());
}
[{"val":1},{"val":1},{"otherVal":1,"val":1}]
Exception in thread "main" com.google.gson.JsonParseException: The JsonDeserializer com.google.gson.DefaultTypeAdapters$CollectionTypeAdapter#638bd7f1 failed to deserialized json object [{"val":1},{"val":1},{"otherVal":1,"val":1}] given the type interface java.util.List
Here's an example, I want to send a list of 3 Message objects, 2 are of the same type and the 3rd is a different type.
import java.util.ArrayList;
import java.util.List;
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.io.json.JettisonMappedXmlDriver;
class MockMessage {
int val = 1;
}
class MockMessageOther {
int otherVal = 1;
}
public class TestJSONXStream {
public static void main(String[] args) {
JettisonMappedXmlDriver xmlDriver = new JettisonMappedXmlDriver();
XStream xstream = new XStream(xmlDriver);
MockMessage mock1 = new MockMessage();
MockMessage mock2 = new MockMessage();
MockMessageOther mock3 = new MockMessageOther();
List messages = new ArrayList();
messages.add(mock1);
messages.add(mock2);
messages.add(mock3);
String jsonString = xstream.toXML(messages);
//JSON list format is non-intuitive single element array with class name fields
System.out.println(jsonString);
List xstreamJSONUnmarshalledMessages = (List)xstream.fromXML(jsonString);
//This will print 3 messages unmarshalled
System.out.println("XStream format JSON Number of messages unmarshalled: " + xstreamJSONUnmarshalledMessages.size());
//Attempt to deserialize a reasonable looking JSON string
String jsonTest =
"{"+
"\"list\" : ["+
"{"+
"\"MockMessage\" : {"+
"\"val\" : 1"+
"}"+
"}, {"+
"\"MockMessage\" : {"+
"\"val\" : 1"+
"}"+
"}, {"+
"\"MockMessageOther\" : {"+
"\"otherVal\" : 1"+
"}"+
"} ]"+
"};";
List unmarshalledMessages = (List)xstream.fromXML(jsonTest);
//We expect 3 messages but XStream only deserializes one
System.out.println("Normal format JSON Number of messages unmarshalled: " + unmarshalledMessages.size());
}
}
Intuitively I expect the XStream JSON to be serialized (and able to deserialize correctly) from the following format:
{
"list" : [
{
"MockMessage" : {
"val" : 1
}
}, {
"MockMessage" : {
"val" : 1
}
}, {
"MockMessageOther" : {
"otherVal" : 1
}
} ]
}
Instead XStream creates a single element list with fields that are named the classnames and nested arrays of Objects of the same type.
{
"list" : [ {
"MockMessage" : [ {
"val" : 1
}, {
"val" : 1
} ],
"MockMessageOther" : {
"otherVal" : 1
}
} ]
}
The trouble may be caused by it using the XStream XML CollectionConverter?
Does anyone have a suggestion for a good JSON Java object serialization that allows you to read/write arbitrary Java objects. I looked at the Jackson Java JSON Processor but when you were reading in objects from a stream you had to specify what type of object it was unlike XStream where it will read in any object (because the serialized XStream JSON contains class name information).
I agree with other poster in that XStream is not a good fit -- it's an OXM (Object/Xml Mapper), and JSON is handled as a secondary output format using XML processing path. This is why a "convention" (of how to convert hierarchich xml model into object-graph model of json and vice versa) is needed; and your choice boils down to using whatever is least intrusive of sub-optimal choices.
That works ok if XML is your primary data format, and you just need some rudimentary JSON(-like) support.
To get good JSON-support, I would consider using a JSON processing library that does real OJM mapping (I assume Svenson does too, but additionally), such as:
Jackson
Google-gson
Also: even if you do need to support both XML and JSON, you are IMO better off using separate libraries for these tasks -- objects (beans) to use on server-side need not be different, just serialization libs that convert to/from xml and json.
I realize this is off-topic, but I'd like to present a solution in svenson JSON.
Do you really need public fields in your domain classes? Apart from having to use properties, svenson can handle cases like this with a more simple JSON output with a discriminator property
class Message
{
// .. your properties with getters and setters ..
// special property "type" acts a signal for conversion
}
class MessageOther
{
...
}
List list = new ArrayList();
list.add(new Message());
list.add(new MessageOther());
list.add(new Message());
String jsonDataSet = JSON.defaultJSON().forValue(list);
would output JSON like
[
{"type":"message", ... },
{"type":"message_other", ... },
{"type":"message", ... }
]
which could be parsed again with code like this
// configure reusable parse instance
JSONParser parser = new JSONParser();
// type mapper to map to your types
PropertyValueBasedTypeMapper mapper = new PropertyValueBasedTypeMapper();
mapper.setParsePathInfo("[]");
mapper.addFieldValueMapping("message", Message.class);
mapper.addFieldValueMapping("message_other", MessageOther.class);
parser.setTypeMapper(mapper);
List list = parser.parse(List.class, jsonDataset);
A svenson type mapper based on the full class name would look something like this
public class ClassNameBasedTypeMapper extends PropertyValueBasedTypeMapper
{
protected Class getTypeHintFromTypeProperty(String value) throws IllegalStateException
{
try
{
return Class.forName(value);
}
catch (ClassNotFoundException e)
{
throw new IllegalStateException(value + " is no valid class", e);
}
}
}
which is not an ideal implementation as it inherits the configuration of PropertyValueBasedTypeMapper without really needing. (should include a cleaner version in svenson)
The setup is very much like above
JSONParser parser = new JSONParser();
ClassNameBasedTypeMapper mapper = new ClassNameBasedTypeMapper();
mapper.setParsePathInfo("[]");
parser.setTypeMapper(mapper);
List foos = parser
.parse( List.class, "[{\"type\":\"package.Foo\"},{\"type\":\"package.Bar\"}]");

Categories