JsonGenerationException when serializing nested object using custom serializer in Jackson - java

Here is the class that I want to serialize.
public class ItemRow<T> {
private String id;
private List<T> items;
}
There are two variations that are allowed.
ItemRow<String>, ItemRow<ItemRow>.
In the latter case, it will be nested.
eg:
ItemRow item1 = new ItemRow("abc", Arrays.asList("item1", "item2", "item3"));
String result = mapper.writeValueAsString(item1);
System.out.println(result);
should give
{
"abc":["item1","item2","item3"]
}
Now, the latter case
ItemRow item2 = new ItemRow("cde", Arrays.asList("item4, item5"));
ItemRow item = new ItemRow("combined", Arrays.asList(item1,item2));
result = mapper.writeValueAsString(item);
System.out.println(result);
should give
{
"combined": {
"abc": ["item1", "item2", "item3"],
"cde": ["item4", "item5"]
}
}
But I get exception while serializing the latter. The first one works as expected. so I believe the recursive serialization is failing, but I am unable to find out why
Here is exception
com.fasterxml.jackson.core.JsonGenerationException: Can not start an object, expecting field name (context: Object)
at com.fasterxml.jackson.core.JsonGenerator._reportError(JsonGenerator.java:1961)
at com.fasterxml.jackson.core.json.JsonGeneratorImpl._reportCantWriteValueExpectName(JsonGeneratorImpl.java:244)
at com.fasterxml.jackson.core.json.WriterBasedJsonGenerator._verifyValueWrite(WriterBasedJsonGenerator.java:866)
at com.fasterxml.jackson.core.json.WriterBasedJsonGenerator.writeStartObject(WriterBasedJsonGenerator.java:279)
at hello.ItemRowSerializer.serialize(ItemRow.java:58)
at hello.ItemRowSerializer.serialize(ItemRow.java:42)
at com.fasterxml.jackson.databind.ser.DefaultSerializerProvider._serialize(DefaultSerializerProvider.java:480)
at com.fasterxml.jackson.databind.ser.DefaultSerializerProvider.serializeValue(DefaultSerializerProvider.java:319)
at com.fasterxml.jackson.databind.ObjectMapper.writeValue(ObjectMapper.java:2655)
at com.fasterxml.jackson.core.base.GeneratorBase.writeObject(GeneratorBase.java:381)
at hello.ItemRowSerializer.serialize(ItemRow.java:67)
at hello.ItemRowSerializer.serialize(ItemRow.java:42)
Serializer implementation
class ItemRowSerializer extends JsonSerializer<ItemRow> {
#Override
public void serialize(ItemRow itemRow, JsonGenerator jgen, SerializerProvider serializerProvider) throws IOException {
String id = itemRow.getId();
List<Object> items = itemRow.getItems();
if (items.isEmpty()) {
jgen.writeStartObject();
jgen.writeFieldName(id);
jgen.writeStartArray();
jgen.writeEndArray();
jgen.writeEndObject();
}
else {
jgen.writeStartObject();
Object item = items.get(0);
jgen.writeFieldName(id);
if (item instanceof ItemRow){
for (Object i : items) {
//ItemRow temp = (ItemRow) i;
//jgen.writeObjectField(temp.getId(), temp);
//jgen.writeObjectField(id, i);
jgen.writeStartObject();
jgen.writeObject(i);
jgen.writeEndObject();
}
}
else {
//jgen.writeFieldName(id);
jgen.writeStartArray();
for (Object arg : items) {
jgen.writeString(arg.toString());
}
jgen.writeEndArray();
}
}
jgen.writeEndObject();
}
}

Your serializer algoritihm is incorrect. The code is down above. You do not need to start object when you are directly deserializing an object. I removed this steps and minimized the code.
Example Test;
#Test
public void serializeTest() throws JsonProcessingException
{
ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.addSerializer(ItemRow.class, new ItemRowSerializer());
mapper.registerModule(module);
ItemRow item1 = new ItemRow("abc", Arrays.asList("item1", "item2", "item3"));
String result = mapper.writeValueAsString(item1);
System.out.println(result);
ItemRow item2 = new ItemRow("cde", Arrays.asList("item4", "item5"));
ItemRow item6 = new ItemRow("deeper-1", Arrays.asList("item6", "item7"));
ItemRow item7 = new ItemRow("deeper-2", Arrays.asList("item6", "item7"));
ItemRow item8 = new ItemRow("deeper", Arrays.asList(item6, item7));
ItemRow item3 = new ItemRow("inner-1", Arrays.asList("item6", "item7"));
ItemRow item4 = new ItemRow("inner-2", Arrays.asList("item6", "item7"));
ItemRow item5 = new ItemRow("inner", Arrays.asList(item3, item4, item8));
ItemRow item = new ItemRow("combined", Arrays.asList(item1,item2,item5));
result = mapper.writeValueAsString(item);
System.out.println(result);
}
Algorithm;
public class ItemRowSerializer extends JsonSerializer<ItemRow>
{
#Override
public void serialize(ItemRow itemRow, JsonGenerator jgen, SerializerProvider serializerProvider) throws IOException
{
jgen.writeStartObject();
writeInnerObject(jgen, itemRow);
jgen.writeEndObject();
}
private void writeStringArr(JsonGenerator jgen, List items) throws IOException
{
jgen.writeStartArray();
for (Object arg : items)
{
jgen.writeString(arg.toString());
}
jgen.writeEndArray();
}
private void writeInnerObject(JsonGenerator jgen, ItemRow row) throws IOException
{
jgen.writeFieldName(row.getId());
if (row.getItems().size() > 0 && row.getItems().get(0) instanceof ItemRow)
{
jgen.writeStartObject();
for (int i = 0; i < row.getItems().size(); i++)
{
ItemRow innerRow = (ItemRow) row.getItems().get(i);
if( innerRow.getItems().size() > 0 && innerRow.getItems().get(0) instanceof ItemRow )
{
writeInnerObject(jgen, innerRow);
}
else
{
jgen.writeFieldName(innerRow.getId());
writeStringArr(jgen, innerRow.getItems());
}
}
jgen.writeEndObject();
}
else
{
writeStringArr(jgen, row.getItems());
}
}
}

Related

Recursive call works only for super parent and not for children

I am trying to get the list of strings from a REST response like below:
private List<String> recursiveRestCall(String folderId) throws JsonMappingException, JsonProcessingException {
List<String> fileList = new ArrayList<>();
Mono<String> mono = webClient.get().uri("/some/api/" + folderId + "/endpoint/goes/here").retrieve()
.bodyToMono(String.class);
final ObjectNode node = new ObjectMapper().readValue(mono.block(), ObjectNode.class);
if (node.get("parent").get("children").isArray()) {
for (JsonNode jsonNode : node.get("parent").get("children")) {
if (jsonNode.get("child").get("isFile").asBoolean()) {
fileList.add(jsonNode.toString());
}
if (jsonNode.get("child").get("isFolder").asBoolean()) {
recursiveRestCall(jsonNode.get("child").get("id").toString().replaceAll("\"", "")); // This is not working and no error whatsoever.
}
}
return fileList;
}
return null;
}
Now, as highlighted in the comment in above snippet. Only the if (jsonNode.get("child").get("isFile").asBoolean()) { condition is executed and I get the list items. I know there are few subfolders having files in them. So effectively the super parent's children are retrieved. But not from the child who is be a parent (as subfolder) too.
What I am missing here?
Save result of this like=
List<String> tempList = recursiveRestCall(jsonNode.get("child").get("id").toString().replaceAll("\"", ""));
if(fileList !=null)
list.addAll(tempList);
Based on the comments, You can edit this method like below:
private List<String> recursiveRestCall(String folderId) throws JsonMappingException, JsonProcessingException {
List<String> fileList = new ArrayList<>();
Mono<String> mono = webClient.get().uri("/some/api/" + folderId + "/endpoint/goes/here").retrieve()
.bodyToMono(String.class);
final ObjectNode node = new ObjectMapper().readValue(mono.block(), ObjectNode.class);
if (node.get("parent").get("children").isArray()) {
for (JsonNode jsonNode : node.get("parent").get("children")) {
if (jsonNode.get("child").get("isFile").asBoolean()) {
fileList.add(jsonNode.toString());
}
if (jsonNode.get("child").get("isFolder").asBoolean()) {
fileList.addAll(recursiveRestCall(jsonNode.get("child").get("id").toString().replaceAll("\"", ""))); // This is not working and no error whatsoever.
}
}
return fileList;
}
return fileList;
}

Custom Serialize List of Objects in Jackson

I have a method which does the following:
final DaySerializer daySerializer = new DaySerializer(Day.class);
final ObjectMapper mapper = new ObjectMapper();
final SimpleModule module = new SimpleModule("DaySerializer", new Version(2, 1, 3, null, null, null));
module.addSerializer(Day.class, daySerializer);
mapper.registerModule(module);
try {
mapper.writerWithDefaultPrettyPrinter().writeValue(new File(this.jsonPath + "/Days.json"), days);
} catch (final Exception e) {
e.printStackTrace();
}
And the serializer class method:
#Override
public void serialize(final Day day, final JsonGenerator jsonGenerator, final SerializerProvider serializerProvider)
throws IOException {
jsonGenerator.writeStartObject();
//Date
SimpleDateFormat format1 = new SimpleDateFormat("yyyy-MM-dd");
jsonGenerator.writeStringField("calendar", "" + format1.format(day.getCalendar().getTime()));
//Day index
jsonGenerator.writeNumberField("dayCount", day.getDayCount());
//Demand
for (final Entry<String, int[]> e : day.getDemand().entrySet()) {
jsonGenerator.writeFieldName("Demand: "+e.getKey());
jsonGenerator.writeStartArray();
for(int i : e.getValue()) {
jsonGenerator.writeNumber(i);
}
jsonGenerator.writeEndArray();
}
//Employee shift allocations
for(EmployeeShiftAllocation eSA : day.getEmployeeShiftAllocations()) {
jsonGenerator.writeNumberField("eSA ID", eSA.getId());
}
jsonGenerator.writeEndObject();
}
Which works for the first Day object in the List which is handed in to it, but doesn't show any errors, but only exports the first Day object. This is confusing since I do hand in the List. Am I missing something simple? Do I need to handle this inside the serializer somehow?

Java+Jackson : How to serialize a object containing array/list of other objects

I am trying to generate following json dynamically on the basis of input params i.e (content's version, list of content Ids)
{
"query": {
"bool": {
"must": [
{
"term": {
"version": {
"value": "published"
}
}
},
{
"terms": {
"contentId": [
"contentId-123",
"contentId-456"
]
}
}
]
}
}
}
The above json is a query body for elastic-search delete request.
version and contentId mentioned in above json are actual fields/attributes of content object or data model.
I am getting this exception while serializing the object:
com.fasterxml.jackson.core.JsonGenerationException: Can not start an object, expecting field name
at com.fasterxml.jackson.core.JsonGenerator._reportError(JsonGenerator.java:1886)
at com.fasterxml.jackson.core.json.WriterBasedJsonGenerator._verifyPrettyValueWrite(WriterBasedJsonGenerator.java:832)
at com.fasterxml.jackson.core.json.WriterBasedJsonGenerator._verifyValueWrite(WriterBasedJsonGenerator.java:797)
at com.fasterxml.jackson.core.json.WriterBasedJsonGenerator.writeStartObject(WriterBasedJsonGenerator.java:268)
at com.cdk.dmg.services.sitecontent.util.ConstantsTest$DeleteQuerySerializer.serialize(Test.java:200)
at com.cdk.dmg.services.sitecontent.util.ConstantsTest$DeleteQuerySerializer.serialize(Test.java:183)
at com.fasterxml.jackson.databind.ser.DefaultSerializerProvider.serializeValue(DefaultSerializerProvider.java:292)
at com.fasterxml.jackson.databind.ObjectWriter$Prefetch.serialize(ObjectWriter.java:1419)
at com.fasterxml.jackson.databind.ObjectWriter._configAndWriteValue(ObjectWriter.java:1147)
at com.fasterxml.jackson.databind.ObjectWriter.writeValueAsString(ObjectWriter.java:1020)
Here is my code:
class SearchParam {
boolean isMultivalued;
String fieldName;
String value;
List<String> values;
#Override
public String toString() {
final StringBuilder sb = new StringBuilder("SearchParam{");
sb.append("isMultivalued=").append(isMultivalued);
sb.append(", fieldName='").append(fieldName).append('\'');
sb.append(", value='").append(value).append('\'');
sb.append(", values=").append(values);
sb.append('}');
return sb.toString();
}
}
class DeleteQuery {
List<SearchParam> mustParams;
}
class DeleteQuerySerializer extends StdSerializer<DeleteQuery> {
protected DeleteQuerySerializer(Class<DeleteQuery> t) {
super(t);
}
public DeleteQuerySerializer() {
this(null);
}
#Override
public void serialize(DeleteQuery value, JsonGenerator jsonGen, SerializerProvider provider) throws IOException {
jsonGen.writeStartObject();
jsonGen.writeObjectFieldStart("query");
jsonGen.writeObjectFieldStart("bool");
jsonGen.writeArrayFieldStart("must");
for (SearchParam param : value.mustParams) {
jsonGen.writeObject(param);
}
jsonGen.writeEndObject();
}
}
class SearchParamSerializer extends StdSerializer<SearchParam> {
protected SearchParamSerializer(Class<SearchParam> t) {
super(t);
}
protected SearchParamSerializer() {
this(null);
}
#Override
public void serialize(SearchParam value, JsonGenerator jsonGen, SerializerProvider provider) throws IOException, JsonGenerationException {
jsonGen.writeStartObject();
if (value.isMultivalued) {
jsonGen.writeObjectFieldStart("terms");
jsonGen.writeArrayFieldStart(value.fieldName);
for (String v : value.values) {
jsonGen.writeString(v);
}
jsonGen.writeEndArray();
} else {
jsonGen.writeObjectFieldStart("term");
jsonGen.writeObjectFieldStart(value.fieldName);
jsonGen.writeStringField("value", value.value);
}
jsonGen.writeEndObject();
}
}
#Test
public void dummyTest() throws JsonProcessingException {
SearchParam versionParam = new SearchParam();
versionParam.fieldName = "version";
versionParam.isMultivalued = false;
versionParam.value = "published";
SearchParam idParam = new SearchParam();
idParam.fieldName = "contentId";
idParam.isMultivalued = true;
idParam.values = Arrays.asList("contentID-1", "contentID-2", "contentID-3");
List<SearchParam> mustParams = Arrays.asList(versionParam, idParam);
ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.addSerializer(SearchParam.class, new SearchParamSerializer());
module.addSerializer(DeleteQuery.class, new DeleteQuerySerializer());
mapper.registerModule(module);
DeleteQuery deleteQuery = new DeleteQuery();
deleteQuery.mustParams = mustParams;
String serialized = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(deleteQuery);
System.out.println(serialized);
}
Reference:
jackson-custom-serialization
I did little bit tweaks in code (e.g. inside SearchParamSerializer.serialize(), rearranged the jsonGen.writeStartObject() and jsonGen.writeEndObject() with respect to value.isMultivalued() condition), but still not getting result as expected.
After googling and stack-overflowing a lot, I got few links, but they talk more about Gson library, and/or are in python.
Any help!
By the way, did you have a look to this API : https://www.elastic.co/guide/en/elasticsearch/client/java-api/current/index.html?
My workaround to proceed for now.
Modified DeleteQuery and DeleteQuerySerializer as below:
class DeleteQuery {
List<String> mustParams;
}
class DeleteQuerySerializer extends StdSerializer<DeleteQuery> {
protected DeleteQuerySerializer(Class<DeleteQuery> t) {
super(t);
}
public DeleteQuerySerializer() {
this(null);
}
#Override
public void serialize(DeleteQuery value, JsonGenerator jsonGen, SerializerProvider provider) throws IOException {
jsonGen.writeStartObject();
jsonGen.writeObjectFieldStart("query");
jsonGen.writeObjectFieldStart("bool");
String mustParam = String.join(",", value.mustParams);
jsonGen.writeFieldName("must");
jsonGen.writeRawValue("[" + mustParam + "]");
jsonGen.writeEndObject();
}
}
And, here is dummyTest():
public void dummyTest() throws IOException {
System.out.println();
SearchParam versionParam = new SearchParam();
versionParam.fieldName = "version";
versionParam.isMultivalued = false;
versionParam.value = "published";
SearchParam idParam = new SearchParam();
idParam.fieldName = "contentId";
idParam.isMultivalued = true;
idParam.values = Arrays.asList("contentId-123", "contentId-456", "xyz");
List<SearchParam> mustParams = Arrays.asList(versionParam, idParam);
ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.addSerializer(SearchParam.class, new SearchParamSerializer());
module.addSerializer(DeleteQuery.class, new DeleteQuerySerializer());
mapper.registerModule(module);
List<String> mustParamList = new ArrayList<>();
for (SearchParam param : mustParams) {
mustParamList.add(mapper.writeValueAsString(param));
}
DeleteQuery deleteQuery = new DeleteQuery();
deleteQuery.mustParams = mustParamList;
String serialized = mapper.writeValueAsString(deleteQuery);
JsonNode jsonNode = mapper.readTree(serialized);
String s = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(jsonNode);
System.out.println(s);
}
Finally, got this output:
{
"query": {
"bool": {
"must": [{
"term": {
"version": {
"value": "published"
}
}
}, {
"terms": {
"contentId": ["contentId-123", "contentId-456", "xyz"]
}
}]
}
}
}
:)
This is not a proper solution, but yes I am unblocked now and can continue further. :)

Parsing JSON in Java without knowing JSON format

I am trying to parse JSON strings in Java and find the key-value pairs so that I can determine the approximate structure of the JSON object since object structure of JSON string is unknown.
For example, one execution may have a JSON string like this:
{"id" : 12345, "days" : [ "Monday", "Wednesday" ], "person" : { "firstName" : "David", "lastName" : "Menoyo" } }
And another like this:
{"url" : "http://someurl.com", "method" : "POST", "isauth" : false }
How would I cycle through the various JSON elements and determine the keys and their values? I looked at jackson-core's JsonParser. I see how I can grab the next "token" and determine what type of token it is (i.e., field name, value, array start, etc), but, I don't know how to grab the actual token's value.
For example:
public void parse(String json) {
try {
JsonFactory f = new JsonFactory();
JsonParser parser = f.createParser(json);
JsonToken token = parser.nextToken();
while (token != null) {
if (token.equals(JsonToken.START_ARRAY)) {
logger.debug("Start Array : " + token.toString());
} else if (token.equals(JsonToken.END_ARRAY)) {
logger.debug("End Array : " + token.toString());
} else if (token.equals(JsonToken.START_OBJECT)) {
logger.debug("Start Object : " + token.toString());
} else if (token.equals(JsonToken.END_OBJECT)) {
logger.debug("End Object : " + token.toString());
} else if (token.equals(JsonToken.FIELD_NAME)) {
logger.debug("Field Name : " + token.toString());
} else if (token.equals(JsonToken.VALUE_FALSE)) {
logger.debug("Value False : " + token.toString());
} else if (token.equals(JsonToken.VALUE_NULL)) {
logger.debug("Value Null : " + token.toString());
} else if (token.equals(JsonToken.VALUE_NUMBER_FLOAT)) {
logger.debug("Value Number Float : " + token.toString());
} else if (token.equals(JsonToken.VALUE_NUMBER_INT)) {
logger.debug("Value Number Int : " + token.toString());
} else if (token.equals(JsonToken.VALUE_STRING)) {
logger.debug("Value String : " + token.toString());
} else if (token.equals(JsonToken.VALUE_TRUE)) {
logger.debug("Value True : " + token.toString());
} else {
logger.debug("Something else : " + token.toString());
}
token = parser.nextToken();
}
} catch (Exception e) {
logger.error("", e);
}
}
Is there a class in jackson or some other library (gson or simple-json) that produces a tree, or allows one to cycle through the json elements and obtain the actual key names in addition to the values?
Take a look at Jacksons built-in tree model feature.
And your code will be:
public void parse(String json) {
JsonFactory factory = new JsonFactory();
ObjectMapper mapper = new ObjectMapper(factory);
JsonNode rootNode = mapper.readTree(json);
Iterator<Map.Entry<String,JsonNode>> fieldsIterator = rootNode.fields();
while (fieldsIterator.hasNext()) {
Map.Entry<String,JsonNode> field = fieldsIterator.next();
System.out.println("Key: " + field.getKey() + "\tValue:" + field.getValue());
}
}
If a different library is fine for you, you could try org.json:
JSONObject object = new JSONObject(myJSONString);
String[] keys = JSONObject.getNames(object);
for (String key : keys)
{
Object value = object.get(key);
// Determine type of value and do something with it...
}
Find the following code for Unknown Json Object parsing using Gson library.
public class JsonParsing {
static JsonParser parser = new JsonParser();
public static HashMap<String, Object> createHashMapFromJsonString(String json) {
JsonObject object = (JsonObject) parser.parse(json);
Set<Map.Entry<String, JsonElement>> set = object.entrySet();
Iterator<Map.Entry<String, JsonElement>> iterator = set.iterator();
HashMap<String, Object> map = new HashMap<String, Object>();
while (iterator.hasNext()) {
Map.Entry<String, JsonElement> entry = iterator.next();
String key = entry.getKey();
JsonElement value = entry.getValue();
if (null != value) {
if (!value.isJsonPrimitive()) {
if (value.isJsonObject()) {
map.put(key, createHashMapFromJsonString(value.toString()));
} else if (value.isJsonArray() && value.toString().contains(":")) {
List<HashMap<String, Object>> list = new ArrayList<>();
JsonArray array = value.getAsJsonArray();
if (null != array) {
for (JsonElement element : array) {
list.add(createHashMapFromJsonString(element.toString()));
}
map.put(key, list);
}
} else if (value.isJsonArray() && !value.toString().contains(":")) {
map.put(key, value.getAsJsonArray());
}
} else {
map.put(key, value.getAsString());
}
}
}
return map;
}
}
JSON of unknown format to HashMap
writing JSON And reading Json
public static JsonParser parser = new JsonParser();
public static void main(String args[]) {
writeJson("JsonFile.json");
readgson("JsonFile.json");
}
public static void readgson(String file) {
try {
System.out.println( "Reading JSON file from Java program" );
FileReader fileReader = new FileReader( file );
com.google.gson.JsonObject object = (JsonObject) parser.parse( fileReader );
Set <java.util.Map.Entry<String, com.google.gson.JsonElement>> keys = object.entrySet();
if ( keys.isEmpty() ) {
System.out.println( "Empty JSON Object" );
}else {
Map<String, Object> map = json_UnKnown_Format( keys );
System.out.println("Json 2 Map : "+map);
}
} catch (IOException ex) {
System.out.println("Input File Does not Exists.");
}
}
public static Map<String, Object> json_UnKnown_Format( Set <java.util.Map.Entry<String, com.google.gson.JsonElement>> keys ){
Map<String, Object> jsonMap = new HashMap<String, Object>();
for (Entry<String, JsonElement> entry : keys) {
String keyEntry = entry.getKey();
System.out.println(keyEntry + " : ");
JsonElement valuesEntry = entry.getValue();
if (valuesEntry.isJsonNull()) {
System.out.println(valuesEntry);
jsonMap.put(keyEntry, valuesEntry);
}else if (valuesEntry.isJsonPrimitive()) {
System.out.println("P - "+valuesEntry);
jsonMap.put(keyEntry, valuesEntry);
}else if (valuesEntry.isJsonArray()) {
JsonArray array = valuesEntry.getAsJsonArray();
List<Object> array2List = new ArrayList<Object>();
for (JsonElement jsonElements : array) {
System.out.println("A - "+jsonElements);
array2List.add(jsonElements);
}
jsonMap.put(keyEntry, array2List);
}else if (valuesEntry.isJsonObject()) {
com.google.gson.JsonObject obj = (JsonObject) parser.parse(valuesEntry.toString());
Set <java.util.Map.Entry<String, com.google.gson.JsonElement>> obj_key = obj.entrySet();
jsonMap.put(keyEntry, json_UnKnown_Format(obj_key));
}
}
return jsonMap;
}
#SuppressWarnings("unchecked")
public static void writeJson( String file ) {
JSONObject json = new JSONObject();
json.put("Key1", "Value");
json.put("Key2", 777); // Converts to "777"
json.put("Key3", null);
json.put("Key4", false);
JSONArray jsonArray = new JSONArray();
jsonArray.put("Array-Value1");
jsonArray.put(10);
jsonArray.put("Array-Value2");
json.put("Array : ", jsonArray); // "Array":["Array-Value1", 10,"Array-Value2"]
JSONObject jsonObj = new JSONObject();
jsonObj.put("Obj-Key1", 20);
jsonObj.put("Obj-Key2", "Value2");
jsonObj.put(4, "Value2"); // Converts to "4"
json.put("InnerObject", jsonObj);
JSONObject jsonObjArray = new JSONObject();
JSONArray objArray = new JSONArray();
objArray.put("Obj-Array1");
objArray.put(0, "Obj-Array3");
jsonObjArray.put("ObjectArray", objArray);
json.put("InnerObjectArray", jsonObjArray);
Map<String, Integer> sortedTree = new TreeMap<String, Integer>();
sortedTree.put("Sorted1", 10);
sortedTree.put("Sorted2", 103);
sortedTree.put("Sorted3", 14);
json.put("TreeMap", sortedTree);
try {
System.out.println("Writting JSON into file ...");
System.out.println(json);
FileWriter jsonFileWriter = new FileWriter(file);
jsonFileWriter.write(json.toJSONString());
jsonFileWriter.flush();
jsonFileWriter.close();
System.out.println("Done");
} catch (IOException e) {
e.printStackTrace();
}
}
Here is a sample I wrote shows how I parse a json and mess every number inside it:
public class JsonParser {
public static Object parseAndMess(Object object) throws IOException {
String json = JsonUtil.toJson(object);
JsonNode jsonNode = parseAndMess(json);
if(null != jsonNode)
return JsonUtil.toObject(jsonNode, object.getClass());
return null;
}
public static JsonNode parseAndMess(String json) throws IOException {
JsonNode rootNode = parse(json);
return mess(rootNode, new Random());
}
private static JsonNode parse(String json) throws IOException {
JsonFactory factory = new JsonFactory();
ObjectMapper mapper = new ObjectMapper(factory);
JsonNode rootNode = mapper.readTree(json);
return rootNode;
}
private static JsonNode mess(JsonNode rootNode, Random rand) throws IOException {
if (rootNode instanceof ObjectNode) {
Iterator<Map.Entry<String, JsonNode>> fieldsIterator = rootNode.fields();
while (fieldsIterator.hasNext()) {
Map.Entry<String, JsonNode> field = fieldsIterator.next();
replaceObjectNode((ObjectNode) rootNode, field, rand);
}
} else if (rootNode instanceof ArrayNode) {
ArrayNode arrayNode = ((ArrayNode) rootNode);
replaceArrayNode(arrayNode, rand);
}
return rootNode;
}
private static void replaceObjectNode(ObjectNode rootNode, Map.Entry<String, JsonNode> field, Random rand)
throws IOException {
JsonNode childNode = field.getValue();
if (childNode instanceof IntNode) {
(rootNode).put(field.getKey(), rand.nextInt(1000));
} else if (childNode instanceof LongNode) {
(rootNode).put(field.getKey(), rand.nextInt(1000000));
} else if (childNode instanceof FloatNode) {
(rootNode).put(field.getKey(), format(rand.nextFloat()));
} else if (childNode instanceof DoubleNode) {
(rootNode).put(field.getKey(), format(rand.nextFloat()));
} else {
mess(childNode, rand);
}
}
private static void replaceArrayNode(ArrayNode arrayNode, Random rand) throws IOException {
int arrayLength = arrayNode.size();
if(arrayLength == 0)
return;
if (arrayNode.get(0) instanceof IntNode) {
for (int i = 0; i < arrayLength; i++) {
arrayNode.set(i, new IntNode(rand.nextInt(10000)));
}
} else if (arrayNode.get(0) instanceof LongNode) {
arrayNode.removeAll();
for (int i = 0; i < arrayLength; i++) {
arrayNode.add(rand.nextInt(1000000));
}
} else if (arrayNode.get(0) instanceof FloatNode) {
arrayNode.removeAll();
for (int i = 0; i < arrayLength; i++) {
arrayNode.add(format(rand.nextFloat()));
}
} else if (arrayNode.get(0) instanceof DoubleNode) {
arrayNode.removeAll();
for (int i = 0; i < arrayLength; i++) {
arrayNode.add(format(rand.nextFloat()));
}
} else {
for (int i = 0; i < arrayLength; i++) {
mess(arrayNode.get(i), rand);
}
}
}
public static void print(JsonNode rootNode) throws IOException {
System.out.println(rootNode.toString());
}
private static double format(float a) {
return Math.round(a * 10000.0) / 100.0;
}
}
Would you be satisfied with a Map from Jackson?
ObjectMapper objectMapper = new ObjectMapper();
Map<String, Object> map = objectMapper.readValue(jsonString, new TypeReference<HashMap<String,Object>>(){});
Or maybe a JsonNode?
JsonNode jsonNode = objectMapper.readTree(String jsonString)

jackson - json encoding of doubles with controlled precision

I'm encoding a complex Map structure with arrays of double values.
High precision is not important and output size is, so I'm trying to get the JSON tool (Jackson in this case) to serialize the double values using a provided DecimalFormat.
The following is my best shot, but this fails as the serializer is not picked by the object mapper to encode the array:
class MyTest
{
public class MyDoubleSerializer extends JsonSerializer<double[]>
{
public void serialize(double[] value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException
{
for (double d : value)
{
jgen.writeStartArray();
jgen.writeRaw( df.format( d ) );
jgen.writeEndArray();
}
}
}
#Test
public void test1() throws Exception
{
ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule("MyModule", new Version(0, 1, 0, "alpha"));
module.addSerializer(double[].class, new MyDoubleSerializer());
mapper.registerModule(module);
Map<String, Object> data = new HashMap<String, Object>();
double[] doubleList = { 1.1111111111D, (double) (System.currentTimeMillis()) };
data.put( "test", doubleList );
System.out.print( mapper.writeValueAsString( data ));
}
}
The output is:
{"test":[1.1111111111,1.315143204964E12}
What I was looking for:
{"test":[1.32E12, 1.11E0]}
Any ideas?
Also, I don't like having to generate a String and write is as raw - is there I could feed a StringBuffer into into DecimalFormat to do this?
Thanks
Managed to resolve this, by borrowing from the built-in serializer for Double.
It's a bit of a hack, because writeRaw() doesn't care about the context and doesn't write a comma between array members, so I'm casting the Json writer and calling its writeValue() method to handle this.
Strangely enough, this does not work on the example in the question (again doesn't get called for serializing these doubles), but does work on my real-world object which is more complex.
Enjoy...
public class JacksonDoubleArrayTest
{
private DecimalFormat df = new DecimalFormat( "0.##E0" );
public class MyDoubleSerializer extends org.codehaus.jackson.map.ser.ScalarSerializerBase<Double>
{
protected MyDoubleSerializer()
{
super( Double.class );
}
#Override
public final void serializeWithType( Double value, JsonGenerator jgen, SerializerProvider provider, TypeSerializer typeSer ) throws IOException,
JsonGenerationException
{
serialize( value, jgen, provider );
}
#Override
public void serialize( Double value, JsonGenerator jgen, SerializerProvider provider ) throws IOException, JsonGenerationException
{
if ( Double.isNaN( value ) || Double.isInfinite( value ) )
{
jgen.writeNumber( 0 ); // For lack of a better alternative in JSON
return;
}
String x = df.format( value );
if ( x.endsWith( "E0" ) )
{
x = x.substring( 0, x.length() - 2 );
}
else if ( x.endsWith( "E1" ) && x.length() == 6 )
{
x = "" + x.charAt( 0 ) + x.charAt( 2 ) + '.' + x.charAt( 3 );
}
JsonWriteContext ctx = (JsonWriteContext)jgen.getOutputContext();
ctx.writeValue();
if ( jgen.getOutputContext().getCurrentIndex() > 0 )
{
x = "," + x;
}
jgen.writeRaw( x );
}
#Override
public JsonNode getSchema( SerializerProvider provider, Type typeHint )
{
return createSchemaNode( "number", true );
}
}
#SuppressWarnings("unchecked")
private static Map<String, Object> load() throws JsonParseException, JsonMappingException, IOException
{
ObjectMapper loader = new ObjectMapper();
return (Map<String, Object>)loader.readValue( new File( "x.json" ), Map.class );
}
#Test
public void test1() throws JsonGenerationException, JsonMappingException, IOException
{
ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule( "StatsModule", new Version( 0, 1, 0, "alpha" ) );
module.addSerializer( Double.class, new MyDoubleSerializer() );
mapper.registerModule( module );
String out = mapper.writeValueAsString( load() );
// System.out.println( out.length() );
}
}

Categories

Resources