I have a service that is sending me a list of values and after parsing with the 'dart:convert json.decode' I get a List < dynamic > json as such:
(values) [ 0.23, 0.2, 0, 0.43 ]
When I try to parse the values to 'double', I get a parsing error
double value = values[2] // error
Because if we check the .runtimeType of each element we can see that we have the following
[double, double, int, double]
So, how can I avoid '0' being interpreted as int?
I would like to avoid using a function like this:
double jsonToDouble(dynamic value) {
if(value.runtimeType == int) {
return (value as int).toDouble();
}
return value;
}
You can add some special logic to the JSON decoding by constructing your own codec with a reviver. This callback will be invoked for every property in a map or item in a List.
final myJsonCodec = new JsonCodec.withReviver((dynamic key, dynamic value) {
if (value is int) return value.toDouble();
return value;
});
However, you will always get a List<dynamic> from dart:covert, whether or not all of your number types are doubles or ints. Depending on what version of dart you are on you may be able to use List.cast to convert to a List<double>.
List<double> values = oldValues.cast<double>();
If that isn't available, you can create a new List<double> and add your values to it so that the runtime type is correctly set.
Doubles have a toDouble() method. So, you can use that in both cases (double and int).
double jsonToDouble(dynamic value) {
return value.toDouble();
}
This works only if the value is a double or an int, though. If the value is a string, an exception occurs.
Related
Minimal reproducible code:
void main() {
print(foo<int>('1'));
print(foo<double>('1.0'));
print(foo<double>('1'));
print(foo<int>('1.0'));
}
T foo<T extends num>(String s) {
final res = num.parse(s);
return (T == double ? res.toDouble() : res.toInt()) as T;
}
As you can see I'm manually handling the types. Is there any better way to do it?
I don't see a much better solution. You have a function which changes behavior entirely based on the type argument. Since all you can do with a type argument is subtype-checks, or comparing it to a constant type literal, you need to do something like that.
I'd prefer subtype-checks, because that also allows promotion, but for something limited like this, where there are only four possible types for T, checking Type object equality can also work. There'll just need to be at least one as T before returning.
Either approach also only works for type hierarchies which are finite, and you account for all the possible types. Even here, the current code is not covering <num> and <Never> which are also valid type arguments for the bound num. So, be vigilant.
Maybe, using subtype checks for promotion:
T parse<T extends num>(String source) {
var value = num.parse(source);
if (value is T) return value;
// T is not `num`.
num d = value.toDouble();
if (d is T) return d; // T was double.
try {
num n = value.toInt(); // Can fail for Infinity/NaN
if (n is T) return n; // T was int
} catch (_) {
// T was `int` after all, so throwing was correct.
if (1 is T) rethrow; // T was int, but the input was not valid.
}
// T was neither num, double, nor int, so must be `Never`.
throw ArgumentError.value(T, "T", "Must not be Never");
}
Or. using Type object equality:
T parse<T extends num>(String source) {
var value = num.parse(source);
switch (T) {
case num: return value as T;
case int: return value.toInt() as T;
case double: return value.toDouble() as T;
default:
throw ArgumentError.value(T, "T", "Must not be Never");
}
}
T getValue<T>(int i) {
if (T == String) return '$i'; // Error
return i; // Error
}
void main() {
var s = getValue<String>(1);
var i = getValue<int>(1);
}
I want getValue to return string if T is String and int otherwise. How to do that?
You can't restrict the type parameter to just int or String, so it will have to accept more than that (at least their least common supertype, Object, so basically any type).
It's not a particularly helpful way to code. It's possible, but not recommended:
T getValue<T>(int i) {
if (i is T) return i;
return "$i" as T;
}
This will return the int if T allows it (so T being any of int, or a super type of int, which is num, Object, dynamic or void, or any number of Comparable<X> wrappings around any any of those supertypes), and otherwise try to return a string. That will fail with a type error unless T is String (since we've already ruled out all supertypes of String).
You can still call it as getValue<bool>(42) and watch it fail, so the type argument doesn't help with correctness.
It's not particularly effective. I'd rather do:
dynamic getValue(int i, {bool toString = false}) {
if (toString) return "$i";
return i;
}
and call it as:
String x = getValue(42, toString: true); // Add `as String` if you disable downcasts.
int y = getValue(42); // And `as int` here.
The type parameter is really just making things harder. You are going to cast or type-check the result anyway, so might as well do it at the call point, rather than introduce type variables that aren't actually preventing misuse anyway.
(I'd probably just do two different functions, but I assume that there is a reason for wanting one function).
As I mentioned in the comments, I don't see any way that you could use your generic as the return type of your getValue function. Even assuming the return under the if statement worked, there is nothing that can be done about trying to return int i when List is passed as the type. You'll be trying to return an int as a List.
If you change it to dynamic, your code will work fine as it's just using the generic as another parameter.
dynamic getValue<T>(int i) {
if (T == String) return '$i';
return i;
}
void main() {
var s = getValue<String>(1);
var i = getValue<int>(1);
}
I wrote this code to convert dynamic list to Word list but linter says:
Omit type annotations for local variables. on 2nd line.
However if I omit type annotations, I get an error A value of type 'List<dynamic>' can't be returned from method 'convert' because it has a return type of 'List<Word>'.
It there any smarter way to convert?
static List<Word> convert(List<dynamic> words) {
final List<Word> wordsList = [];
words.forEach((v) {
final map = Map<String, dynamic>.from(v as Map<dynamic, dynamic>);
wordsList.add(Word.fromMap(map));
});
return wordsList;
}
Word.fromMap is:
Word.fromMap(Map<String, dynamic> map)
: text = map['text'] as String,
count = map['count'] as int;
To avoid the warning, and put the type on the right-hand side as the lint wants, just write:
final wordsList = <Word>[];
I assume words is JSON data, so the maps are already Map<String, dynamic>. Then you can also do everything in one line:
static List<Word> convert(List<dynamic> words) =>
[for (var v in words) Word.fromMap(v)];
Use the cast() method like this:
class Word {
final String text;
final int count;
static List<Word> convert(List<dynamic> words) {
final List<Word> wordsList = [];
words.cast<Map<dynamic, dynamic>>().forEach((v) { // <-- look here
final map = Map<String, dynamic>.from(v);
wordsList.add(Word.fromMap(map));
});
return wordsList;
}
Word.fromMap(Map<String, dynamic> map)
: text = map['text'] as String,
count = map['count'] as int;
}
It will ensure the casting are done on each element. Make sure the type are correct since it will else result in a type-cast error.
I am trying to write a function that takes two arguments: givenType and targetType. If these two arguments match, I want givenType to be returned, otherwise null.
For this objective, I am trying to utilize Dart's is expression (maybe there is a better way to go about it, I am open to suggestions). Initially, I thought it would be as simple as writing this:
matchesTarget(givenType, targetType) {
if (givenType is targetType) {
return givenType;
}
return null;
}
But this produces an error:
The name 'targetType' isn't a type and can't be used in an 'is'
expression. Try correcting the name to match an existing
type.dart(type_test_with_non_type)
I tried looking up what satisfies an is expression but cannot seem to find it in the documentation. It seems like it needs its right-hand operand to be known at compile-time (hoping this is wrong, but it does not seem like I can use a variable), but if so, how else can I achieve the desired effect?
I cant guess the purpose of the function (or the scenario where it would be used, so if you can clarify it would be great). First of all, I don't know if you are passing "types" as arguments. And yes, you need to specify in compile time the right hand argument of the is function.
Meanwhile, if you are passing types, with one change, you can check if the types passed to your function at runtime.
matchesTarget(Type givenType, Type targetType) {
print('${givenType.runtimeType} ${targetType.runtimeType}');
if (givenType == targetType) {
return givenType;
}
return null;
}
main(){
var a = int; //this is a Type
var b = String; //this is also a Type
print(matchesTarget(a,b)); //You are passing different Types, so it will return null
var c = int; //this is also a Type
print(matchesTarget(a,c)); //You are passing same Types, so it will return int
}
But if you are passing variables, the solution is pretty similar:
matchesTarget(givenVar, targetVar) {
print('${givenVar.runtimeType} ${targetVar.runtimeType}');
if (givenVar.runtimeType == targetVar.runtimeType) {
return givenVar.runtimeType;
}
return null;
}
main(){
var a = 10; //this is a variable (int)
var b = "hello"; //this is also a variable (String)
print(matchesTarget(a,b)); //this will return null
var c = 12; //this is also a variable (int)
print(matchesTarget(a,c)); //this will return int
}
The Final Answer
matchesTarget(givenVar, targetType) {
print('${givenVar.runtimeType} ${targetType}');
if (givenVar.runtimeType == targetType) {
return givenVar;
}
return null;
}
main(){
var a = 10; //this is a variable (int)
var b = String; //this is a type (String)
print(matchesTarget(a,b)); //this will return null because 'a' isnt a String
var c = int; //this is also a type (int)
print(matchesTarget(a,c)); //this will return the value of 'a' (10)
}
The as, is, and is! operators are handy for checking types at runtime.
The is operator in Dart can be only used for type checking and not checking if two values are equal.
The result of obj is T is true if obj implements the interface specified by T. For example, obj is Object is always true.
See the below code for an example of how to use the is operator
if (emp is Person) {
// Type check
emp.firstName = 'Bob';
}
Even the error message that you're getting says that
The name 'targetType' isn't a type and can't be used in an 'is'
expression.
So the bottomline is that you can use is only for checking if a variable or value belongs to a particular data type.
For checking equality, you can use the == operator if comparing primitive types, or write your own method for comparing the values. Hope this helps!
I am using a 3rd party C library in my iOS application, which I am in the process of converting from Objective-C to Swift. I hit an obstacle when attempting to read one of the structs returned by the C library in Swift.
The struct looks similar to this:
typedef unsigned int LibUint;
typedef unsigned char LibUint8;
typedef struct RequestConfiguration_ {
LibUint8 names[30][128];
LibUint numberNames;
LibUint currentName;
} RequestConfiguration;
Which is imported into Swift as a Tuple containing 30 Tuples of 128 LibUint8 values. After a long time of trial and error using nested withUnsafePointer calls, I eventually began searching for solutions to iterating a Tuple in Swift.
What I ended up using is the following functions:
/**
* Perform iterator on every children of the type using reflection
*/
func iterateChildren<T>(reflectable: T, #noescape iterator: (String?, Any) -> Void) {
let mirror = Mirror(reflecting: reflectable)
for i in mirror.children {
iterator(i.label, i.value)
}
}
/**
* Returns a String containing the characters within the Tuple
*/
func libUint8TupleToString<T>(tuple: T) -> String {
var result = [CChar]()
let mirror = Mirror(reflecting: tuple)
for child in mirror.children {
let char = CChar(child.value as! LibUint8)
result.append(char)
// Null reached, skip the rest.
if char == 0 {
break;
}
}
// Always null terminate; faster than checking if last is null.
result.append(CChar(0))
return String.fromCString(result) ?? ""
}
/**
* Returns an array of Strings by decoding characters within the Tuple
*/
func libUint8StringsInTuple<T>(tuple: T, length: Int = 0) -> [String] {
var idx = 0
var strings = [String]()
iterateChildren(tuple) { (label, value) in
guard length > 0 && idx < length else { return }
let str = libUint8TupleToString(value)
strings.append(str)
idx++
}
return strings
}
Usage
func handleConfiguration(config: RequestConfiguration) {
// Declaration types are added for clarity
let names: [String] = libUint8StringsInTuple(config.names, config.numberNames)
let currentName: String = names[config.currentName]
}
My solution uses reflection to iterate the first Tuple, and reflection to iterate the second, because I was getting incorrect strings when using withUnsafePointer for the nested Tuples, which I assume is due to signage. Surely there must be a way to read the C strings in the array, using an UnsafePointer alike withUsafePointer(&struct.cstring) { String.fromCString(UnsafePointer($0)) }.
To be clear, I'm looking for the fastest way to read these C strings in Swift, even if that involves using Reflection.
Here is a possible solution:
func handleConfiguration(var config: RequestConfiguration) {
let numStrings = Int(config.numberNames)
let lenStrings = sizeofValue(config.names.0)
let names = (0 ..< numStrings).map { idx in
withUnsafePointer(&config.names) {
String.fromCString(UnsafePointer<CChar>($0) + idx * lenStrings) ?? ""
}
}
let currentName = names[Int(config.currentName)]
print(names, currentName)
}
It uses the fact that
LibUint8 names[30][128];
are 30*128 contiguous bytes in memory. withUnsafePointer(&config.names)
calls the closure with $0 as a pointer to the start of that
memory location, and
UnsafePointer<CChar>($0) + idx * lenStrings
is a pointer to the start of the idx-th subarray. The above code requires
that each subarray contains a NUL-terminated UTF-8 string.
The solution suggested by Martin R looks good to me and, as far as I can see from my limited testing, does work. However, as Martin pointed out, it requires that the strings be NUL-terminated UTF-8. Here are two more possible approaches. These follow the principle of handling the complexity of C data structures in C instead of dealing with it in Swift. Which of these approaches you choose depends on what specifically you are doing with RequestConfiguration in your app. If you are not comfortable programming in C, then a pure Swift approach, like the one suggested by Martin, might be a better choice.
For the purposes of this discussion, we will assume that the 3rd party C library has the following function for retrieving RequestConfiguration:
const RequestConfiguration * getConfig();
Approach 1: Make the RequestConfiguration object available to your Swift code, but extract names from it using the following C helper function:
const unsigned char * getNameFromConfig(const RequestConfiguration * rc, unsigned int nameIdx)
{
return rc->names[nameIdx];
}
Both this function's signature and the RequestConfiguration type must be available to the Swift code via the bridging header. You can then do something like this in Swift:
var cfg : UnsafePointer<RequestConfiguration> = getConfig()
if let s = String.fromCString(UnsafePointer<CChar>(getNameFromConfig(cfg, cfg.memory.currentName)))
{
print(s)
}
This approach is nice if you need the RequestConfiguration object available to Swift in order to check the number of names in multiple places, for example.
Approach 2: You just need to be able to get the name at a given position. In this case the RequestConfiguration type does not even need to be visible to Swift. You can write a helper C function like this:
const unsigned char * getNameFromConfig1(unsigned int idx)
{
const RequestConfiguration * p = getConfig();
return p->names[idx];
}
and use it in Swift as follows:
if let s = String.fromCString(UnsafePointer<CChar>(getNameFromConfig1(2)))
{
print(s)
}
This will print the name at position 2 (counting from 0). Of course, with this approach you might also want to have C helpers that return the count of names as well as the current name index.
Again, with these 2 approaches it is assumed the strings are NUL-terminated UTF-8. There are other approaches possible, these are just examples.
Also please note that the above assumes that you access RequestConfiguration as read-only. If you also want to modify it and make the changes visible to the 3rd party library C code, then it's a different ballgame.