Search code examples
javaapache-camelmarshalling

How to marshal JSON with repeating groups in a fixed length data format in java using camel?


I need to process a JSON - it has repeating groups I need to convert it to fixed length record

{
    "blockType" : "BL-H ",
    "blockTypeLength" : "00000031",
    "blockId" : "S62951156229900",
    "totalNoOfTX" : "001",
    "msgblockType" : "TX-S ",
    "messageLength" : "00000728",
    "noa":[
        {
            "title":"Behin",
            "artist":"LIMP ",
            "itunes_link":"http:behind"
        },
        {
            "title":"Alone",
            "artist":"ALYSS",
            "itunes_link":"http:clk.doubler.com"
        }
    ]

}

Should be converted to BehinLIMP http:behindAloneALYSShttp:clk.doubler.com

I can have one to many such groups in the request

What I have tried -

DataFormat bindy = new BindyFixedLengthDataFormat(myModel.class);

                from("direct:testUnmarshall")
            .log("${body}")
            //.unmarshal().json(JsonLibrary.Jackson, BillingBookingRequest[].class)
            .inputType(BillingBookingRequest.class)
            .process(new Processor() {
                @Override
                public void process(Exchange exchange) throws Exception {

                    try {
                        BillingBookingRequest responseBody = exchange.getMessage().getBody(BillingBookingRequest.class);

                        ZvkkRequest requestBody = new ZvkkRequest();
                        requestBody.setBlockType(responseBody.getBlockType());
                        requestBody.setBlockTypeLength(responseBody.getBlockTypeLength());
                        requestBody.setBlockId(responseBody.getBlockId());
                        requestBody.setTotalNoOfTX(responseBody.getTotalNoOfTX());
                        requestBody.setMsgblockType(responseBody.getMsgblockType());
                        requestBody.setMessageLength(responseBody.getMessageLength());

                        List<DAO> noaList = responseBody.getNoa();
                        List<DAOFix> repGrp = new ArrayList<>();

                        for (DAO noa:
                             noaList) {
                            DAOFix obj = new DAOFix();
                            obj.setArtist(noa.getArtist());obj.setTitle(noa.getTitle());obj.setItunes_link(noa.getItunes_link());
                            repGrp.add(obj);
                        }

                        requestBody.setRepeatingGrp(repGrp);
                        exchange.getOut().setBody(requestBody);
                    } catch (Exception exception){
                        System.out.println("EXCEPTION HERE :: "+exception.getMessage());
                        exception.printStackTrace();
                    }

                }
            })
            .log("Before marshal ....... ${body}")
            .marshal(bindy)
            .log("After marshal ....... ${body}")
            .to("{{file.path}}fileName=check.dat")
            .end();
java.lang.ClassCastException: java.lang.Class cannot be cast to java.lang.reflect.ParameterizedType
also observed - org.apache.camel.NoTypeConversionAvailableException: No type converter available to convert from type: myFixedLengthRequestModel to the required type: java.io.InputStream with value myFixedLengthRequestModel[with all the values]

I have used the corresponding models holding the values for json as well as @FixedLengthRecord need help to understand and get this resolved.

public class ZvkkRequest {
@DataField(pos = 1, length=5, align = "L", paddingChar=' ')
private String blockType;
@DataField(pos = 2, length=8, align = "R", paddingChar='0')
private int blockTypeLength;
@DataField(pos = 3, length=15, align = "L", paddingChar=' ')
private String blockId;
@DataField(pos = 4, length=3, align = "R", paddingChar='0')
private int totalNoOfTX;
@DataField(pos = 5, length=5, align = "L", paddingChar=' ')
private String msgblockType;
@DataField(pos = 6, length=8, align = "R", paddingChar='0')
private int messageLength;

@OneToMany(mappedTo = "classpath.DAOFix")
private List<DAOFix> repeatingGrp;
}

class DAOFix {

@DataField(pos = 7, length=5, align = "R", paddingChar=' ')
private String title;
@DataField(pos = 8, length=5, align = "R", paddingChar=' ')
private String artist;
@DataField(pos = 9, length=5, align = "R", paddingChar=' ')
private String itunes_link;

}


Solution

  • Tested this out and it seems @OneToMany does not seem to work with BindyFixedLengthDataFormat like it does with BindyCsvDataFormat this means that you'll likely process the json and create multiple object instances which you can then marshal in to fixed length record with Bindy.

    Below is code I used to test this out as well as one way (direct:bindyFixedLenghtProcessed) to convert single ZvkkRequest instance to list of ZvkkRequestFixedLenght instances which you can then marshal to BindyFixedLengthDataFormat

    public class ExampleTests extends CamelTestSupport {
        
        @Test
        public void bindyCSVTest(){
    
            // Test bindy @OneToMany with BindyCsvDataFormat 
            template.sendBody("direct:bindyCSV", testJson);
        }
    
        @Test
        public void bindyFixedLenghtTest(){
    
            // Test bindy @OneToMany with BindyFixedLengthDataFormat
            template.sendBody("direct:bindyFixedLenght", testJson);
        }
    
        @Test
        public void bindyFixedLenghtProcessedTest(){
    
            // Test workaraound 
            template.sendBody("direct:bindyFixedLenghtProcessed", testJson);
        }
    
        @Override
        protected RoutesBuilder createRouteBuilder() throws Exception {
            
            return new RouteBuilder(){
    
                @Override
                public void configure() throws Exception {
                    
                    // Doesn't seem to work with OneToMany
                    DataFormat bindyCSV = new BindyCsvDataFormat(ZvkkRequest.class);
    
                    from("direct:bindyCSV")
                        .unmarshal().json(JsonLibrary.Jackson, ZvkkRequest.class)
                        .marshal(bindyCSV)
                        .log("${body}");
    
                    DataFormat bindyFixedLenght = new BindyFixedLengthDataFormat(ZvkkRequest.class);
    
                    from("direct:bindyFixedLenght")
                        .unmarshal().json(JsonLibrary.Jackson, ZvkkRequest.class)
                        .marshal(bindyFixedLenght)
                        .log("${body}");
    
                    DataFormat bindyFixedLenght2 = new BindyFixedLengthDataFormat(ZvkkRequestFixedLenght.class);
    
                    from("direct:bindyFixedLenghtProcessed")
                        .unmarshal().json(JsonLibrary.Jackson, ZvkkRequest.class)
                        .process(new Processor(){
    
                            @Override
                            public void process(Exchange exchange) throws Exception {
                                
                                ZvkkRequest body = exchange.getMessage()
                                    .getBody(ZvkkRequest.class);
                                
                                List<ZvkkRequestFixedLenght> requests = new ArrayList<>();
    
                                for (DAOFix it : body.noa) {
                                    ZvkkRequestFixedLenght request = new ZvkkRequestFixedLenght();
                                    request.blockType = body.blockType;
                                    request.blockTypeLength = body.blockTypeLength;
                                    request.blockId = body.blockId;
                                    request.totalNoOfTX = body.totalNoOfTX;
                                    request.msgblockType = body.msgblockType;
                                    request.messageLength = body.messageLength;
                                    request.artist = it.artist;
                                    request.title = it.title;
                                    request.itunesLink = it.itunesLink;
                                    requests.add(request);
                                }
                                exchange.getMessage().setBody(requests);
                            }
                            
                        })
                        .marshal(bindyFixedLenght2)
                        .log("${body}");
    
                }
            };
        }
    
        String testJson = "{" +
        "    \"blockType\" : \"BL-H \"," +
        "    \"blockTypeLength\" : \"00000031\"," +
        "    \"blockId\" : \"S62951156229900\"," +
        "    \"totalNoOfTX\" : \"001\"," +
        "    \"msgblockType\" : \"TX-S \"," +
        "    \"messageLength\" : \"00000728\"," +
        "    \"noa\":[" +
        "        {" +
        "            \"title\":\"Behin\"," +
        "            \"artist\":\"LIMP \"," +
        "            \"itunes_link\":\"http:behind\"" +
        "        }," +
        "        {" +
        "            \"title\":\"Alone\"," +
        "            \"artist\":\"ALYSS\"," +
        "            \"itunes_link\":\"http:clk.doubler.com\"" +
        "        }" +
        "    ]" +
        "}";
    }
    

    bindyFixedLenghtProcessedTest logs following:

    BL-H 00000031S62951156229900001TX-S 00000728BehinLIMP                    http:behind
    BL-H 00000031S62951156229900001TX-S 00000728AloneALYSS          http:clk.doubler.com
    

    In case you want to have these on a single line you can try to marshal ZvkkRequest and DAOFix separately and use string concatenation to combine the unmarshalled DAOFix entries to unmarshalled ZvkkRequest.

    ZvkkRequest.java

    @FixedLengthRecord
    @CsvRecord(separator=",")
    public class ZvkkRequest {
        
        @DataField(pos = 1, length = 5, align = "L", paddingChar = ' ')
        public String blockType;
    
        @DataField(pos = 2, length = 8, align = "R", paddingChar = '0')
        public int blockTypeLength;
        
        @DataField(pos = 3, length = 15, align = "L", paddingChar = ' ')
        public String blockId;
        
        @DataField(pos = 4, length = 3, align = "R", paddingChar = '0')
        public int totalNoOfTX;
        
        @DataField(pos = 5, length = 5, align = "L", paddingChar = ' ')
        public String msgblockType;
        
        @DataField(pos = 6, length = 8, align = "R", paddingChar = '0')
        public int messageLength;
    
        @OneToMany
        public List<DAOFix> noa;
    }
    

    DAOFix.java

    public class DAOFix {
    
        @DataField(pos = 7, length=5, align = "R", paddingChar=' ')
        public String title;
        
        @DataField(pos = 8, length=5, align = "R", paddingChar=' ')
        public String artist;
    
        @JsonProperty("itunes_link")
        @DataField(pos = 9, length=30, align = "R", paddingChar=' ')
        public String itunesLink;
    }
    

    ZvkkRequestFixedLenght.java

    @CsvRecord(separator=",")
    @FixedLengthRecord
    public class ZvkkRequestFixedLenght {
        
        @DataField(pos = 1, length = 5, align = "L", paddingChar = ' ')
        public String blockType;
    
        @DataField(pos = 2, length = 8, align = "R", paddingChar = '0')
        public int blockTypeLength;
        
        @DataField(pos = 3, length = 15, align = "L", paddingChar = ' ')
        public String blockId;
        
        @DataField(pos = 4, length = 3, align = "R", paddingChar = '0')
        public int totalNoOfTX;
        
        @DataField(pos = 5, length = 5, align = "L", paddingChar = ' ')
        public String msgblockType;
        
        @DataField(pos = 6, length = 8, align = "R", paddingChar = '0')
        public int messageLength;
    
        @DataField(pos = 7, length=5, align = "R", paddingChar=' ')
        public String title;
        
        @DataField(pos = 8, length=5, align = "R", paddingChar=' ')
        public String artist;
    
        @DataField(pos = 9, length=30, align = "R", paddingChar=' ')
        public String itunesLink;
    }