My objective is to read a very complex JSON using Spring Batch. Below is the sample JSON.
{
"order-info" : {
"order-number" : "Test-Order-1"
"order-items" : [
{
"item-id" : "4144769310"
"categories" : [
"ABCD",
"DEF"
],
"item_imag" : "http:// "
"attributes: {
"color" : "red"
},
"dimensions" : {
},
"vendor" : "abcd",
},
{
"item-id" : "88888",
"categories" : [
"ABCD",
"DEF"
],
.......
I understand that I would need to create a Custom ItemReader to parse this JSON.
Kindly provide me some pointers. I am really clueless.
I am now not using CustomItemReader. I am using Java POJOs. My JsonItemReader is as per below:
#Bean
public JsonItemReader<Trade> jsonItemReader() {
ObjectMapper objectMapper = new ObjectMapper();
JacksonJsonObjectReader<Trade> jsonObjectReader =
new JacksonJsonObjectReader<>(Trade.class);
jsonObjectReader.setMapper(objectMapper);
return new JsonItemReaderBuilder<Trade>()
.jsonObjectReader(jsonObjectReader)
.resource(new ClassPathResource("search_data_1.json"))
.name("tradeJsonItemReader")
.build();
}
The exception which I now get is :
java.lang.IllegalStateException: The Json input stream must start with an array of Json objects
From similar posts in this forum I understand that I need to use JsonObjectReader. "You can implement it to read a single json object and use it with the JsonItemReader (either at construction time or using the setter)".
How can I do this either # construction time or using setter? Please share some code snippet for the same.
The delegate of MultiResourceItemReader should still be a JsonItemReader. You just need to use a custom JsonObjectReader with the JsonItemReader instead of JacksonJsonObjectReader. Visually, this would be: MultiResourceItemReader -- delegates to --> JsonItemReader -- uses --> your custom JsonObjectReader.
Could you please share a code snippet for the above?
JacksonJsonItemReader is meant to parse from a root node that is already and array node, so it expects your json to start with '['.
If you desire to parse a complex object - in this case, one that have many parent nodes/properties before it gets to the array - you should write a reader. It is really simple to do it and you can follow JacksonJsonObjectReader's structure. Here follows and example of a generic reader for complex object with respective unit tests.
The unit test
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.BlockJUnit4ClassRunner;
import org.springframework.core.io.ByteArrayResource;
import com.example.batch_experiment.dataset.Dataset;
import com.example.batch_experiment.dataset.GenericJsonObjectReader;
import com.example.batch_experiment.json.InvalidArrayNodeException;
import com.example.batch_experiment.json.UnreachableNodeException;
import com.fasterxml.jackson.databind.ObjectMapper;
#RunWith(BlockJUnit4ClassRunner.class)
public class GenericJsonObjectReaderTest {
GenericJsonObjectReader<Dataset> reader;
#Before
public void setUp() {
reader = new GenericJsonObjectReader<Dataset>(Dataset.class, "results");
}
#Test
public void shouldRead_ResultAsRootNode() throws Exception {
reader.open(new ByteArrayResource("{\"result\":{\"results\":[{\"id\":\"a\"}]}}".getBytes()) {});
Assert.assertTrue(reader.getDatasetNode().isArray());
Assert.assertFalse(reader.getDatasetNode().isEmpty());
}
#Test
public void shouldIgnoreUnknownProperty() throws Exception {
String jsonStr = "{\"result\":{\"results\":[{\"id\":\"a\", \"aDifferrentProperty\":0}]}}";
reader.open(new ByteArrayResource(jsonStr.getBytes()) {});
Assert.assertTrue(reader.getDatasetNode().isArray());
Assert.assertFalse(reader.getDatasetNode().isEmpty());
}
#Test
public void shouldIgnoreNullWithoutQuotes() throws Exception {
String jsonStr = "{\"result\":{\"results\":[{\"id\":\"a\",\"name\":null}]}}";
try {
reader.open(new ByteArrayResource(jsonStr.getBytes()) {});
Assert.assertTrue(reader.getDatasetNode().isArray());
Assert.assertFalse(reader.getDatasetNode().isEmpty());
} catch (Exception e) {
Assert.fail(e.getMessage());
}
}
#Test
public void shouldThrowException_OnNullNode() throws Exception {
boolean exceptionThrown = false;
try {
reader.open(new ByteArrayResource("{}".getBytes()) {});
} catch (UnreachableNodeException e) {
exceptionThrown = true;
}
Assert.assertTrue(exceptionThrown);
}
#Test
public void shouldThrowException_OnNotArrayNode() throws Exception {
boolean exceptionThrown = false;
try {
reader.open(new ByteArrayResource("{\"result\":{\"results\":{}}}".getBytes()) {});
} catch (InvalidArrayNodeException e) {
exceptionThrown = true;
}
Assert.assertTrue(exceptionThrown);
}
#Test
public void shouldReadObjectValue() {
try {
reader.setJsonParser(new ObjectMapper().createParser("{\"id\":\"a\"}"));
Dataset dataset = reader.read();
Assert.assertNotNull(dataset);
Assert.assertEquals("a", dataset.getId());
} catch (Exception e) {
Assert.fail(e.getMessage());
}
}
}
And the reader:
import java.io.IOException;
import java.io.InputStream;
import java.util.logging.Logger;
import org.springframework.batch.item.ParseException;
import org.springframework.batch.item.json.JsonObjectReader;
import org.springframework.core.io.Resource;
import com.example.batch_experiment.json.InvalidArrayNodeException;
import com.example.batch_experiment.json.UnreachableNodeException;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
/*
* This class follows the structure and functions similar to JacksonJsonObjectReader, with
* the difference that it expects a object as root node, instead of an array.
*/
public class GenericJsonObjectReader<T> implements JsonObjectReader<T>{
Logger logger = Logger.getLogger(GenericJsonObjectReader.class.getName());
ObjectMapper mapper = new ObjectMapper();
private JsonParser jsonParser;
private InputStream inputStream;
private ArrayNode targetNode;
private Class<T> targetType;
private String targetPath;
public GenericJsonObjectReader(Class<T> targetType, String targetPath) {
super();
this.targetType = targetType;
this.targetPath = targetPath;
}
public JsonParser getJsonParser() {
return jsonParser;
}
public void setJsonParser(JsonParser jsonParser) {
this.jsonParser = jsonParser;
}
public ArrayNode getDatasetNode() {
return targetNode;
}
/*
* JsonObjectReader interface has an empty default method and must be implemented in this case to set
* the mapper and the parser
*/
#Override
public void open(Resource resource) throws Exception {
logger.info("Opening json object reader");
this.inputStream = resource.getInputStream();
JsonNode jsonNode = this.mapper.readTree(this.inputStream).findPath(targetPath);
if (!jsonNode.isMissingNode()) {
this.jsonParser = startArrayParser(jsonNode);
logger.info("Reader open with parser reference: " + this.jsonParser);
this.targetNode = (ArrayNode) jsonNode; // for testing purposes
} else {
logger.severe("Couldn't read target node " + this.targetPath);
throw new UnreachableNodeException();
}
}
#Override
public T read() throws Exception {
try {
if (this.jsonParser.nextToken() == JsonToken.START_OBJECT) {
T result = this.mapper.readValue(this.jsonParser, this.targetType);
logger.info("Object read: " + result.hashCode());
return result;
}
} catch (IOException e) {
throw new ParseException("Unable to read next JSON object", e);
}
return null;
}
/**
* Creates a new parser from an array node
*/
private JsonParser startArrayParser(JsonNode jsonArrayNode) throws IOException {
JsonParser jsonParser = this.mapper.getFactory().createParser(jsonArrayNode.toString());
if (jsonParser.nextToken() == JsonToken.START_ARRAY) {
return jsonParser;
} else {
throw new InvalidArrayNodeException();
}
}
#Override
public void close() throws Exception {
this.inputStream.close();
this.jsonParser.close();
}
}
Related
Use-case:
Developers/I, want to only implement a Protobuf implementation (binary protocol). However, I need a way to add config, so, the same implementation is exposed as rest/json api as well -- without code duplication.
I have proto endpoints exposed. I also want consumers to post json equivalent of those proto objects and return/receive json equivalent of the results with type info (Pojo?). The type info helps with OpenAPI / Swagger documentation too!
What are the most elegant/simple ways to achieve that without code duplication?
Any example github code that achieves that would be helpful.
Note: This is for webflux & netty - no tomcat.
ProtobufJsonFormatHttpMessageConverter - works for tomcat, does not work for netty. A working example code would be great.
I was messing around with this and ended up with this. Nothing else worked for me.
Using protov3 and setting a protobuf like this
syntax = "proto3";
option java_package = "com.company";
option java_multiple_files = true;
message CreateThingRequest {
...
message CreateThingResponse {
....
I can scan for the protobuf files by setting app.protoPath in my application.properties
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.google.common.reflect.ClassPath;
import com.google.protobuf.Message;
import com.google.protobuf.util.JsonFormat;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.codec.ServerCodecConfigurer;
import org.springframework.http.codec.json.Jackson2JsonDecoder;
import org.springframework.http.codec.json.Jackson2JsonEncoder;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
import org.springframework.web.reactive.config.WebFluxConfigurer;
#Configuration
public class WebConfig implements WebFluxConfigurer {
#Value("${app.protoPath:com.}")
private String protoPath;
#Override
public void configureHttpMessageCodecs(ServerCodecConfigurer configurer) {
configurer.defaultCodecs().jackson2JsonEncoder(
new Jackson2JsonEncoder(Jackson2ObjectMapperBuilder.json().serializerByType(
Message.class, new JsonSerializer<Message>() {
#Override
public void serialize(Message value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
String str = JsonFormat.printer().omittingInsignificantWhitespace().print(value);
gen.writeRawValue(str);
}
}
).build())
);
final ClassLoader loader = Thread.currentThread().getContextClassLoader();
Map<Class<?>, JsonDeserializer<?>> deserializers = new HashMap<>();
try {
for (final ClassPath.ClassInfo info : ClassPath.from(loader).getTopLevelClasses()) {
if (info.getName().startsWith(protoPath)) {
final Class<?> clazz = info.load();
if (!Message.class.isAssignableFrom(clazz)) {
continue;
}
#SuppressWarnings("unchecked") final Class<Message> proto = (Class<Message>) clazz;
final JsonDeserializer<Message> deserializer = new CustomJsonDeserializer() {
#Override
public Class<Message> getDeserializeClass() {
return proto;
}
};
deserializers.put(proto, deserializer);
}
}
} catch (IOException e) {
throw new RuntimeException(e);
}
configurer.defaultCodecs().jackson2JsonDecoder(new Jackson2JsonDecoder(Jackson2ObjectMapperBuilder.json().deserializersByType(deserializers).build()));
}
private abstract static class CustomJsonDeserializer extends JsonDeserializer<Message> {
abstract Class<? extends Message> getDeserializeClass();
#Override
public Message deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException {
Message.Builder builder = null;
try {
builder = (Message.Builder) getDeserializeClass()
.getDeclaredMethod("newBuilder")
.invoke(null);
} catch (Exception e) {
throw new RuntimeException(e);
}
JsonFormat.parser().merge(jp.getCodec().readTree(jp).toString(), builder);
return builder.build();
}
}
}
Then I just use the object types in the returns;
#PostMapping(
path = "/things",
consumes = {MediaType.APPLICATION_JSON_VALUE, "application/x-protobuf"},
produces = {MediaType.APPLICATION_JSON_VALUE, "application/x-protobuf"})
Mono<CreateThingResponse> createThing(#RequestBody CreateThingRequest request);
With https://github.com/innogames/springfox-protobuf you can get the responses to show in swagger but the requests still aren't showing for me.
You'll have to excuse the messy Java I'm a little rusty.
I needed to support json and the following code helped
#Bean
public WebFluxConfigurer webFluxConfigurer() {
return new WebFluxConfigurer() {
#Override
public void configureHttpMessageCodecs(ServerCodecConfigurer configurer) {
ObjectMapper mapper = new ObjectMapper()
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false)
.registerModule(new ProtobufModule());
configurer.customCodecs().register(new Jackson2JsonEncoder(mapper));
configurer.customCodecs().register(new Jackson2JsonDecoder(mapper));
}
};
}
Try adding ProtoEncoder in your WebFlux config:
#EnableWebFlux
public class MyConfig implements WebFluxConfigurer {
#Override
public void configureHttpMessageCodecs(ServerCodecConfigurer configurer) {
configurer.customCodecs().register(new ProtobufEncoder());
}
}
Then in your request mapping return the proto object:
#GetMapping (produces = "application/x-protobuf")
public MyProtoObject lookup() {
return new MyProtoObject();
}
Furthermore, if you want to serialize the proto object into JSON and return String, then have a look at com.googlecode.protobuf-java-format:protobuf-java-format library and JsonFormat::printToString capability (https://code.google.com/archive/p/protobuf-java-format/):
#GetMapping
public String lookup() {
return new JsonFormat().printToString(new MyProtoObj());
}
Since version 4.1 spring provides org.springframework.http.converter.protobuf.ProtobufHttpMessageConverter for reading and writing protos as Json.
However, If you are using Spring 5.x and Protobuf 3.x there is org.springframework.http.converter.protobuf.ProtobufJsonFormatHttpMessageConverter for more explicit conversion of Json.
This documentation should help you:
https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/http/converter/protobuf/ProtobufHttpMessageConverter.html
https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/http/converter/protobuf/ProtobufJsonFormatHttpMessageConverter.html
Using Browserstack tutorials (https://www.browserstack.com/app-automate/appium-junit) and sample project (https://github.com/browserstack/junit-appium-app-browserstack) I am struggling with setup of parallel tests.
Specifically, I need to run suirte with Cucumber.class (#RunWith(Cucumber.class)) for my tests to be read from scenarios, while Browserstack documentation tells me to run with Parameterized.class (public class Parallelized extends Parameterized).
The biggest problem I encounter is how to parse multiple device+os configurations to Browserstack, if you run the suite with Cucumber class.
My Runner class:
package step_definitions;
import org.junit.runner.RunWith;
import io.cucumber.junit.CucumberOptions;
import io.cucumber.junit.Cucumber;
#RunWith(Cucumber.class)
#CucumberOptions(features = {
"src/main/resources/FeatureFiles" }, dryRun = false, strict = false, monochrome = true, plugin = {
"html:target/cucumber", "json:target/cucumber.json" },
// glue = {"iOSAutomation/src/test/java/step_definitions"},
tags = { "#Login"})
public class RunTest {
}
Launcher:
package step_definitions;
import (...)
public class Launcher {
public static IOSDriver<IOSElement> driver;
public static WebDriverWait wait;
// Parallel BS tests
private static JSONObject config;
#Parameter(value = 0)
public static int taskID;
#Parameters
public static Iterable<? extends Object> data() throws Exception {
List<Integer> taskIDs = new ArrayList<Integer>();
if (System.getProperty("config") != null) {
JSONParser parser = new JSONParser();
config = (JSONObject) parser.parse(new FileReader("src/main/resources/conf/" + System.getProperty("config")));
int envs = ((JSONArray) config.get("environments")).size();
for (int i = 0; i < envs; i++) {
taskIDs.add(i);
}
}
return taskIDs;
}
#Before
public static void Launchapp(Scenario scenario) throws InterruptedException, FileNotFoundException, IOException, ParseException {
JSONArray envs = (JSONArray) config.get("environments");
DesiredCapabilities caps = new DesiredCapabilities();
caps.setCapability(MobileCapabilityType.AUTOMATION_NAME, "xcuitest");
caps.setCapability(MobileCapabilityType.PLATFORM_NAME, "iOS");
caps.setCapability("bundleId", bundleId);
caps.setCapability(MobileCapabilityType.APP, "useNewWDA");
caps.setCapability(MobileCapabilityType.APP, "clearSystemFiles");
caps.setCapability(MobileCapabilityType.APP, app);
caps.setCapability("browserstack.local", "false");
caps.setCapability("webkitResponseTimeout", "60000");
caps.setCapability("browserstack.localIdentifier", "Test123");
caps.setCapability("browserstack.appium_version", "1.9.1");
caps.setCapability("startIWDP", true);
caps.setCapability("instrumentApp", true);
caps.setCapability("webkitResponseTimeout", 70000);
Map<String, String> envCapabilities = (Map<String, String>) envs.get(taskID);
Iterator it = envCapabilities.entrySet().iterator();
while (it.hasNext()) {
Map.Entry pair = (Map.Entry) it.next();
caps.setCapability(pair.getKey().toString(), pair.getValue().toString());
}
driver = new IOSDriver<IOSElement>(
new URL("http://" + userName + ":" + accessKey + "#hub-cloud.browserstack.com/wd/hub"), caps);
sessionId = driver.getSessionId().toString();
driver.manage().timeouts().implicitlyWait(60, TimeUnit.SECONDS);
wait = new WebDriverWait(driver, 30);
}
#After
public void tearDown(Scenario scenario) throws Exception {
driver.quit();
}
}
Parallelized.java:
package step_definitions;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import org.junit.runners.Parameterized;
import org.junit.runners.model.RunnerScheduler;
import io.cucumber.junit.Cucumber;
public class Parallelized extends Parameterized {
private static class ThreadPoolScheduler implements RunnerScheduler {
private ExecutorService executor;
public ThreadPoolScheduler() {
String threads = System.getProperty("junit.parallel.threads", "4");
int numThreads = Integer.parseInt(threads);
executor = Executors.newFixedThreadPool(numThreads);
}
#Override
public void finished() {
executor.shutdown();
try {
executor.awaitTermination(10, TimeUnit.MINUTES);
} catch (InterruptedException exc) {
throw new RuntimeException(exc);
}
}
#Override
public void schedule(Runnable childStatement) {
executor.submit(childStatement);
}
}
public Parallelized(Class klass) throws Throwable {
super(klass);
setScheduler(new ThreadPoolScheduler());
}
}
and config file:
{
"environments": [{
"device": "iPhone XR",
"os_version": "12"
}, {
"device": "iPhone 6S",
"os_version": "11"
}, {
"device": "iPhone XS",
"os_version": "13"
}, {
"device": "iPhone XS Max",
"os_version": "12"
}]
}
How to make it work? May I run this with Cucumber.class AND somehow incorporate methods from Parallelized.java?
You can use MakeFile where you can provide all the devices or platform capabilities and with cucumber-jvm-parallel-plugin the tests can be run parallel in Browserstack. This will be the easiest solution.
Sample MakeFile:
browserstack_parallel:
make -j bs_iPhoneXS bs_iPhoneX
bs_iPhoneXS:
mvn test -Dbs_local_testing=false -Dbs_device=iPhoneXS -Dbs_app=bs://0fb247cde17a979db4d7e5a521bc600af7620b63
bs_iPhoneX:
mvn test -Dbs_local_testing=false -Dbs_device=iPhoneX -Dbs_app=bs://0fb247cde17a979db4d7e5a521bc600af7620b63
You can run MakeFile from terminal by typing make browserstack_parallel
I am currently building an API gateway for a new microservices system, using the Spring Netflix Zuul library.
So far my gateway contains PRE and POST filters that intercept the requests and perform the required logic, etc.
One thing that I see is that REST calls to specific microservices require invoking an API endpoint (either GET or POST) containing JSON payload data that is very complex.
For an end-user sending a request to a microservice containing this JSON would not be user friendly.
I had an idea such that the API gateway act as a mediator, where the user can submit a more "simplified/ user-friendly" JSON to the API gateway, which will transform the JSON payload with the correct "complex" JSON structure that the target microservice can understand in order to handle the request efficiently.
My understanding of how Netflix Zuul is that this can be done by creating a RouteFilter and then including this logic here.
Can anyone explain if (or how) this transformation could be done using Netflix Zuul?
Any advice is appreciated.
Thanks.
No doubt you can do it with Zuul, i am currently trying to do almost the same. i'd suggest you take a look at this repo :
sample-zuul-filters
and the official doc on github.
Filters have to extend ZuulFilter and implement the following methods :
/**
*return a string defining when your filter must execute during zuul's
*lyfecyle ('pre'/'post' routing
**/
#Override
public String filterType(){
return 'pre'; // run this filter before sending the final request
}
/**
* return an int describing the order that the filter should run on,
* (relative to the other filters and the current 'pre' or 'post' context)
**/
#Override
public int filterOrder {
return 1; //this filter runs first in a pre-request context
}
/**
* return a boolean indicating if the filter should run or not
**/
#Override
public boolean shouldFilter() {
RequestContext ctx = RequestContext.getCurrentContext();
if(ctx.getRequest().getRequestURI().equals("/theRouteIWantToFilter"))
{
return true;
}
else {
return false;
}
}
/**
* After all the config stuffs you can set what your filter actually does
* here. This is where your json logic goes.
*/
#Override
public Object run() {
try {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
InputStream stream = ctx.getResponseDataStream();
String body = StreamUtils.copyToString(stream, Charset.forName("UTF-8"));
// transform your json and send it to the api.
ctx.setResponseBody(" Modified body : " + body);
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
I am not sure my answer is 100% accurate since i am working on it but it's a start.
I've done payload conversion in pre filter but this should work in route filter as well. Use com.netflix.zuul.http.HttpServletRequestWrapper to capture and modify the original request payload before forwarding the request to target microservice.
Sample code:
package com.sample.zuul.filters.pre;
import com.google.common.io.CharStreams;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.http.HttpServletRequestWrapper;
import com.netflix.zuul.http.ServletInputStreamWrapper;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
import org.json.simple.parser.ParseException;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
public class JsonConverterFilter extends ZuulFilter {
#Override
public String filterType() {
return "pre";
}
#Override
public int filterOrder() {
return 0; // Set it to whatever the order of your filter is
}
#Override
public boolean shouldFilter() {
return true;
}
#Override
public Object run() {
RequestContext context = RequestContext.getCurrentContext();
HttpServletRequest request = new HttpServletRequestWrapper(context.getRequest());
String requestData = null;
JSONParser jsonParser = new JSONParser();
JSONObject requestJson = null;
try {
if (request.getContentLength() > 0) {
requestData = CharStreams.toString(request.getReader());
}
if (requestData == null) {
return null;
}
requestJson = (JSONObject) jsonParser.parse(requestData);
} catch (Exception e) {
//Add your exception handling code here
}
JSONObject modifiedRequest = modifyJSONRequest(requestJson);
final byte[] newRequestDataBytes = modifiedRequest.toJSONString().getBytes();
request = getUpdatedHttpServletRequest(request, newRequestDataBytes);
context.setRequest(request);
return null;
}
private JSONObject modifyJSONRequest(JSONObject requestJSON) {
JSONObject jsonObjectDecryptedPayload = null;
try {
jsonObjectDecryptedPayload = (JSONObject) new JSONParser()
.parse("Your new complex json");
} catch (ParseException e) {
e.printStackTrace();
}
return jsonObjectDecryptedPayload;
}
private HttpServletRequest getUpdatedHttpServletRequest(HttpServletRequest request, final byte[] newRequestDataBytes) {
request = new javax.servlet.http.HttpServletRequestWrapper(request) {
#Override
public BufferedReader getReader() throws IOException {
return new BufferedReader(
new InputStreamReader(new ByteArrayInputStream(newRequestDataBytes)));
}
#Override
public ServletInputStream getInputStream() throws IOException {
return new ServletInputStreamWrapper(newRequestDataBytes);
}
/*
* Forcing any calls to HttpServletRequest.getContentLength to return the accurate length of bytes
* from a modified request
*/
#Override
public int getContentLength() {
return newRequestDataBytes.length;
}
};
return request;
}
}
First, I have a very simple java bean which can be easily serialized to json:
class Node {
private String text;
// getter and setter
}
Node node = new Node();
node.setText("Hello");
String json = new Gson().toJson(node);
// json is { text: "Hello" }
Then in order to make such beans have some dynamic values, so I create a "WithData" base class:
Class WithData {
private Map<String, Object> map = new HashMap<String, Object>();
public void setData(String key, Object value) { map.put(key, value); }
public Object getData(String key) = { return map.get(key); }
}
class Node extends WithData {
private String text;
// getter and setter
}
Now I can set more data to a node:
Node node = new Node();
node.setText("Hello");
node.setData("to", "The world");
But Gson will ignore the "to", the result is still { text: "Hello" }. I expect it to be: { text: "Hello", to: "The world" }
Is there any way to write a serializer for type WithData, that all classes extend it will not only generate its own properties to json, but also the data in the map?
I tried to implement a custom serializer, but failed, because I don't know how to let Gson serialize the properties first, then the data in map.
What I do now is creating a custom serializer:
public static class NodeSerializer implements JsonSerializer<Node> {
public JsonElement serialize(Node src,
Type typeOfSrc, JsonSerializationContext context) {
JsonObject obj = new JsonObject();
obj.addProperty("id", src.id);
obj.addProperty("text", src.text);
obj.addProperty("leaf", src.leaf);
obj.addProperty("level", src.level);
obj.addProperty("parentId", src.parentId);
obj.addProperty("order", src.order);
Set<String> keys = src.getDataKeys();
if (keys != null) {
for (String key : keys) {
obj.add(key, context.serialize(src.getData(key)));
}
}
return obj;
};
}
Then use GsonBuilder to convert it:
Gson gson = new GsonBuilder().
registerTypeAdapter(Node.class, new NodeSerializer()).create();
Tree tree = new Tree();
tree.addNode(node1);
tree.addNode(node2);
gson.toJson(tree);
Then the nodes in the tree will be converted as I expected. The only boring thing is that I need to create a special Gson each time.
Actually, you should expect Node:WithData to serialize as
{
"text": "Hello",
"map": {
"to": "the world"
}
}
(that's with "pretty print" turned on)
I was able to get that serialization when I tried your example. Here is my exact code
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import java.net.MalformedURLException;
import java.util.HashMap;
import java.util.Map;
public class Class1 {
public static void main(String[] args) throws MalformedURLException {
GsonBuilder gb = new GsonBuilder();
Gson g = gb.setPrettyPrinting().create();
Node n = new Node();
n.setText("Hello");
n.setData("to", "the world");
System.out.println(g.toJson(n));
}
private static class WithData {
private Map<String, Object> map = new HashMap<String, Object>();
public void setData(String key, Object value) { map.put(key, value); }
public Object getData(String key) { return map.get(key); }
}
private static class Node extends WithData {
private String text;
public Node() { }
public String getText() {return text;}
public void setText(String text) {this.text = text;}
}
}
I was using the JDK (javac) to compile - that is important because other compilers (those included with some IDEs) may remove the information on which Gson relies as part of their optimization or obfuscation process.
Here are the compilation and execution commands I used:
"C:\Program Files\Java\jdk1.6.0_24\bin\javac.exe" -classpath gson-2.0.jar Class1.java
"C:\Program Files\Java\jdk1.6.0_24\bin\java.exe" -classpath .;gson-2.0.jar Class1
For the purposes of this test, I put the Gson jar file in the same folder as the test class file.
Note that I'm using Gson 2.0; 1.x may behave differently.
Your JDK may be installed in a different location than mine, so if you use those commands, be sure to adjust the path to your JDK as appropriate.
How to parse a JSON object in LWUIT,give me some example or some link from where i can read this.Suppose i have the objects given below.
"{'guild': 'Crimson', 'region': 'us', 'realm': 'Caelestrasz', 'timestamp': 1311860040}"
Json Example Code:This Code will work for json.
package com.ndtv.parser;
import java.io.IOException;
import java.io.InputStream;
import java.util.Vector;
import javax.microedition.io.Connector;
import javax.microedition.io.HttpConnection;
import com.ndtv.callback.jsonActivelistener;
import com.ndtv.datatype.StockActiveItem;
import json.me.JSONArray;
import json.me.JSONException;
import json.me.JSONObject;
public class StockActiveParser {
public Vector jsonObjVector = new Vector();
public JSONArray arrayObj = null;
public String name,LastPrice;
protected jsonActivelistener mjsonListener;
public static boolean ParserCanceled = false;
public void setjsonListener(jsonActivelistener listener) {
mjsonListener = listener;
}
// Non-blocking.
public void parser(final String url) {
Thread t = new Thread() {
public void run() {
// set up the network connection
try {
jsonParse(url);
}
catch (Exception e) {
mjsonListener.parserExceptionListing(e);
}
mjsonListener.parseDidFinishListing();
}
};
t.start();
}
protected void jsonParse(String url) {
StringBuffer stringBuffer = new StringBuffer();
InputStream is = null;
HttpConnection hc = null;
System.out.println(url);
try {
hc = (HttpConnection)Connector.open(url);
is = hc.openInputStream();
int ch;
while ((ch = is.read()) != -1) {
stringBuffer.append((char) ch);
}
}
catch (SecurityException se) {
System.out.println("security exception:"+se);
}
catch (NullPointerException npe) {
System.out.println("null pointer excception:"+npe);
}
catch (IOException ioe) {
System.out.println("io exception:"+ioe);
}
try{
hc.close();
is.close();
}catch(Exception e) {
System.out.println("Error in MostActivePareser Connection close:"+e);
e.printStackTrace();
}
String jsonData = stringBuffer.toString();
try {
JSONObject js = new JSONObject(jsonData);
JSONArray js2 = js.getJSONArray("values");
System.out.println(js2.length());
for (int i = 0; i < js2.length(); i++) {
StockActiveItem item = new StockActiveItem();
JSONObject jsObj = js2.getJSONObject(i);
item.name = jsObj.getString("name");
item.last_traded_price = jsObj.getString("last_traded_price");
item.change = jsObj.getString("change");
item.price_diff = jsObj.getString("price_diff");
item.chart=jsObj.getString("chart");
item.company_id=jsObj.getString("company_id");
mjsonListener.itemParsedListing(item);
}
} catch (JSONException e1) {
System.out.println("Json Data error:"+e1);
e1.printStackTrace();
}
catch (NullPointerException e) {
System.out.println("null error:"+e);
}
}
}
public class StockActiveItem
{
public String name ="";
public String last_traded_price ="";
public String change="";
public String price_diff ="";
public String chart="";
public String company_id="";
public String year_high="";
public String year_low="";
}
you just replace name, for example guild replacing name.If any doubt ask me.
Use LWUIT JSONParser and parser the JSON format string. Just use MIDP_IO.jar from latest LWUIT 1.5 for this.
I was able to use sample code provided in below given links successfully in my app.
http://jimmod.com/blog/2010/03/java-me-j2me-json-implementation-tutorialsample/
http://jimmod.com/blog/2011/09/java-me-j2me-json-implementation-for-array-object/
BTW, Latest JSON ME library can be found here : https://github.com/upictec/org.json.me/
You can use JSON jar and and import that in your project. After that create a JSON object and you can use optString or getString methods of that object accordingly to get the values.