Home > front end >  Parse JSON array into Java classes using Jackson
Parse JSON array into Java classes using Jackson

Time:08-13

I have a rather complex JSON file which I need to parse into multiple Java classes.

The structure of JSON is as follows:


{
  "dataBlock": [
    {
      "headingName": {
        "name": "Operational Information",
        "position": "1",
        "attributes": [
          {
            "name": "Brand",
            "position": "1",
            "value": [
              "A",
              "B"
            ]
          },
          {
            "name": "Data Model Id",
            "position": "2",
            "value": "000001"
          }
        ]
      }
    },
    {
      "headingName": {
        "name": "CRA",
        "position": "6",
        "attributes": [
          {
            "name": "Company",
            "position": "1",
            "value": "private_limited_company"
          },
          {
            "name": "Address",
            "position": "3",
            "value": {
              "line1": "AAA",
              "line2": "BBB",
              "line3": "CCC",
              "line4": "DDD",
              "postalCode": "AB XYZ",
              "countryCode": "GBR"
            }
          }
        ]
      }
    }
  ]
}

I wish to store each "headingName" node into a separate Java Class (the structure may vary in future for individual categories/headingName within dataBlock Array such as my response is:

@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@JsonIgnoreProperties(ignoreUnknown = true)
public class MyResponse {
    private OperationalInformation operationalInformation;
    private CRA cra;
    ...
}

How do I achieve that?

CodePudding user response:

My Solution involves two stages:

deserialization

deserialize the json into objects in the same hierarchy and structure of the input json that contain String and object properties. This set of classes can support any and all heading names and attributes

@Data
public abstract class JsonCommonProperties {
    protected String name;
    protected String position;
}

@Data
@ToString(callSuper = true)
public class JsonAttribute<T> extends JsonCommonProperties {
    private T value;
}

@Data
@ToString(callSuper = true)
public class JsonHeading extends JsonCommonProperties {
    private List<JsonAttribute<?>> attributes;
}

public class JsonResponse {
    
    public List<HeadingName> dataBlock;
    
    public static class HeadingName {
        public JsonHeading headingName;
    }
}

this set of classes can be used for straightforward deserialization using ObjectMapper.readValue().

build MyResponse

convert the above object hierarchy to MyResponse, using reflection and 3rd party libraries to build objects and populate instance variables from String names.

I used one common super class for all Headings. the set of classes is specified below

@Getter
@Setter
@ToString
public abstract class Heading {
    private String name;

    public Heading(String name) {
        setName(name);
    }
}

@Getter
@Setter
@ToString(callSuper = true)
class CRA extends Heading {
    private String company;
    private Map<String, String> address;

    public CRA (String name) {
        super(name);
    }
}

@Getter
@Setter
@ToString(callSuper = true)
class OperationalInformation extends Heading {
    private List<String> brand;
    private String dataModelId;

    public OperationalInformation (String name) {
        super(name);
    }
}

@Data
public class MyResponse {
    private OperationalInformation operationalInformation;
    private CRA cra;
}

the challenges and solutions were

  1. convert heading name to type of heading: I used apache common StringUtils and apache text CaseUtils to convert json property to class name, and then used reflection to instantiate an instance

  2. populate instance vairables of headings from JsonAttributes: I used Jodd Util library (better alternative to apache BeanUtils) to dynamically call setters from string names.

I put the above solutions in one heading factory class

public class HeadingFactory {

    /**
     * instantiate concrete Heading from specified JsonHeading
     */
    @SuppressWarnings("unchecked")
    public static Heading getHeading(JsonHeading jsonHeading) throws ReflectiveOperationException {
        Class<Heading> concreteHeadingClass =
            (Class<Heading>)Class.forName(getMyResponsePackage()   "."   getHeadingType(jsonHeading));
        Heading heading = concreteHeadingClass.getConstructor(String.class).newInstance(jsonHeading.getName());
        populateHeading(heading, jsonHeading);
        return heading;
    }

    /**
     * return class name of concrete Heading (type of instance var in MyResponse)
     * from specified JsonHeading
     */
    public static String getHeadingType(JsonHeading jsonHeading) {
        // handle all uppercase heading, like CRA
        if (StringUtils.isAllUpperCase(jsonHeading.getName())) return jsonHeading.getName();
        // handle separate word heading, like Operational Information
        return CaseUtils.toCamelCase(jsonHeading.getName(), true, ' ');
    }

    private static String getMyResponsePackage() {
        return StringUtils.substringBeforeLast(MyResponse.class.getName(), ".");
    }

    // use jodd util to dynamically call setter methods of heading
    // and pass values of jsonAttributes of jsonHeading
    private static void populateHeading(Heading heading, JsonHeading jsonHeading) {
        jsonHeading.getAttributes().forEach(jsonAttribute ->
            BeanUtil.pojo.setProperty(heading, jsonAttribute.getPropertyName(), jsonAttribute.getValue())
        );
    }
}

populate instance variables of MyResponse with same technique and library. here's my main method

public static void main(String[] args) {
    ObjectMapper mapper = new ObjectMapper();
    try (BufferedReader br = Files.newBufferedReader(Paths.get("C://temp/json.json"))) {
        JsonResponse jsonResponse = mapper.readValue(br, JsonResponse.class);
        MyResponse myResponse = new MyResponse();
        for (JsonResponse.HeadingName headingName : jsonResponse.dataBlock) {
            Heading heading = HeadingFactory.getHeading(headingName.headingName);
            String myResponseProperty = headingName.headingName.getPropertyName();
            BeanUtil.pojo.setProperty(myResponse, myResponseProperty, heading);
        }
        System.out.println(myResponse);

    } catch (Exception e) {
        e.printStackTrace();
    }
}

CodePudding user response:

Use JSON query language to transform the JSON structure before deserialize into POJO. A single Josson query statement can do the job.

dataBlock.map(headingName.name.camelCase()::headingName.field(name:)).mergeObjects()

https://github.com/octomix/josson

Demo

Josson josson = Josson.fromJsonString(
    "{"  
    "  \"dataBlock\": [ {"  
    "    \"headingName\": {"  
    "      \"name\": \"Operational Information\","  
    "      \"position\": \"1\","  
    "      \"attributes\": [ {"  
    "          \"name\": \"Brand\","  
    "          \"position\": \"1\","  
    "          \"value\": [\"A\",\"B\"]"  
    "      },"  
    "      {"  
    "        \"name\": \"Data Model Id\","  
    "        \"position\": \"2\","  
    "        \"value\": \"000001\""  
    "      } ]"  
    "    }"  
    "  },"  
    "  {"  
    "    \"headingName\": {"  
    "      \"name\": \"CRA\","  
    "      \"position\": \"6\","  
    "      \"attributes\": [ {"  
    "        \"name\": \"Company\","  
    "        \"position\": \"1\","  
    "        \"value\": \"private_limited_company\""  
    "      },"  
    "      {"  
    "        \"name\": \"Address\","  
    "        \"position\": \"3\","  
    "        \"value\": {"  
    "          \"line1\": \"AAA\","  
    "          \"line2\": \"BBB\","  
    "          \"line3\": \"CCC\","  
    "          \"line4\": \"DDD\","  
    "          \"postalCode\": \"AB XYZ\","  
    "          \"countryCode\": \"GBR\""  
    "        }"  
    "      } ]"  
    "    }"  
    "  } ]"  
    "}");
JsonNode node = josson.getNode(
    "dataBlock.map(headingName.name.camelCase()::headingName.field(name:)).mergeObjects()");
System.out.println(node.toPrettyString());

Output

{
  "operationalInformation" : {
    "position" : "1",
    "attributes" : [ {
      "name" : "Brand",
      "position" : "1",
      "value" : [ "A", "B" ]
    }, {
      "name" : "Data Model Id",
      "position" : "2",
      "value" : "000001"
    } ]
  },
  "cra" : {
    "position" : "6",
    "attributes" : [ {
      "name" : "Company",
      "position" : "1",
      "value" : "private_limited_company"
    }, {
      "name" : "Address",
      "position" : "3",
      "value" : {
        "line1" : "AAA",
        "line2" : "BBB",
        "line3" : "CCC",
        "line4" : "DDD",
        "postalCode" : "AB XYZ",
        "countryCode" : "GBR"
      }
    } ]
  }
}
  • Related