How create dynamic json model - flutter

how can I make a dynamic JSON model for this Josn, all keys are dynamic like "Large,X-Larg, Red, Blue", only the key optionDisplayValues is
{
"optionDisplayValues":{
"Large":{
"value":"Large",
"displayType":"text"
},
"X-Larg":{
"value":"X-Larg",
"displayType":"text"
},
"Red":{
"value":"#7d4141",
"displayType":"color"
},
"Blue":{
"value":"#25a953",
"displayType":"color"
}
}
}

You can go to this link, and you can paste your json there.
Then after that, you can name your class whatever you want and click on the Generate button. It will generate the model for you.
There are also plugins available for both Android studio and VS code for converting JSON to dart.

Each dynamic item should be represented as Map<DisplayValue>, so:
DisplayValue should be:
class DisplayValue {
String value;
String displayText;
}
And your collection should be:
class MyDataCollection {
Map<String,DisplayValue> optionDisplayValues;
}
To generate toJson and fromJson method use json_serializable plugin:
https://pub.dev/packages/json_serializable
You will also need to generate constructors and put json annotations/methods as required by that lib.

Related

yup - is there any way to set the default value for a string field to be something without defining it for each one

I want that every time I use yup.string(), it will add a specific default value for it
for example:
const schema = yup.object({
text: yup.string()// I want it to also do .default('some string') in the background,
});
or - another option - is there any way to set the default value after creating the scheme? something like setDefault('text', 'some string')
The closest solution I came across to solve your issue is extending your string with a custom method that implements your needs. To do that you need to use addMethod from yup:
import { addMethod, string } from 'yup';
addMethod(string, 'append', function append(appendStr) {
return this.transform((value) => `${value}${appendStr}`);
});
Now, you can use your custom method (append) and apply it to any string you want:
string().append('~~~~').cast('hi'); // 'hi~~~~'
If you want to add the custom method to all your schema types like date, number, etc..., you need to extend the abstract base class Schema:
import { addMethod, Schema } from 'yup';
addMethod(Schema, 'myCustomMethod', ...)
Extra
For Typescript
In your type definition file, you need to declare module yup with your custom method's arguments and return types:
// globals.d.ts
import { StringSchema } from "yup";
declare module 'yup' {
interface StringSchema<TType, TContext, TDefault, TFlags> {
append(appendStr: string): this;
}
}
Unknow behavior for transform method
While I was trying to extend the functionality of the date schema with a custom method that transform the date that user enters from DD-MM-YYY to YYYY-MM-DD, the custom method broke after I used it with other methods like min, max for example.
// `dayMonthYear` should transform "31-12-2022"
// to "2022-12-31" but for some reason it kept
// ignoring the `cast` date and tried to transform
// `1900` instead!
Yup.date().dayMonthYear().min(1900).max(2100).required().cast("31-12-2022") // error
To work around this issue, I appended my custom method at the end of my schema chain:
Yup.date().min(1900).max(2100).required().cast("31-12-2022").dayMonthYear() // works as expected
This issue is mentioned in this GH ticket which I recommend going through it as it's going more in-depth on how to add custom methods with Typescript.
References
addMethod
Extending built-in schema with new methods
Example of addMethod in Typescript (GH ticket)

How to enforce a new property has a value set at compile time in dart?

I am new to dart/flutter and I am adding internationalization on my app.
As flutter don't define a proper way to implement internationalization (according to the video I watched), devs are free to do it in any way they think its better.
The video showed an implementation using json files. So on "lang" folder I have my json files with all strings and their respective translations.
Now imagine this is a large file with lots of entries. And I have to make a big change on my app, removing some strings, adding others and adding a new language. This is very error prone as I may forget to add some key-value entry in any of the json files. It happened sometimes and the error is thrown at runtime.
In java there is a way to enforce the dev don't forget to add the same values in all variations of the same property at compile time. Maybe it's not the best way to achieve this, but one way is using an enum like in the image below.
Now if I want to add a new language, I just add a new parameter in the enum constructor and there is no way to forget adding a translation because the code would not even compile.
To fix the error I just have to add the value on the Strings (I forgot the "es" case here):
And then I can use the values doing something like this (the code to find the locale using the context could even be moved to inside the .value() method):
So is there a good way to achieve this in dart/flutter? To enforce all the languages have all the translated texts at compile time?
My method looks like this:
First, define the interface, and its implementations:
abstract class StringsInterface {
String get greet;
}
class EnglishStrings implements StringsInterface {
String get greet => "Hello";
}
class SpanishStrings implements StringsInterface {
String get greet => "Hola";
}
Next, define a class to store an instance of the current locale, as well as configure which class to use, for which language code:
class Strings {
static StringsInterface current = EnglishStrings();
static late Locale _locale;
static Locale get locale => _locale;
static set locale(Locale locale) {
_locale = locale;
current = stringsForLocale(locale);
}
static StringsInterface stringsForLocale(Locale locale) {
switch(locale.languageCode) {
// Here is where you define which class gets used for which language code
case 'es': return SpanishStrings();
default: return EnglishStrings();
}
}
}
Set the locale for the Strings class at runtime:
void main() async {
/// load from your own future
/// (e.g. pull from a persisted value stored on device in SharedPreferences/UserDefaults)
Strings.locale = Locale(await Future<String>.value('en'));
runApp(const MyApp());
}
Finally, use like this:
Text(Strings.current.greet)
As of dart 2.17 you can now use enhanced enums with members:
enum Strings {
welcome(en: 'Welcome', pt: 'Bem-vindo'),
goodbye(en: 'Good bye', pt: 'Adeus');
final String en;
final String pt;
const Strings({
required this.en,
required this.pt,
});
String value(String lang) {
switch (lang) {
case 'pt':
return pt;
case 'en':
default:
return en;
}
}
}

Flutter 1.22 Internationalization with variable as key

I implemented the new (official) localization for Flutter (https://pascalw.me/blog/2020/10/02/flutter-1.22-internationalization.html) and everything is working fine, except that I don't know how to get the translation for a variable key.
The translation is in the ARB file, but how can I access it?
Normally I access translations using Translations.of(context).formsBack, but now I would like to get the translation of value["labels"]["label"].
Something like Translations.of(context).(value["labels"]["label"]) does not work of course.
I don't think this is possible with gen_l10n. The code that is generated by gen_l10n looks like this (somewhat abbreviated):
/// The translations for English (`en`).
class TranslationsEn extends Translations {
TranslationsEn([String locale = 'en']) : super(locale);
#override
String get confirmDialogBtnOk => 'Yes';
#override
String get confirmDialogBtnCancel => 'No';
}
As you can see it doesn't generate any code to perform a dynamic lookup.
For most cases code generation like this is a nice advantage since you get auto completion and type safety, but it does mean it's more difficult to accommodate these kinds of dynamic use cases.
The only thing you can do is manually write a lookup table, or choose another i18n solution that does support dynamic lookups.
A lookup table could look something like this. Just make sure you always pass in the current build context, so the l10n code can lookup the current locale.
class DynamicTranslations {
String get(BuildContext context, String messageId) {
switch(messageId) {
case 'confirmDialogBtnOk':
return Translations.of(context).confirmDialogBtnOk;
case 'confirmDialogBtnCancel':
return Translations.of(context).confirmDialogBtnCancel;
default:
throw Exception('Unknown message: $messageId');
}
}
}
To provide an example for https://stackoverflow.com/users/5638943/kristi-jorgji 's answer (which works fine):
app_en.arb ->
{
"languages": "{\"en\": \"English\", \"ro\": \"Romanian\"}"
}
localization_controller.dart ->
String getLocaleName(BuildContext ctx, String languageCode) {
return jsonDecode(AppLocalizations.of(ctx)!.languages)[languageCode];
}
getLocaleName(context, 'ro') -> "Romanian"
You can store a key in translation as json string.
Then you read it, parse it to Map<string,string> and access dynamically what you need.
Been using this approach with great success

Deserialise JSON response from API into type in Dart

I'm going out of my mind trying to figure this out because it's such a simple thing to do and there's no information on it anywhere.
I have an API call that is returning a Student which is defined like this
class Student {
String photo;
String upn;
String fullName;
String studentClass;
String studentYear;
}
I'm using the dart http library to make a call to an API that returns an array of a number of students. All I want to do is deserialise the string returned by the API into a typed list so that I can actually do something with it.
studentFuture.then((res) {
log(res.body.toString());
List<dynamic> dynamicList = jsonDecode(res.body);
var student = dynamicList[0] as Student;
});
Trying to cast it to Student using as doesn't work, I just get this
Unhandled Exception: type '_InternalLinkedHashMap<String, dynamic>' is not a subtype of type 'Student' in type cast
All I want is to be able to do something like this like you can in C#
var obj = DeserialiseJson<Type>(jsonString);
No matter what I try I can't get the string to be deserialised into an object, how do I do this?
You can't do this in Dart/Flutter in the way that you want. This is because in order to accomplish this functionality in C#, Java, or any other strongly-typed language that supports this feature, the language uses reflection under the hood to map JSON fields to object fields.
Dart also has reflection functionality in the dart:mirror library. However, this library is disabled in Flutter due to its tendency to create large app footprints due to all the debug information for various types and fields and things needing to be included in the compiled binary. (That's the very simple explanation: the official reason given by the Flutter team goes into more detail.)
What this means is when you deserialize a JSON string, you get an object that is essentially a List<dynamic> or Map<String, dynamic> (depending on the JSON string in question; it may even just be some primitive value). Assuming the latter, you cannot directly convert between a Map and a Student because they are completely different types with nothing in common. There is no implicit way to take the Map and generate a Student because the reflection tools necessary to do so don't exist within a Flutter app. As a result, you get an error saying you cannot convert a Map to a Student, as you can see.
To work around this, you are encouraged to add a toJson/fromJson method to your model class so you can explicitly generate an instance of the class from a deserialized JSON string:
class Student {
String photo;
String upn;
String fullName;
String studentClass;
String studentYear;
factory Student.fromJson(Map<String, dynamic> jsonData) {
return Student()
..photo = jsonData['photo']
..upn = jsonData['upn']
..fullName = jsonData['fullName']
..studentClass = jsonData['studentClass']
..studentYear = jsonData['studentYear'];
}
}
And use it like so:
studentFuture.then((res) {
log(res.body.toString());
List<dynamic> dynamicList = jsonDecode(res.body);
var student = Student.fromJson(dynamicList[0]);
});
This is all well and good, but if you have more than a few model classes you will quickly realize that adding this boilerplate code to every model class gets really tedious really quick. That's why various packages exist to try and alleviate the burden with automatic code generation, such as json_serializable and freezed.
TL;DR: What you are asking for is not currently possible in Flutter Dart, and that isn't likely to change any time soon. You need to either write a manual factory constructor to make the conversion or use a code-gen package that generates the constructors for you.
Have a look at https://pub.dev/packages/json_serializable which, through code generation, will give you methods like
#JsonSerializable()
class Student {
String photo;
String upn;
String fullName;
String studentClass;
String studentYear;
Student({this.photo, this.upn, this.fullName, this.studentClass, this.studentYear});
factory Student.fromJson(Map<String, dynamic> json) => _$StudentFromJson(json);
Map<String, dynamic> toJson() => _$StudentToJson(this);
}
Then you can do:
var list = jsonDecode(res.body);
for (var map in list) {
print(Student.fromJson(map)); // Student class
}

Unity3D & YamlDotNet Deserializing Data into Monobehaviour-derived classes

I'm trying to serialize data into / from my classes, derived from MonoBehaviour, which cannot be created from client code (e.g., with the new keyword), but rather must be created by a Unity3D-specific method, GameObject.AddComponent<T>(). How can I use the YamlDotNet framework to populate my classes with values without having to create an adapter for each one? Is there some sort of built-in adapter that I can configure, such that YamlDotNet doesn't instantiate the class it's trying to serialize to?
A typical file might contain a mapping of items, e.g.,
%YAML 1.1
%TAG !invt! _PathwaysEngine.Inventory.
%TAG !intf! _PathwaysEngine.Adventure.
---
Backpack_01: !invt!Item+yml
mass: 2
desc:
nouns: /^bag|(back)?pack|sack|container$/
description: |
Your backpack is only slightly worn, and...
rand_descriptions:
- "It's flaps twirl in the breeze."
- "You stare at it. You feel enriched."
MagLite_LR05: !invt!Lamp+yml
cost: 56
mass: 2
time: 5760
desc:
nouns: /^light|flashlight|maglite|lr_05$/
description: |
On the side of this flashlight is a label...
(Type "light" to turn it on and off.)
...
Where the tags are the fully specified class names of my Items, e.g., PathwaysEngine.Inventory.Lamp+yml, PathwaysEngine is the namespace I use for my game engine code, Inventory deals with items & whatnot, and Lamp+yml is how the compiler denotes a nested class, yml inside Lamp. Lamp+yml might look like this:
public partial class Lamp : Item, IWearable {
public new class yml : Item.yml {
public float time {get;set;}
public void Deserialize(Lamp o) {
base.Deserialize((Item) o);
o.time = time;
}
}
}
I call Deserialize() on all objects that derive from Thing from Awake(), i.e., once the MonoBehaviour classes exist in the game. Elsewhere, I've already created a pretty complicated Dictionary filled with objects of type Someclass+yml, and then Deserialize takes an instance of the real, runtime class Someclass and populates it with values. There's got to be a cleaner way to do this, right?
How can I:
Tell the Deserializer what my classes are?
See the second edit for a good solution for the above issue
Get the data without it attempting to create my MonoBehaviour-derived classes?
Edit: I've since worked at the problem, and have found out a good way of dealing with custom data (in my particular case of trying to parse regexes out of my data, and having them not be considered strings & therefore, un-castable to regex) is to use a IYamlTypeConverter for that particular string. Using YamlDotNet with Unity3D MonoBehaviours, however, is still an issue.
Another Edit: The above examples use a pretty ugly way of determining types. In my case, the best thing to do was to register the tags first with the deserializer, e.g.,
var pre = "tag:yaml.org,2002:";
var tags = new Dictionary<string,Type> {
{ "regex", typeof(Regex) },
{ "date", typeof(DateTime) },
{ "item", typeof(Item) }};
foreach (var tag in tags)
deserializer.RegisterTagMapping(
pre+tag.Key, tag.Value);
Then, I use the !!tag notation in the *.yml file, e.g.,
%YAML 1.1
---
Special Item: !!item
nouns: /thing|item|object/
someBoolean: true
Start Date: !!date 2015-12-17
some regex: !!regex /matches\s+whatever/
...
You can pass a custom implementation of IObjectFactory to the constructor of the Deserializer class. Every time the deserializer needs to create an instance of an object, it will use the IObjectFactory to create it.
Notice that your factory will be responsible for creating instances of every type that is deserialized. The easiest way to implement it is to create a decorator around DefaultObjectFactory, such as:
class UnityObjectFactory : IObjectFactory
{
private readonly DefaultObjectFactory DefaultFactory =
new DefaultObjectFactory();
public object Create(Type type)
{
// You can use specific types manually
if (type == typeof(MyCustomType))
{
return GameObject.AddComponent<MyCustomType>();
}
// Or use a marker interface
else if (typeof(IMyMarkerInterface).IsAssignableFrom(type))
{
return typeof(GameObject)
.GetMethod("AddComponent")
.MakeGenericMethod(type)
.Invoke();
}
// Delegate unknown types to the default factory
else
{
return DefaultFactory(type);
}
}
}