How do I convert an InputStream into a Map in Groovy? - groovy

Suppose I have an InputStream that contains JSON data, and I want to convert it to a Map(Key Value Pairs), so for example I can write that to a log file.
What is the easiest way to take the InputStream and convert it to a Map?
public String convertInputStreamToMap(InputStream isobj) {
// ???
}
I've tried converting to String which works as expected but when the data is really long the data will be incomplete. So I want some easiest way to directly convert this to Map.

Use ObjectMapper from com.fasterxml.jackson.databind to directly convert inputstream to Map:
for example:
objectMapper.readValue(is, Map.class);

there is a built-in class in groovy to parse json: groovy.json.JsonSlurper
it has parse method that accepts almost any source including InputStream
def url = 'https://httpbin.org/get'.toURL()
def json = url.withInputStream{inputStream->
new groovy.json.JsonSlurper().parse(inputStream)
}
println json.headers
and if you want to convert InputStream to String, groovy provides additional methods to work with InputStream class: https://docs.groovy-lang.org/latest/html/groovy-jdk/java/io/InputStream.html
the following code reads the content of this InputStream using specified charset and returns it as a String.
String s = inputStream.getText("UTF-8")

Related

Groovy - How to serialize an object into a string

How to serialize an object into a string
below is the .net code for serializing an object into a string
String sampleEntity= JsonConvert.SerializeObject(entity))
same I need it in groovy? please suggest
Assuming entity is some object or list of objects, the easiest way IMO is:
import groovy.json.*
class Person { // this is a sample object, like entity in your example
String name
}
def json = JsonOutput.toJson([ new Person(name: 'John'), new Person(name: 'Max') ])
println json​
// output (string): [{"name":"John"},{"name":"Max"}]
If you need to customize the output (like fiddle with exact format of dates or something), you should use JsonGenerator Instead. It has a builder that will allow to do this fine grained setup. Since its a kind of beyond the scope of the question, I'll just provide a link to the relevant chapter of documentation

NodaTime UnparsableValueException due to usage of "Z" in pattern

I am exchanging JSON messages between Java and C# (and vice-versa).
In Java I use a java.time.Instant (JSR-310) to represent a point in time on the global timeline. In order to create a human readable date/time string in JSON, I convert my Instant as follows:
private static final DateTimeFormatter FORMATTER = ofPattern("yyyy-MM-dd'T'HH:mm:ssZ").withZone(ZoneId.systemDefault());
which generates the following output:
2017-04-28T19:54:44-0500
Now, on the message consumer side of things (C#) I wrote a custom Newtonsoft.Json.JsonConverter, which extends the abstract JsonCreationConvert class that contains the following overridden ReadJson() method:
public override object ReadJson(JsonReader reader, Type objectType, object existingValue,
JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
{
return null;
}
if (reader.TokenType == JsonToken.StartArray)
{
return JToken.Load(reader).ToObject<string[]>();
}
reader.DateParseHandling = DateParseHandling.None; // read NodaTime string Instant as is
serializer.Converters.Add(NodaConverters.InstantConverter);
// Load JObject from stream
var jObject = JObject.Load(reader);
// Create target object based on JObject
T target = Create(objectType, jObject);
// Populate the object properties
var writer = new StringWriter();
serializer.Serialize(writer, jObject);
using (var newReader = new JsonTextReader(new StringReader(writer.ToString())))
{
newReader.Culture = reader.Culture;
newReader.DateParseHandling = reader.DateParseHandling;
newReader.DateTimeZoneHandling = reader.DateTimeZoneHandling;
newReader.FloatParseHandling = reader.FloatParseHandling;
serializer.Populate(newReader, target);
}
return target;
}
Create() is an abstract method.
When I now convert this JSON string into a NodaTime.Instant (v2.0.0) by calling:
InstantPattern.General.Parse(creationTime).Value;
I get this exception:
NodaTime.Text.UnparsableValueException: The value string does not match a quoted string in the pattern. Value being parsed: '2017-04-28T19:54:44^-0500'. (^ indicates error position.)
If I pass a text literal "Z" (so no outputted offset "-0500" and Z is interpreted as 0 offset) the NodaTime.Serialization.JsonNet.NodaConverters.InstantConverter correctly reads without throwing an exception.
Looking into the GeneralPatternImpl I see:
internal static readonly InstantPattern GeneralPatternImpl = InstantPattern.CreateWithInvariantCulture("uuuu-MM-ddTHH:mm:ss'Z'");
Why does an InstantConverter require the offset to be a text literal? Is this happening because an Instant is agnostic to an offset? If this is the case, then why doesn't the InstantConverter just ignore the offset instead of throwing an exception? Do I need to write a custom converter to get around this problem?
That's like asking for 2017-04-28T19:54:44 to be parsed as a LocalDate - there's extra information that we'd silently be dropping. Fundamentally, your conversion from Instant to String in Java is "adding" information which isn't really present in the original instant. What you're ending up with is really an OffsetDateTime, not an Instant - it has more information than an Instant does.
You should decide what information you really care about. If you only care about the instant in time, then change your Java serialization to use UTC, and it should end up with Z in the serialized form, and all will be well. This is what I suggest you do - propagating irrelevant information is misleading, IMO.
If you actually care about the offset in the system default time zone, which your call to .withZone(ZoneId.systemDefault()) implies you do, then you should parse it as an OffsetDateTime on the .NET side of things. You can convert that to an Instant afterwards if you want to (just call ToInstant()).

How to pass string as value in mapper?

I am trying to pass a string as value in the mapper, but getting error that it is not Writable. How to resolve?
public void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
String TempString = value.toString();
String[] SingleRecord = TempString.split("\t");
//using Integer.parseInt to calculate profit
int Amount = Integer.parseInt(SingleRecord[7]);
int Asset = Integer.parseInt(SingleRecord[8]);
int SalesPrice = Integer.parseInt(SingleRecord[9]);
int Profit = Amount*(SalesPrice-Asset);
String ValueProfit = String.valueOf(Profit);
String ValueOne = String.valueOf(one);
custID.set(SingleRecord[2]);
data.set(ValueOne + ValueProfit);
context.write(custID, data);
}
Yahoo's tutorial says :
Objects which can be marshaled to or from files and across the network must obey a particular interface, called Writable, which allows Hadoop to read and write the data in a serialized form for transmission.
From Cloudera site :
The key and value classes must be serializable by the framework and hence must implement the Writable interface. Additionally, the key classes must implement the WritableComparable interface to facilitate sorting.
So you need an implementation of Writable to write it as a value in the context. Hadoop ships with a few stock classes such as IntWritable. The String counterpart you are looking for is the Text class. It can be used as :
context.write(custID, new Text(data));
OR
Text outValue = new Text();
val.set(data);
context.write(custID, outValue)
I case, you need specialized functionality in the value class, you may implement Writable (not a big deal after all). However seems like Text is just enough for you.
you havent set data in map function according to import text in above,and TextWritable is wrong just use Text as well.

How to override string serialization in ServiceStack.Text?

How come the following works to override Guid formatting:
ServiceStack.Text.JsConfig<Guid>.SerializeFn = guid => guid.ToString();
But doing this to force null strings to empty strings doesn't?
ServiceStack.Text.JsConfig<string>.SerializeFn = str => str ?? string.Empty;
I have this enabled:
ServiceStack.Text.JsConfig.IncludeNullValues = true;
I have also tried the String class rather than the string primitive. And the raw version named .RawSerializeFn
Is there a different work around?
String's are specially handled in ServiceStack.Text so you can't override their behavior with configuration.
Given you can't override it, the only solution I can see (other than submitting a pull-request) is to reflect over the model and populate null properties with empty strings.

Can I do this with ExpandoMetaClasses in Groovy?

When upgrading from Groovy 1.8.4 to 1.8.5 the JsonSlurper returns a BigDecimal instead of a float or double for numbers in Json. For example consider the following JSON document:
{"person":{"name":"Guillaume","age":33.4,"pets":["dog","cat"]}}
In Groovy 1.8.4 "age" would be represented as a float whereas in Groovy 1.8.5+ it's represented as a BigDecimal. I've created a Java framework that uses the Groovy JsonSlurper under the hood so in order to maintain backward-compatibility I'd like to convert JSON numbers (such as 33.4) to float or double transparently. Having looked at the groovy-json source code I see that JsonSluper uses a JsonToken which is the one that creates a BigDecimal out of 33.4 in its "getValue()" method. This method is called by the JsonSlurper instance.
So what (I think) I want to do is to override the getValue() method in the JsonToken class to have it return a float or double instead. This is what I've tried:
def original = JsonToken.metaClass.getMetaMethod("getValue")
JsonToken.metaClass.getValue = {->
def result = original.invoke(delegate)
// Convert big decimal to float or double
if (result instanceof BigDecimal) {
if (result > Float.MAX_VALUE) {
result = result.doubleValue();
} else {
result = result.floatValue();
}
}
result
}
The problem is that even though the code stated above is executed before new JsonSluper().parseText(..) the overridden "getValue()" in JsonToken is not called (the original getValue() method is called instead). But if I copy all code from the JsonSlurper class into my own class, let's call it JsonSlurper2, and do new JsonSluper2().parseText(..) the overridden method of "getValue()" is called and everything works as expected. Why is this? What would I have to do to avoid copying JsonSlurper to my own class?
JsonSlurper is a Java class and therefore you are unable to override its methods via metaClass. See this mailing list thread.
This question looks like it might have a way for you to do this.

Resources