1

Please help me figure out the problem. I program in groovy (you can use a java example, it looks like there). Json comes to the input, in which it is not known how many fields. There can be 5 fields, maybe 10, maybe 50. My task is to process this json and return the data back using:

// Names of dataset columns
def names = ["a", "b", "c"];
// types of return values ​​in each column (for any field (column) json is always String)
def types = ["String", "String", "String"];
// formation of the dataset header
reader.outputLinesSetHeaders (names, types);
// Passing the data itself from json
reader.outputLines ([it.a, it.b, it.c])
// Close the dataset
reader.outputLinesEnd ();

If I knew the incoming json, then I would have set the required field names, the number of "String" in advance and returned their values by referring to a specific json field. The example below shows 3 json fields: auto, home, job. Accordingly, 3 times "String" for each of the fields and referring to a specific field to return the value of it.auto, it.home, it.job. But how can I do the same if I don't know the incoming json?

import groovy.json.JsonSlurper

import ru.itrpro.xm.plugins.groovy.ResultSetReader;

class XM_PARSE_XLS {

    def execute(ResultSetReader reader, String pfile) {



        def jsonSlurper = new JsonSlurper()
        def list = jsonSlurper.parseText(pfile)



        //The names of the columns of the dataset (now set statically to show an example; but in my case I don't know json and can't write it this way in advance)
        def names = ["AUTO", "HOME", "JOB"];
        //return types in each column (for any json, only "String" types)
        def types = ["String", "String", "String"];

        //формирование заголовка датасета
        reader.outputLinesSetHeaders(names,types);


                list.each {
                        //pass the values as a dataset from json (now set statically to show an example; but in my case I don't know json and can't write it this way in advance)
                        reader.outputLines([it?.AUTO, it?.HOME, it?.JOB]);
                }

        //closing dataset
        reader.outputLinesEnd();
        return null;



    }
    static void main(String... args) {
            String pfile =  """
[{"AUTO":"bmw",
  "HOME":"vest",
  "JOB":"bbds"},
  
  {"AUTO":"audi",
  "HOME":"dest",
  "JOB":"aads"},
  
  {"AUTO":"opel",
  "HOME":"lest",
  "JOB":"ffds"}]
"""
            def SSC = new XM_PARSE_XLS()
            def res = SSC.execute(new ResultSetReader(), pfile)

    }

}

Perhaps it is worth collecting all the field names of the incoming json into a list and specifying a list of "String" (any incoming json has all fields only "String") with the same number as the fields? But how to do this and how then should I pass the field values (it. ***)?

Ekz0
  • 63
  • 1
  • 8

2 Answers2

1

Assuming you want just to know the keys and the type of the value, you can create a union of all keys/types. This assumes, that the value type for a key is the same over all keys.

import groovy.json.JsonSlurper

def data = new JsonSlurper().parseText("""[{"a": 1, "b": 2, "c": 3, "x": true}, {"a": 4, "b": 5, "c": 6, "d": "Hello"}]""")
def content = data.collectEntries{ 
    it.collectEntries{ 
        [it.key, it.value.class.name] 
    } 
}

println content
// → [a:java.lang.Integer, b:java.lang.Integer, c:java.lang.Integer, x:java.lang.Boolean, d:java.lang.String]
cfrick
  • 35,203
  • 6
  • 56
  • 68
  • There are 3 problems with this method: 1. How can I fill in as many types of "String" as there are unique fields in json? 2. How can I collect and transfer these unique fields (in your example you specified "key" explicitly, but I do not know what is inside json)? 3. How can I pass values ​​in the format that I need using the same construction as indicated in the header? Added changes, gave a complete example of code for static data (I repeat, but I need the code to work for any json, be it not 3 fields that are already known, but 5, 10 or more unique fields that I don't know). – Ekz0 Oct 30 '20 at 22:00
  • Are you asking how to get the keys and type of the values for each map in the list of your? – cfrick Oct 31 '20 at 10:03
  • I edited the question, gave a code example. I have json as input and using ResultSetReader I have to return data from json as a dataset. But how can I do this if I do not know the names of the fields and their number in the incoming json? The example shows the work of the program with static data (if I knew json and its fields): "AUTO", "HOME", "JOB". How can I do the same, but for any json, the fields of which I don't know in advance, except that everything will be of type "String"? How do I pass a list of fields to "names" and values to reader.outputLines ([])? – Ekz0 Oct 31 '20 at 10:08
1

If the input JSON is of type Object (key-value pairs), it's parsed to a Map, so you can use it's methods to inspect it.

import groovy.json.JsonSlurper

String pfile =  """
[{"AUTO":"bmw",
  "HOME":"vest",
  "JOB":1},
  
  {"AUTO":"audi",
  "HOME":"dest",
  "JOB":2},
  
  {"AUTO":"opel",
  "HOME":"lest",
  "JOB":3}]
"""
def jsonSlurper = new JsonSlurper()
def list = jsonSlurper.parseText pfile

List names = list.inject( new HashSet() ){ res, map ->
  res.addAll map.keySet()
  res
}.toList()

// get types based on actual values
def types = list.first().values()*.getClass()*.simpleName

assert '[AUTO, JOB, HOME]' == names.toString()
assert '[String, String, Integer]' == types.toString()

//reader.outputLinesSetHeaders names, types

list.each{ e ->
  //reader.outputLines names.collect{ e[ it ] }
  println names.collect{ e[ it ] }
}

//reader.outputLinesEnd()

The lines are commented out to avoid compilation problems.

injecteer
  • 20,038
  • 4
  • 45
  • 89
  • Input json of type "String" (added a more detailed code example in the header). The proposed method does not work, the error is: Exception in thread "main" groovy.lang.MissingMethodException: No signature of method: XM_PARSE_XLS$_execute_closure1.doCall() is applicable for argument types: (java.util.LinkedHashMap, groovy.json.internal.LazyMap) values: [[:], [AUTO:bmw, HOME:vest, JOB:bbds]] Possible solutions: doCall(java.lang.Object, java.lang.Object, java.lang.Object) ... Swears at "Map namesTypes = list.inject([:]){ res, k, v ->" and "def res = SSC.execute(new ResultSetReader(), pfile)" – Ekz0 Oct 30 '20 at 21:54
  • 1
    you should put all relevant info in your question in the 1st place. See update – injecteer Oct 31 '20 at 00:01
  • Yes, your way works, but I need to use ResultSetReader () on assignment to return json as a dataset. If used, the error is: Exception in thread "main" groovy.lang.MissingMethodException: No signature of method: ru.itrpro.xm.plugins.groovy.ResultSetReader.outputLinesSetHeaders() is applicable for argument types: (java.util.HashSet, java.util.ArrayList) values: [[AUTO, JOB, HOME], [String, String, String]] Possible solutions: outputLinesSetHeaders(java.util.List, java.util.List) I understand that this is because of the "map", but we need a list. How to be? – Ekz0 Oct 31 '20 at 08:52
  • Thank you so much! Can you somehow choose your answer to be correct? – Ekz0 Oct 31 '20 at 12:10
  • Hello again! Another question arose. Is it possible somehow to dynamically set types in "types"? If, for example, not all json fields will be "String", but some "String", some "Integer" and define the field of what type and enter into "types"? – Ekz0 Nov 02 '20 at 13:14
  • I tried it, it reads the types, but I can't figure out why the sorting is broken? The types must match the names and go in the same order. For example, the names ["auto", "number", "job"] and the types ["String", "Integer", "String"]. In the process of executing the program, the types are obtained in a random order and the names of the json fields are also not in order, but randomly. Why is that? – Ekz0 Nov 03 '20 at 07:51
  • And the second question I can't figure out is it reads the type from the first json line? That is, if the value of number in the first line is null, and in the second it is already 10, then the type is null? It is impossible to make the type determined by going through the entire json and even if null everywhere, then output String by default, for example? Apologies for the possibly stupid questions, I am a beginner and am trying to figure it out. – Ekz0 Nov 03 '20 at 07:51
  • that doesn't work like this. Ask new questions and provide full info to them – injecteer Nov 03 '20 at 10:07
  • Look please: https://stackoverflow.com/questions/64802501/how-in-groovy-java-automatically-find-out-the-json-field-type-and-return-value/64803433#64803433 – Ekz0 Nov 12 '20 at 18:35