1

I tried using both flexjson.JSONSerializer and com.fasterxml.jackson.databind.ObjectMapper in a freemarker template to convert a csv file loaded via the csv data loader csv(menu.csv, {trimCells: true}) to JSON.

When I try to run this, I get the following exception:

...
Caused by: java.lang.UnsupportedOperationException: Operation supported only on TemplateHashModelEx. fmpp.models.CsvSequence does not implement it though.
    at freemarker.ext.beans.HashAdapter.getModelEx(HashAdapter.java:186)
    at freemarker.ext.beans.HashAdapter.access$000(HashAdapter.java:38)
    at freemarker.ext.beans.HashAdapter$1.iterator(HashAdapter.java:99)
    at com.fasterxml.jackson.databind.ser.std.MapSerializer.serializeFields(MapSerializer.java:696)
    ... 46 common frames omitted

I don't know any of the internals of FreeMarker (yet), but this looks to me like the CsvSequence does implement the freemarker.template.TemplateHashModel but not freemarker.template.TemplateHashModelEx.

The only place I found getModelEx is in the HashAdaptor, so I assume that it uses the newer interface for hashs.

Any idea how to solve this?

Peter T.
  • 2,927
  • 5
  • 33
  • 40
  • `CsvSequence` is essentially a `List`-like thing, and I guess you want to serialize a `List` of `Map`-s, not a single `Map`. So are you sure you wanted to call `MapSerializer`? (Otherwise, yes, `CsvSequence` certainly should implement `TemplateHashModelEx`, but as per above, that's irrelevant in your case.) – ddekany Jun 28 '21 at 21:13
  • @ddekany thanks for you help. BTW: Are you aware of a more elegant way to convert the csv sequence to a normal sequence than the ones I noted in my answer? Both somehow feel like a work around ... and for a reader they might not look intuitive to understand (although they do work). – Peter T. Jun 29 '21 at 13:21
  • 1
    This is a problem with interfacing with the Java type system, where FreeMarker has to decide if it will convert the multi-typed value to `List` or to `Map`. I guess `stringify` declared `Object` argument type, and so FreeMarker is clueless, and happens to chose conversion to `Map`. I don't know about a better workaround than what you did, for the current versions. FreeMarker should add built-ins like `?purely_sequence` to handle these. Also in FMPP `CsvSequence` could have a `data` key, and it could implement `TemplateHashModelEx`. – ddekany Jul 03 '21 at 10:40

2 Answers2

1

The CsvSequence implements both freemarker.template.TemplateHashModel and freemarker.template.TemplateSequenceModel, see http://fmpp.sourceforge.net/api/fmpp/models/CsvSequence.html

CsvSequence is also a hash that contains one key: headers. This is a sequence that stores the header names

As I want to convert only the sequence, not the headers, I can simply convert the CsvSequence to a normal sequence e.g. like this:

${JSON.stringify(csvInput[0..])}

or

${JSON.stringify([] + csvInput)}
Peter T.
  • 2,927
  • 5
  • 33
  • 40
0

You can also just convert manually:

    <#assign header = _responses["getCSV"]["rawBody"]?keep_before('\r\n')?split(_variables["textquoter"]+_variables["commaseperator"]+_variables["textquoter"])>
    <#assign data = _responses["getCSV"]["rawBody"]?keep_after('\r\n')?replace('\r\n',_variables["textquoter"]+_variables["commaseperator"]+_variables["textquoter"])?split(_variables["textquoter"]+_variables["commaseperator"]+_variables["textquoter"])>
    <#if data[data?size-1] = ''><#assign data = data[0..data?size-2]></#if>

    <#assign json>
    <#compress>
    [
    <#list data as X>
    <#assign mod = (X?index) % (header?size)>
    <#if mod = 0>{</#if>
    "${header[mod]?remove_beginning(_variables["textquoter"])?remove_ending(_variables["textquoter"])}" : "${X?remove_beginning(_variables["textquoter"])?remove_ending(_variables["textquoter"])}"<#if mod lt header?size-1>,</#if>
    <#if mod = header?size-1>},</#if>
    </#list>
    </#compress>
    </#assign>
    <#assign json = json?remove_ending(',') + '\n]'>
    ${json}