I have a JSON String:
{
"productName": "Gold",
"offerStartDate": "01152023",
"offerEndDate": "01152024",
"offerAttributes": [
{
"id": "TGH-DAD3KVF3",
"storeid": "STG-67925",
"availability": true
}
],
"offerSpecifications": {
"price": 23.25
}
}
The validation logic for the same is written as
Map<String, Object> map = mapper.readValue(json, Map.class);
String productNameValue = (String)map.get("productName");
if(productNameValue ==null && productNameValue.isEmpty()) {
throw new Exception();
}
String offerStartDateValue = (String)map.get("offerStartDate");
if(offerStartDateValue ==null && offerStartDateValue.isEmpty()) {
throw new Exception();
}
List<Object> offerAttributesValue = (List)map.get("offerAttributes");
if(offerAttributesValue ==null && offerAttributesValue.isEmpty()) {
throw new Exception();
}
Map<String, Object> offerSpecificationsValue = (Map)map.get("offerSpecifications");
if(offerSpecificationsValue ==null && offerSpecificationsValue.isEmpty() && ((String)offerSpecificationsValue.get("price")).isEmpty()) {
throw new Exception();
}
This is a portion of the JSON response. The actual response has more than 48 fields and the. The existing code has validation implemented for all the 48 fields as above. Note the responses are way complex than these.
I feel the validation code has very verbose and is very repetitive. How do, I fix this? What design pattern should I use to write a validation logic. I have seen builder pattern, but not sure how to use it for this scenario.
Build a JSON template for comparison.
ObjectMapper mapper = new ObjectMapper();
JsonNode template = mapper.readTree(
"{" +
" \"productName\": \"\"," +
" \"offerStartDate\": \"\"," +
" \"offerEndDate\": \"\"," +
" \"offerAttributes\": []," +
" \"offerSpecifications\": {" +
" \"price\": 0" +
" }" +
"}");
JsonNode data = mapper.readTree(
"{" +
" \"productName\": \"Gold\"," +
" \"offerStartDate\": \"01152023\"," +
" \"offerEndDate\": \"01152024\"," +
" \"offerAttributes\": [" +
" {" +
" \"id\": \"TGH-DAD3KVF3\"," +
" \"storeid\": \"STG-67925\"," +
" \"availability\": true" +
" }" +
" ]," +
" \"offerSpecifications\": {" +
" \"price\": 23.25" +
" }" +
"}");
validate(template, data);
Here is the recursion function to compare the template and the data.
public void validate(JsonNode template, JsonNode data) throws Exception {
final Iterator<Map.Entry<String, JsonNode>> iterator = template.fields();
while (iterator.hasNext()) {
final Map.Entry<String, JsonNode> entry = iterator.next();
JsonNode dataValue = data.get(entry.getKey());
if (dataValue == null || dataValue.isNull()) {
throw new Exception("Missing " + entry.getKey());
}
if (entry.getValue().getNodeType() != dataValue.getNodeType()) {
throw new Exception("Mismatch data type: " + entry.getKey());
}
switch (entry.getValue().getNodeType()) {
case STRING:
if (dataValue.asText().isEmpty()) {
throw new Exception("Missing " + entry.getKey());
}
break;
case OBJECT:
validate(entry.getValue(), dataValue);
break;
case ARRAY:
if (dataValue.isEmpty()) {
throw new Exception("Array " + entry.getKey() + " must not be empty");
}
break;
}
}
}
Option 1
If you are able to deserialize into a class instead of a map, you can use Bean Validaion to do something like this:
class Product {
#NotEmpty String productName;
#NotEmpty String offerStartDate;
#NotEmpty String offerEndDate;
}
You can then write your own #MyCustomValidation for any custom validation.
Option 2
If you must keep the object as a Map, you can extract each of your validations into validator objects make things extensible/composable/cleaner, roughly like this:
#FunctionalInterface
interface Constraint {
void validate(Object value);
default Constraint and(Constraint next) {
return (value) -> {
this.validate(value);
next.validate(value);
};
}
}
var constraints = new HashMap<Validator>();
constraints.put("productName", new NotEmpty());
constraints.put("offerStartDate", new NotEmpty());
constraints.put("someNumber", new LessThan(10).and(new GreaterThan(5)));
// Then
map.forEach((key, value) -> {
var constraint = constraints.get(key);
if (constraint != null) {
constraint.validate(value);
}
});
Hello, you can validate JSON with the following approach.
I created a mini, scaled-down version of yours but it should point you towards the right direction hopefully.
I used the Jackson libary, specifically I used the object mapper.
Here is some documentation to the object mapper - https://www.baeldung.com/jackson-object-mapper-tutorial.
So I created a simple Product class.
The Product class was composed of :
A empty constructor
An all arguments constructor
Three private properties, Name, Price, InStock
Getters & Setters
Here is my Main.java code.
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.Map;
public class Main {
public static void main(String[] args) throws Exception {
// Mock JSON
String json = """
{
"name" : "",
"price" : 5.99,
"inStock": true
}""";
// Declare & Initialise a new object mapper
ObjectMapper mapper = new ObjectMapper();
// Declare & Initialise a new product object
Product product = null;
try {
// Try reading the values from the json into product object
product = mapper.readValue(json, Product.class);
}
catch (Exception exception) {
System.out.println("Exception");
System.out.println(exception.getMessage());
return;
}
// Map out the object's field names to the field values
Map<String, Object> propertiesOfProductObject = mapper.convertValue(product, Map.class);
// Loop through all the entries in the map. i.e. the fields and their values
for(Map.Entry<String, Object> entry : propertiesOfProductObject.entrySet()){
// If a value is empty throw an exception
// Else print it to the console
if (entry.getValue().toString().isEmpty()) {
throw new Exception("Missing Attribute : " + entry.getKey());
} else {
System.out.println(entry.getKey() + "-->" + entry.getValue());
}
}
}
}
It throws an error sauing the name field is empty.
Related
I have JSON-object which has a dynamic key inside it. I need to get a specific value mapped to this dynamic Key.
For example: value "10.00" will be returned for the key "value" and value REFUND_COMPLETED will be obtained as a result for the key "refundState"
Code:
public static void main(String[] args) {
String json2 = "{\n"
+ " \"refundStatusDetails\": {\n"
+ " \"txn_f2a7802c-ef84-43c3-8615-5f706b995c23\": {\n"
+ " \"refundTransactionId\": \"txn_f2a7802c-ef84-43c3-8615-5f706b995c23\",\n"
+ " \"requestId\": \"refund-request-id-1\",\n"
+ " \"refundState\": \"REFUND_COMPLETED\",\n"
+ " \"amount\": {\n"
+ " \"currency\": \"INR\",\n"
+ " \"value\": \"10.00\"\n"
+ " },\n"
+ " \"refundRequestedTime\": 1513788119505,\n"
+ "}";
System.out.println("JSON: " + json2);
JsonParser p = new JsonParser();
Map<String,String> res =check("refundState", p.parse(json2));
System.out.println("JSON: " + res.get("refundState"));
}
private static Map<String,String> check(String key, JsonElement jsonElement) {
Map<String,String> res = new HashMap<>();
if (jsonElement.isJsonObject()) {
Set<Map.Entry<String, JsonElement>> entrySet = jsonElement.getAsJsonObject().entrySet();
entrySet.stream().forEach((x) ->{
if (x.getKey().equals(key)) {
res.put(x.getKey(),x.getValue().toString());
}
});
}
return res;
}
If you are interested to access any field in a JSON object structure then you can use a method like the following one that I used to access the fields that I needed from an JSON Array structure using "package org.json;"
public static final String COLLECTION_OBJECT = "collectionObject";
public static final String FIELD = "field";
private ArrayList<Object> getSearchFilterCriteriaAsString() {
String jsonString = "{" +
"\n\"collectionObject\": " +
"[\n" +
"{" +
"\n\"field\": \"productId\"," +
"\n\"value\": \"3\"," +
"\n\"operator\": \"EQUALS\"\n" +
"},\n" +
"{" +
"\n\"field\": \"productPrice\"," +
"\n\"value\": \"15\"," +
"\n\"operator\": \"MORE_THAN\"\n" +
"},\n" +
"{" +
"\n\"field\": \"productQuantity\"," +
"\n\"value\": \"25\"," +
"\n\"operator\": \"LESS_THAN\"\n" +
"}\n" +
"]\n" +
"}";
JSONObject jsonObject = new JSONObject(jsonString);
JSONArray jsonArray = jsonObject.getJSONArray(COLLECTION_OBJECT);
ArrayList<Object> filteredObjectsList = new ArrayList<>();
if (jsonArray != null) {
for (int i = 0; i < jsonArray.length(); i++) {
JSONObject filteredObj = (JSONObject) jsonArray.get(i);
filteredObjectsList.add(filteredObj.getString(FIELD));
}
}
return filteredObjectsList;
}
As long as you know your key values you can parse any JSON as deep you want, without to care about how big it is, how many attributes it has.
I have JSON Object which has a dynamic key inside the object I need a
specific key value
The recursive method listed below is capable of fetching the value mapped to the provided key from a nested JSON-object.
The method return an optional result.
If provided JsonElement is not a JsonObject an empty optional will be returned.
Otherwise, if the given JSON-object contains the given key the corresponding value wrapped by an optional will be returned. Or if it's not the case the entry set obtained from an object will be processed with stream. And for every JSON-object in the stream method getValue() will be called recursively.
If the given key is present in one of the nested objects, the first encountered non-empty optional will be returned. Or empty optional if the key was not found.
private static Optional<JsonElement> getValue(String key, JsonElement jsonElement) {
if (!jsonElement.isJsonObject()) {
return Optional.empty();
}
JsonObject source = jsonElement.getAsJsonObject();
return source.has(key) ? Optional.of(source.get(key)) :
source.entrySet().stream()
.map(Map.Entry::getValue)
.filter(JsonElement::isJsonObject)
.map(element -> getValue(key, element))
.filter(Optional::isPresent)
.findFirst()
.orElse(Optional.empty());
}
main() - demo
public static void main(String[] args) {
String json2 =
"""
{
"refundStatusDetails": {
"txn_f2a7802c-ef84-43c3-8615-5f706b995c23": {
"refundTransactionId": "txn_f2a7802c-ef84-43c3-8615-5f706b995c23",
"requestId": "refund-request-id-1",
"refundState": "REFUND_COMPLETED",
"amount": {
"currency": "INR",
"value": "10.00"
},
"refundRequestedTime": "1513788119505"
}
}
}""";
JsonElement element = JsonParser.parseString(json2);
System.out.println(getValue("refundState", element));
System.out.println(getValue("value", element));
}
Output
Optional["REFUND_COMPLETED"]
Optional["10.00"]
Note:
If you are using Java 17 you can utilize text blocks by plasing the text between the triple quotation marks """ JSON """.
Constructor of the JsonParser is deprecated. Instead of instantiating this class, we have to use its static methods.
I have an ObjectNode, that looks as follows
{
"Header":{
"sub-header1":{
"#field":"value",
"#field":"value",
},
"sub-header2":{
"field":"",
"field":"",
"field":"",
"panel_field":{
"value":"",
"value":""
}
}
}
Now, what I want to do is to get all the fields from sub-headers in a list. This is the method that I'm employing
public static List<String> getDocumentFields(ObjectNode jsonDocumentNode) {
List<String> documentFields = new ArrayList<>();
Iterator<JsonNode> fields = jsonDocumentNode.elements();
while (fields.hasNext()) {
JsonNode jsonNode = fields.next();
Iterator<Map.Entry<String, JsonNode>> jsonFields = jsonNode.fields();
while (jsonFields.hasNext()) {
Map.Entry<String, JsonNode> jsonNodeEntry = jsonFields.next();
documentFields.add(jsonNodeEntry.getKey());
}
}
return documentFields;
}
But, I'm only getting headers in the list like {sub-header1, sub-header2}, instead of fields. How can I fix this? I'd really appreciate any kind of help.
EDIT:
While #sfiss's answer helped a great deal, I still wanted to find a way to do so without hardcoding the loop-logic and this answer turned out to be the exact thing I was looking for.
Well, it's simple, you are not iterating deep enough (field list is on third level). If you know the structure of your JSON, just iterate until you find the desired fields:
public class MyTest {
#Test
public void testJson() throws IOException {
final String json = getJson();
final JsonNode jsonDocumentNode = new ObjectMapper().readTree(json);
final List<String> fields = getDocumentFields((ObjectNode) jsonDocumentNode);
assertThat(fields, Matchers.contains("#field1", "#field2", "field1", "field2", "field3", "panel_field"));
}
public static String getJson() {
return "{\r\n" +
" \"Header\":{\r\n" +
" \"sub-header1\":{\r\n" +
" \"#field1\":\"value\",\r\n" +
" \"#field2\":\"value\"\r\n" +
" },\r\n" +
" \"sub-header2\":{\r\n" +
" \"field1\":\"\",\r\n" +
" \"field2\":\"\",\r\n" +
" \"field3\":\"\",\r\n" +
" \"panel_field\":{\r\n" +
" \"value1\":\"\",\r\n" +
" \"value2\":\"\"\r\n" +
" }\r\n" +
" } \r\n" +
" }\r\n" +
"}";
}
public static List<String> getDocumentFields(final ObjectNode jsonDocumentNode) {
final List<String> documentFields = new ArrayList<>();
for (final JsonNode header : (Iterable<JsonNode>) jsonDocumentNode::elements) {
for (final JsonNode subheader : (Iterable<JsonNode>) header::elements) {
for (final Map.Entry<String, JsonNode> field : (Iterable<Map.Entry<String, JsonNode>>) subheader::fields) {
documentFields.add(field.getKey());
}
}
}
return documentFields;
}
}
However, I would argue that it is simpler to let jackson serialize the JSON into a convenient data structure, and you just use the POJO's getters to obtain your values. That would also make it more clear than handling the JsonNode.
Just FYI, I edited your JSON slightly and used Java 8 SAM conversions to create iterables for the foreach-loops from the iterators, but you can still use your code and iterate one more level using while and iterators.
I am currently learning a bit about streams.
I have the following JSONArray, and I want to be able to retrieve all the distinct xvalues.
datasets: {
ds1: {
xvalues: [
"(empty)",
"x1",
"x2"
]
},
ds2: {
xvalues: [
"(empty)",
"x1",
"x2",
"x3"
]
}
}
I am trying the following code but it doesn't seem quite right....
List<String> xvalues = arrayToStream(datasets)
.map(JSONObject.class::cast)
.map(dataset -> {
try {
return dataset.getJSONArray("xvalues");
} catch (JSONException ex) {
}
return;
})
.distinct()
.collect((Collectors.toList()));
private static Stream<Object> arrayToStream(JSONArray array) {
return StreamSupport.stream(array.spliterator(), false);
}
What you get with .map(dataset -> dataset.getJSONArray("xvalues") (try-catch block omitted for sake of clarity) is a list itself and the subsequent call of distinct is used on the Stream<List<Object>> checking whether the lists itself with all its content is same to another and leave distinct lists.
You need to flat map the structure to Stream<Object> and then use the distinct to get the unique items. However, first, you need to convert JSONArray to List.
List<String> xvalues = arrayToStream(datasets)
.map(JSONObject.class::cast)
.map(dataset -> dataset -> {
try { return dataset.getJSONArray("xvalues"); }
catch (JSONException ex) {}
return;})
.map(jsonArray -> toJsonArray(jsonArray )) // convert it to List
.flatMap(List::stream) // to Stream<Object>
.distinct()
.collect(Collectors.toList());
I believe using a json library(Jackson, Gson) is the best way to deal with json data. If you can use Jackson, this could be a solution
public class DataSetsWrapper {
private Map<String, XValue> datasets;
//Getters, Setters
}
public class XValue {
private List<String> xvalues;
//Getters, Setters
}
ObjectMapper objectMapper = new ObjectMapper();
DataSetsWrapper dataSetsWrapper = objectMapper.readValue(jsonString, DataSetsWrapper.class);
List<String> distinctXValues = dataSetsWrapper.getDatasets()
.values()
.stream()
.map(XValue::getXvalues)
.flatMap(Collection::stream)
.distinct()
.collect(Collectors.toList());
Replace jsonString with your json. I tested this with this json
String jsonString = "{\"datasets\": {\n" +
" \"ds1\": {\n" +
" \"xvalues\": [\n" +
" \"(empty)\",\n" +
" \"x1\",\n" +
" \"x2\"\n" +
" ]\n" +
" },\n" +
" \"ds2\": {\n" +
" \"xvalues\": [\n" +
" \"(empty)\",\n" +
" \"x1\",\n" +
" \"x2\",\n" +
" \"x3\"\n" +
" ]\n" +
" }\n" +
"}}";
I have a hashmap which has key value pair of String and object. It is the conversion of something like below json.
{
"test1": {
"test2": {
"test3": {
"key": "value"
},
"somefields12": "some value2"
},
"somefields": "some value"
}
}
But, I am not converting to map. I have just that map. If this may has key and value , I have to do write some logic based on that value. I implemented as below:
if (map.containsKey("test1") ) {
final HashMap<String, Object> test1 = (HashMap<String, Object>) map.get("test1");
if (test1.containsKey("test2")) {
final List<Object> test2 = (List<Object>) test1.get("test2");
if (!test2.isEmpty()) {
final HashMap<String, Object> test3 = (HashMap<String, Object>) test2.get(0);
if (test3.containsKey("key")) {
final String value = String.valueOf(test2.get("key"));
if (!StringUtils.isBlank(value)) {
//do some work based on value
}
}
}
}
}
Now, I wanted to avoid the nested if (multiple ifs) from my code. What would be the best way to do so?
I'm not familiar with the fancy new Java 8 features, so I'd do it the old fashioned way with a function that takes a path to look up, and walks the list with a loop:
import java.util.*;
class Test {
static String getByPath(HashMap<String, Object> map, String... path) {
for(int i=0; i<path.length-1; i++) {
map = (HashMap<String, Object>) map.get(path[i]);
if (map == null) return null;
}
Object value = map.get(path[path.length-1]);
return value == null ? null : String.valueOf(value);
}
public static void main(String[] args) {
HashMap<String, Object> map = new HashMap<>();
HashMap<String, Object> tmp1 = new HashMap<>();
HashMap<String, Object> tmp2 = new HashMap<>();
map.put("test1", tmp1);
tmp1.put("test2", tmp2);
tmp2.put("key1", "My Value");
System.out.println("With valid path: " + getByPath(map, "test1", "test2", "key1"));
System.out.println("With invalid path: " + getByPath(map, "test1", "BANANA", "key1"));
}
}
This results in:
With valid path: My Value
With invalid path: null
This can optionally be extended to:
Check that nodes are in fact maps before casting
Use Optional or a helpful exception instead of returning null
Using Gson library combined with java 8 Optional you can do something like this:
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import java.util.Optional;
public class FindJsonKey {
public static final String JSON = "{\n" +
" \"test1\": {\n" +
" \"test2\": {\n" +
" \"test3\": {\n" +
" \"key\": \"value\"\n" +
" },\n" +
" \"somefields12\": \"some value2\"\n" +
" },\n" +
" \"somefields\": \"some value\"\n" +
" }\n" +
"}";
public static void main(String[] args) {
Gson gson = new GsonBuilder().create();
JsonObject jsonObject = gson.fromJson(JSON, JsonObject.class);
Optional
.ofNullable(jsonObject.getAsJsonObject("test1"))
.map(test1 -> test1.getAsJsonObject("test2"))
.map(test2 -> test2.getAsJsonObject("test3"))
.map(test3 -> test3.get("key"))
.map(JsonElement::getAsString)
.ifPresent(key -> {
// Do something with key..
});
}
}
A recursive solution that assumes the JSON structure is as given -- and ignores the names of the nested parent maps -- could look like the following:
/**
* Traverses the map from {#code nextEntry} until a key matching {#code keyTofind} is found
* or the bottom of the map is reached.
* The method assumes that any nested maps are contained in the first
* element of the parent map.
*
* #param nextEntry the next entry to search from
* #param keyToFind the stopping condition for the recursion
* #return the value matching {#code keyToFind} (may be empty)
*/
private static Optional<Object> find(Map.Entry<String,Object> nextEntry, String keyToFind) {
Object value = nextEntry.getValue();
if (nextEntry.getKey().equals(keyToFind)) {
return Optional.of(value);
} else if (nextEntry.getValue() instanceof Map) {
return find(((Map<String, Object>)nextEntry.getValue())
.entrySet().iterator().next(), keyToFind);
} else {
return Optional.empty();
}
}
Testing with the provided data (with help of Jackson for JSON parsing)
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
public class Test {
private static final String JSON = "{ "
+ "\"test1\": {"
+ " \"test2\": {"
+ " \"test3\": {"
+ " \"key\": \"value\""
+ "},"
+ " \"somefields12\": \"some value2\""
+ "},"
+ " \"somefields\": \"some value\""
+ "}"
+ "}";
//...
public static void main(String[] args) throws Exception {
Map<String, Object> json = new ObjectMapper()
.readValue(JSON, new TypeReference<Map<String, Object>>() { });
final String keyToFind = "key";
Optional<Object> value = find(json.entrySet().iterator().next(), keyToFind);
System.out.println(value.isPresent() ? value.get() : "null");
}
}
gives
value
I'm using Jackson 2.8.9 in my application to generate some JSON. I have some unit tests in which I compare generated JSON with some files content.
When I compare my generated JSON with my file content, it does not match due to the properties order.
For the tests to be repeatable I need to have the properties sorted alphabetically. But with Jackson, it does not seems to work.
I wrote some tests for illustation. Only should_indent_properties pass.
public class FormatJsonWithJacksonTest {
private static final String INDENTED_UNSORTED = "{\r\n" +
" \"firstChild\" : {\r\n" +
" \"subChild\" : {\r\n" +
" \"alphaItem\" : \"1234567891234567\",\r\n" +
" \"otherProperty\" : \"2017-06-21\",\r\n" +
" \"someOtherProperty\" : \"NONE\",\r\n" +
" \"alphaType\" : \"KIND_OF_TYPE\"\r\n" +
" }\r\n" +
" }\r\n" +
"}";
private static final String INDENTED_SORTED = "{\r\n" +
" \"firstChild\" : {\r\n" +
" \"subChild\" : {\r\n" +
" \"alphaItem\" : \"1234567891234567\",\r\n" +
" \"alphaType\" : \"KIND_OF_TYPE\",\r\n" +
" \"otherProperty\" : \"2017-06-21\",\r\n" +
" \"someOtherProperty\" : \"NONE\"\r\n" +
" }\r\n" +
" }\r\n" +
"}";
private static final String UNINDENTED_UNSORTED = "{" +
"\"firstChild\":{" +
"\"subChild\":{" +
"\"alphaItem\":\"1234567891234567\"," +
"\"otherProperty\":\"2017-06-21\"," +
"\"someOtherProperty\":\"NONE\"," +
"\"alphaType\":\"KIND_OF_TYPE\"" +
"}" +
"}" +
"}";
private static final String UNINDENTED_SORTED = "{" +
"\"firstChild\":{" +
"\"subChild\":{" +
"\"alphaItem\":\"1234567891234567\"," +
"\"alphaType\":\"KIND_OF_TYPE\"," +
"\"otherProperty\":\"2017-06-21\"," +
"\"someOtherProperty\":\"NONE\"" +
"}" +
"}" +
"}";
#Test
public void should_sort_properties() throws Exception {
// Given
ObjectMapper objectMapper = new ObjectMapper();
objectMapper = objectMapper
.configure(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY, true);
// When
String formattedJson = tryingToFormatJson(objectMapper, INDENTED_UNSORTED);
// Then
assertEquals(UNINDENTED_SORTED, formattedJson);
}
#Test
public void should_indent_properties() throws Exception {
// Given
ObjectMapper objectMapper = new ObjectMapper();
objectMapper = objectMapper.enable(SerializationFeature.INDENT_OUTPUT);
// When
String formattedJson = tryingToFormatJson(objectMapper, UNINDENTED_UNSORTED);
// Then
assertEquals(INDENTED_UNSORTED, formattedJson);
}
#Test
public void should_sort_and_indent_properties() throws Exception {
// Given
ObjectMapper objectMapper = new ObjectMapper();
objectMapper = objectMapper
.configure(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY, true)
.enable(SerializationFeature.INDENT_OUTPUT);
// When
String formattedJson = tryingToFormatJson(objectMapper, INDENTED_UNSORTED);
// Then
assertEquals(INDENTED_SORTED, formattedJson);
}
//
// Utils
//
private String tryingToFormatJson(ObjectMapper objectMapper, String unformattedJson)
throws IOException, JsonProcessingException {
JsonNode unsortedTree = objectMapper.readTree(unformattedJson);
Object treeToValue = objectMapper.treeToValue(unsortedTree, Object.class);
return objectMapper.writeValueAsString(treeToValue);
}
}
How could I sort my JSON with Jackson?
Do you have any solution for implementing my method tryingToFormatJson?
Is Jackson the right tool I want for doing this?
When you call objectMapper.treeToValue(unsortedTree, Object.class), the Object's type is a subclass of Map - just put a System.out.println(treeToValue.getClass()) to check.
And according to javadoc, SORT_PROPERTIES_ALPHABETICALLY doesn't sort map keys:
Feature that defines default property serialization order used for POJO fields (note: does not apply to Map serialization!)
If you want to sort the fields, you have to create a custom class for you structure, like this:
public class Tree {
private Map<String, Child> firstChild;
// getters and setters
}
public class Child {
private String alphaItem;
private String otherProperty;
private String someOtherProperty;
private String alphaType;
// getters and setters
}
And change the treeToValue call to:
Tree treeToValue = objectMapper.treeToValue(unsortedTree, Tree.class);
With this, the fields will be sorted and the test will work.