0

I'm trying to parse multiple invoices converted to nested JSON that is coming from the rest service. Here is how it looks like:

{
    "files": [
        {
            "bill_from": "TestCorp",
            "usa._ny_02123._washington_": "New_York._NY._02",
            "united_states": "yao123@mail.",
            "bill_to": "Yao_Min",
            "la._2123._los_ange": "new_York._NY._9803",
            "invoice_#": "01",
            "invoice_date": "2022-08-05",
            "amount_due": "36.79",
            "subtotal": "36.74",
            "tax_(0.13%)": "0.05",
            "total": "36.79",
            "items": [
                {
                    "item": "Pen",
                    "description": "Pen. black co",
                    "quantity": "1.5",
                    "unit_cost": "1.16",
                    "line_total": "1.74"
                },
                {
                    "item": "Book",
                    "description": "Calculus. Zorych. pt",
                    "quantity": "3.5",
                    "unit_cost": "10.00",
                    "line_total": "35.00"
                }
            ]
        },
        {
            "bill_from": "BioTech_Inc.",
            "usa._ny_02123._washington_": "New_York._NY._02",
            "united_states": "jmaky@gmail.",
            "bill_to": "Jack_Ma",
            "la._2123._los_ange": "Los_Angeles._CA._9803",
            "invoice_#": "01",
            "invoice_date": "2022-09-23",
            "amount_due": "5.831",
            "subtotal": "5.160",
            "tax_(13.0%)": "670.92",
            "total": "5.831",
            "items": [
                {
                    "item": "Laser",
                    "description": "Laser. 12mw. 65",
                    "quantity": "5.5",
                    "unit_cost": "460.28",
                    "line_total": "2.531"
                },
                {
                    "item": "Beam Splitter",
                    "description": "BS RE 4553. phi=45 d",
                    "quantity": "4.5",
                    "unit_cost": "350.99",
                    "line_total": "1.579"
                },
                {
                    "item": "Optical Fiber",
                    "description": "Optical FIber (x10)",
                    "quantity": "3.5",
                    "unit_cost": "299.97",
                    "line_total": "1.049"
                }
            ]
        },
        {
            "sub_total": "250.00",
            "total": "250.00",
            "balance_due": "250.00",
            "invoice_date": "2021-04-30",
            "terms": "Net_15",
            "due_date": "15 May 2021",
            "noltic_llc": "Company_Registration_No._41305",
            "68._henerala_\u0441huprynky_str": "Lviv_79044_Ukraine",
            "invoice": "Invoice#_INV-000399",
            "bill_to": "Millhouse Logistics Inc",
            "items": [
                {
                    "item_and_description": "Salesforce software development services",
                    "qty": "5.00",
                    "rate": "50.00",
                    "amount": "250.00"
                }
            ]
        }
    ]
}

In my apex I want to get fields of each invoice and it's items to generate opportunities based on them. For that I'm using following wrapper class

public class Invoice {
    public Invoice() {}

    public class Items {
        public String item {get;set;}
        public String description {get;set;}
        public String quantity {get;set;}
        public String unit_cost {get;set;}
        public String line_total {get;set;}
        public Items() {}
    }

    public class Files {
        public String bill_from { get; set; }
        public String bill_to { get; set; }
        public String invoice_number { get; set; }
        public String invoice_date { get; set; }
        public String amount_due { get; set; }
        public String subtotal { get; set; }
        public String total { get; set; }
        public List<Map<String, String>> items { get; set; }
        public String tax { get; set; } 
        public String sub_total { get; set; }
        public String balance_due { get; set; }
        public String due_date { get; set; }
        public Files() {}
    }
}

And in my InvoiceParser class I'm trying to get acces to items and genral info:

public with sharing class InvoiceParser {
    public static HttpResponse response = InvoiceCallout.makeGetCallout();
    public static JSONParser parser = JSON.createParser(response.getBody());
    public static Invoice.Items items = (Invoice.Items)parser.readValueAs(Invoice.Items.class);
    public static Invoice.Files files = (Invoice.Files)parser.readValueAs(Invoice.Files.class);
    
    @AuraEnabled
    public static void sendInvoiceData() {
        System.debug('Response ==============' + response.getBody());
        System.debug('Items =================' + items);
        System.debug('Files.Items ===========' + files.items);
        System.debug('Bill From =============' + files.bill_from);
    }
}

But I can't get any info at all and instead I'm getting the following error:

System.JSONException: No content to map to Object due to end of input

Why does this error occure? Is my wrapper class written bad or something? Thank you in advance.

2
  • @identigral not much because I was doing what it says and it didn't work for me.
    – user120280
    Nov 22, 2022 at 16:50
  • You can use this tool online, it generated you the whole wrapper class with your JSON : adminbooster.com/tool/json2apex
    – Amadou
    Nov 25, 2022 at 14:56

2 Answers 2

3

I don't think you really need to go through a parser, that's gonna complicate your life. you can directly go to a JSON.deserialize

What i usually do in your situation is the following :

  1. In your File Apex class, modify the map to have a list of item
public class Files {
        ...
        public List<Invoice.Items> items { get; set; }
        ...
    }
  1. In your Main class constructor, have your top level of your JSON
public class Invoice {
    public List<Files> file {get;set;}
...
}

With these two, you editions, you can now simply in your method, call it this way :

public with sharing class InvoiceParser {
    
    @AuraEnabled
    public static void sendInvoiceData() {
        HttpResponse response = InvoiceCallout.makeGetCallout();
        System.debug('Response ==============' + response.getBody());
        Invoice parsedJson = JSON.deserialize(response.getBody(), Invoice.Class);
        for (Invoice.File aFile: parsedJson.files){
            System.debug('File =================' + aFile);
            System.debug('Bill From =============' + afile.bill_from);
            System.debug('Files.Items ===========' + afile.items);
        }
    }
}

Hope this helps

5
  • 2
    Yes, upvoted, JSONParser (and also JSONGenerator) should only be used as a last resort. About the only other note I can make here is that you shouldn't need to qualify the Items class in the Files inner class (since they are all part of the same top-level /outer class). We only need to use Invoice.Files and Invoice.Items in other classes such as in InvoiceParser.
    – Derek F
    Nov 22, 2022 at 14:39
  • Well I tried that and now it says System.NullPointerException: Attempt to de-reference a null obj, but now I am able to see at least response in System.debug()
    – user120280
    Nov 22, 2022 at 16:39
  • it crushes on the line where the for loop starts smh. Seens like it doesn't populates that files list
    – user120280
    Nov 22, 2022 at 16:47
  • @Philippe OK thanks :) Now it works (I use parser.readValueAs anyway cuz I need some fields to be ignored in some cases, and now I'm getting my structure with list of items. The problem is that it's a List, but I need a list of maps e.g. [{Item=Pen, unit_cost=1,....}]. Is there any way to parse that items object to apex Map?
    – user120280
    Nov 23, 2022 at 11:25
  • Ohh, I finally figured that out! I just created JSONParser instance in my Files class and then did public List<Map<String, String>> items = (List<Map<String, String>>)parser.readValueAs(Items.class); . But rest of the code from your example works fine as well. Thank you! :)
    – user120280
    Nov 23, 2022 at 16:06
0

public List<Map<String, String>> items { get; set; }
public List items { get; set; }

You cant parse directly to the items object this way. You need to deserialize it in this form and then change its format. Or if you need to parse it using parser then you need to drill down to the object level and parse it into the desired format.