Xenofono
Xenofono

Reputation: 219

Parse List<String> as JSON

I'm following the book Real-World Software Development and the current chapter was about writing a BankStatementCSVParser which reads a file like this:

30-01-2017,-100, Deliveroo
30-01-2017,-50, Tesco
01-02-2017,6000, Salary
02-02-2017,2000, Royalties
02-02-2017,-4000, Rent
03-02-2017,3000, Tesco
05-02-2017,-30, Cinema

and parses each line and outputs an object of this class:

public class BankTransaction {
    private final LocalDate date;
    private final double amount;
    private final String description;

    public BankTransaction(final LocalDate date, final double amount, 
           final String description) {
        this.date = date;
        this.amount = amount;
        this.description = description;
    }

    public LocalDate getDate() {
        return date;
    }

    public double getAmount() {
        return amount;
    }

    public String getDescription() {
        return description;
    }
}

This works fine and CSV was easy (especially since I was copying the book...), the problem came at the end of the chapter when I was asked to implement BankStatementJSONParser on my own if I could.

I've tried woth GSON and Jackson and I can't really get any example to work since my BankTransaction class is immutable and doesn't allow setters.

I managed to get one solution working but it's horrible to look at:

public List<BankTransaction> parseLinesFrom(List<String> lines) {
    Map<String, ?> map = new Gson().fromJson(String.join(" ", lines), Map.class);
    List<?> listOfTransactions = (List<?>) map.get("transactions");

    List<BankTransaction> bankTransactions = new ArrayList<>();
    listOfTransactions.forEach(rawJson -> {
        LinkedTreeMap<String, ?> javaJson = (LinkedTreeMap<String, ?>) rawJson;

        final LocalDate localDate = LocalDate.parse(javaJson.get("date").toString(), DATE_PATTERN);
        final double amount = Double.parseDouble(javaJson.get("amount").toString());
        final String description = javaJson.get("description").toString();
        BankTransaction newTransaction = new BankTransaction(localDate, amount, description);
        bankTransactions.add(newTransaction);
    });
    return bankTransactions;
}

Thankful for any insights how I can make this less ugly, maybe the problem is with my self-written .json file:

{
  "transactions": [
    {
      "date": "30-01-2017",
      "amount": -100,
      "description": "Deliveroo"
    },
    {
      "date": "01-02-2017",
      "amount": 6000,
      "description": "Salary"
    }
  ]
}

Upvotes: 3

Views: 115

Answers (2)

Xenofono
Xenofono

Reputation: 219

I used the method described by the accepted answer but in Jackson instead of GSON by overriding the Jackson ObjectMapper and accepting a DateTimeFormatter in the constructor:

public class CustomObjectMapper extends ObjectMapper {
public CustomObjectMapper(DateTimeFormatter pattern){
    SimpleModule customModule = new SimpleModule("LocalDateMapper");
    customModule.addSerializer(LocalDate.class, new JsonSerializer<LocalDate>() {
        @Override
        public void serialize(LocalDate value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
            ToStringSerializer.instance.serialize(value, gen, serializers);
        }
    });
    customModule.addDeserializer(LocalDate.class, new JsonDeserializer<LocalDate>() {
        @Override
        public LocalDate deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
            return LocalDate.parse(p.getText(), pattern);
        }
    });

    registerModule(customModule);

}

}

Upvotes: 0

ThexXTURBOXx
ThexXTURBOXx

Reputation: 391

There are some different solutions to your problem:

  1. You could create your own TypeAdapter to parse the List
  2. You could use TypeTokens to tell Gson at runtime what you will load (this will most certainly break the field date though)
  3. You could create your own JsonDeserializer

I personally would do the second one and create a custom TypeAdapter just for the LocalDate, which gets automatically applied by Gson after registering it.

Upvotes: 2

Related Questions