How to deserialize Date ISO String to DateTime Object in built_value serialization in dart? - flutter

I wanna serialize a json object including an ISOString date to a dart object using built value.
this is a sample json:
{
"title": "test",
"description": "test description",
"date": "2020-06-05T11:42:38.585Z",
"creator": {
"email": "test#test.com"
}
}
this is the model:
abstract class Post implements Built<Post, PostBuilder> {
#nullable
#BuiltValueField(wireName: '_id')
String get id;
String get title;
String get description;
DateTime get date;
#nullable
User get creator;
Post._();
static Serializer<Post> get serializer => _$postSerializer;
factory Post([updates(PostBuilder b)]) = _$Post;
factory Post.fromJson(Map<String, dynamic> map) =>
serializers.deserializeWith(Post.serializer, map);
Map<String, dynamic> toJson() =>
serializers.serializeWith(Post.serializer, this);
}
and this is the error:
Deserializing '[title, test1, description, test1 description, date, 2020-06-05T...' to
'Post' failed due to: Deserializing '2020-06-05T11:42:38.585Z' to 'DateTime' failed due
to: type 'String' is not a subtype of type 'int' in type cast
how do I fix that?

You can import the Iso8601DateTimeSerializer directly from built_value - don't copy the file over to your project.
Your final serializers.dart should look like:
import 'package:built_value/iso_8601_date_time_serializer.dart';
import 'package:built_value/iso_8601_duration_serializer.dart';
import 'package:built_value/serializer.dart';
import 'package:built_value/standard_json_plugin.dart';
part 'serializers.g.dart';
#SerializersFor([
// your built value classes
])
final Serializers serializers = (_$serializers.toBuilder()
..add(Iso8601DateTimeSerializer())
..add(Iso8601DurationSerializer())
..addPlugin(StandardJsonPlugin())
).build();

You need to add a custom DateTime serializer that you can find here: Iso8601DateTimeSerializer
create a new dart file (I named it iso8601_date_time_serializer.dart)
paste the code from 1
add the import to your serializers.dart file (import 'iso8601_date_time_serializer.dart';)
edit your serializers.g.dart file
Serializers _$serializers = (new Serializers().toBuilder()
..add(Iso8601DateTimeSerializer())
..add(Post.serializer) // I assume you have this in yours
..addPlugin(StandardJsonPlugin()))
.build();
Please note that this modification might be deleted if you regenerate the code with build_runner.
In case you want to dig deeper, I got the answer from built_value GitHub issue 454

Related

Flutter dart replace replace Json object with variables

In this case I have class. Where I took a variable. Also I have a Json map. So I want to change Json map object replace with variables. Here is my code example....
So how can I achieve that
I want replace Json object with dart variable
class Data {
late String slug;
Map<String, String> singleProductVariable = {"slug": "$slug"};
}
Firstly, there is no JSON in your code sample.
I assume that you would like to set the value of the corresponding key in your Map when setting the variable.
If so, you might want to use a setter in a next way:
class Data {
String _slug;
late Map<String, String> v = {"slug": _slug};
Data(String slug) : _slug = slug;
set slug(String str) => v['slug'] = str;
}
void main() {
final d = Data("slug");
print(d.v);
d.slug = "newSlug";
print(d.v);
}
The output of the code above will be:
{slug: val}
{slug: newVal}

How to serialize private fields in json-serializable?

Minimal reproducible code:
#JsonSerializable()
class A {
final int _x;
A(int x) : _x = x;
factory A.fromJson(Map<String, dynamic> json) => _$AFromJson(json);
}
Note:
I don't want to make my private field _x public or define a public x getter.
This PR addresses what you want:
https://github.com/google/json_serializable.dart/pull/1256/files#diff-0acaf4c472e452d1e5d215a15fcd2266ccd02ab6abdfac0080c2fca845eb9096
You will be able to explicitly set includeFromJson and includeToJson on the private fields you want to include.
Example:
class X {
#JsonKey(includeFromJson: true, includeToJson: true)
int _includeMeToTheJsonParsing;
}
It was merged November 30th. Latest package version is v6.5.4, released at October 25th. So you will need to wait a little bit if you want the official release. Otherwise, you can point directly to the latest commit if you need it ASAP.
If you don't want to make your private field public or define a public getter, you can still use a serialization library like json_serializable or built_value to serialize and deserialize the class, but you will need to define a custom toJson method that manually serializes the private field. Here's an example of using json_serializable with a custom toJson method:
import 'package:json_annotation/json_annotation.dart';
part 'my_class.g.dart';
#JsonSerializable()
class MyClass {
final int _x;
MyClass(this._x);
Map<String, dynamic> toJson() => {
'x': _x,
};
factory MyClass.fromJson(Map<String, dynamic> json) => MyClass(json['x'] as int);
}
You can then use the fromJson and toJson methods to serialize and deserialize your class:
import 'dart:convert';
void main() {
// Serialize to JSON
MyClass obj = MyClass(42);
String json = jsonEncode(obj);
// Deserialize from JSON
MyClass obj2 = MyClass.fromJson(jsonDecode(json));
}

Encoding to a standard, colon-seperated json in dart/flutter and built_value

In my flutter project, I want to use built_value for json serialization. One of the sample classes is:
import 'dart:convert';
import 'package:built_value/built_value.dart';
import 'package:built_value/serializer.dart';
part 'device.model.g.dart';
abstract class Device implements Resource, Built<Device, DeviceBuilder> {
// Fields
String get name;
String get code;
bool get isActive;
Device._();
factory Device([void Function(DeviceBuilder) updates]) = _$Device;
String toJson() {
return json.encode(serializers.serializeWith(Device.serializer, this));
}
factory Device.fromJson(String jsonString) {
return serializers.deserializeWith(
Device.serializer, json.decode(jsonString))!;
}
static Serializer<Device> get serializer => _$deviceSerializer;
}
I used the build_runner to generate the codes and everything is OK.
Also I have defined the serializers:
part 'serializers.g.dart';
final standardSerializers =
(serializers.toBuilder()..addPlugin(StandardJsonPlugin())).build();
#SerializersFor([
Device])
final Serializers serializers = _$serializers;
Now I want to use the toJson() method:
final device = Device((d) => d
..code = "DEV11"
..name = "Some Tools"
..isActive = true);
print(device.toJson());
The output is:
["name","Some Tools","code","DEV11","isActive",true]
which is a List of objects, instead of Map<String, dynamic>. The generated serializer is:
Iterable<Object?> serialize(Serializers serializers, Device object,
{FullType specifiedType = FullType.unspecified})
...
which returns an Iterable of Object?.
How can I make built_value to generate a json with the standard format, i.e:
{"name":"Some Tools","code":"DEV11","isActive":true};
P.S: using the dart encoder,
print(json.encode(device));
results in:
"[\"name\",\"Some Tools\",\"code\",\"DEV11\",\"isActive\",true]"
I made a silly mistake. Should be:
#SerializersFor([
Device,
])
final Serializers serializers =
(_$serializers.toBuilder()..addPlugin(StandardJsonPlugin())).build();

How to Convert response from api using graphQL to Plain Old Dart Object in flutter?

I am trying to convert the Json to PODO(Dart Model Class) but getting following error :
[VERBOSE-2:dart_error.cc(16)] Unhandled exception:
FormatException: Unexpected character (at character 2)
Model Class
class FormModel {
final String id;
final String name;
FormModel({this.id, this.name});
factory FormModel.fromJSON(dynamic data) {
return FormModel(id: data["id"], name: data["name"]);
}
}
Creating object from JSON
FormModel form = FormModel.fromJSON(jsonData);

com.fasterxml.jackson.databind.JsonMappingException: Can not instantiate value of type [simple type, class java.time.LocalDateTime] from String

I have Java8 LocalDateTime in my Jax-RS REST APIs. My webapp is deployed in wildfly10. When i make a POST call(which includes LocalDateTime as parameter) i get following exception;
Caused by: com.fasterxml.jackson.databind.JsonMappingException: Can not instantiate value of type [simple type, class java.time.LocalDateTime] from String value ('2016-06-02T00:08:25.605Z'); no single-String constructor/factory method
at [Source: io.undertow.servlet.spec.ServletInputStreamImpl#396d1714; line: 2, column: 3] (through reference chain: com.leightonobrien.core.model.base.Company["created"])
at com.fasterxml.jackson.databind.JsonMappingException.from(JsonMappingException.java:148)
at com.fasterxml.jackson.databind.DeserializationContext.mappingException(DeserializationContext.java:843)
at com.fasterxml.jackson.databind.deser.ValueInstantiator._createFromStringFallbacks(ValueInstantiator.java:277)
at com.fasterxml.jackson.databind.deser.std.StdValueInstantiator.createFromString(StdValueInstantiator.java:284)
at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.deserializeFromString(BeanDeserializerBase.java:1150)
at com.fasterxml.jackson.databind.deser.BeanDeserializer._deserializeOther(BeanDeserializer.java:153)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:144)
at com.fasterxml.jackson.databind.deser.SettableBeanProperty.deserialize(SettableBeanProperty.java:523)
at com.fasterxml.jackson.databind.deser.impl.FieldProperty.deserializeAndSet(FieldProperty.java:101)
at com.fasterxml.jackson.databind.deser.impl.BeanPropertyMap.findDeserializeAndSet(BeanPropertyMap.java:285)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.vanillaDeserialize(BeanDeserializer.java:248)
Based on following guides ;
Wildfly throws "Unable to find a constructor that takes a String param or a valueOf() or fromString() method for javax.ws.rs.QueryParam" error
and
jaxrs could not find my custom (de)serializers for joda.money type
and
https://github.com/FasterXML/jackson-datatype-jsr310
I have written my provider and registered in the application path;
package com.test;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import javax.ws.rs.ext.ParamConverter;
import javax.ws.rs.ext.ParamConverterProvider;
import javax.ws.rs.ext.Provider;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider;
#Provider
public class LocalDateTimeConverterProvider extends JacksonJsonProvider implements ParamConverterProvider {
private final LocalDateTimeConverter converter = new LocalDateTimeConverter();
#Override
public <T> ParamConverter<T> getConverter(Class<T> rawType, Type genericType, Annotation[] annotations) {
if (!rawType.equals(LocalDateTime.class))
return null;
return (ParamConverter<T>) converter;
}
public class LocalDateTimeConverter implements ParamConverter<LocalDateTime> {
#Override
public LocalDateTime fromString(String value) {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
LocalDateTime dateTime = LocalDateTime.parse(value, formatter);
return dateTime;
}
#Override
public String toString(LocalDateTime value) {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
String formattedDateTime = value.format(formatter);
return formattedDateTime;
}
}
public LocalDateTimeConverterProvider() {
ObjectMapper mapper = new ObjectMapper();
mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
mapper.registerModule(new JavaTimeModule());
setMapper(mapper);
}
}
import javax.ws.rs.ApplicationPath;
#ApplicationPath("/rest")
public class RestApplication extends Application {
#Override
public Set<Class<?>> getClasses() {
HashSet<Class<?>> set = new HashSet<Class<?>>();
set.add(com.test.JsonMoneyProvider.class);
set.add(com.test.DurtaionConverterProvider.class);
set.add(com.test.LocalDateTimeConverterProvider.class);
set.add(com.test.MoneyConverterProvider.class);
...
I make POST call like;
curl -X POST --header 'Content-Type: application/json' -d '{
"created": "2016-06-02T00:08:25.605Z",
"updated": "2016-06-02T00:08:25.605Z",
"id": 0,
"code": "string",
"name": "string",
"abn": "string",
"addresses": [
{
"id": 0,
"address1": "string",
"address2": "string",
"city": "string",
"state": "string",
"postcode": "string",
"country": "string",
"gps": {
"latitude": {
"latitude": 0,
"value": 0
},
"longitude": {
"longitude": 0,
"value": 0
}' 'http://localhost:8080/test2dbwar/rest/Companys'
How can I overcome above issue? Now I'm clueless..I tried to put all stuff (try to avoid wildfly's jax-rs complex support issue, jackson serialization issue) and sort out the issue..
Any help?
The first question you linked to involves conversion of #XxxParam annotations. This is what the ParamConverterProvider is for. This is a completely different (de)serailization process from entity body (de)serialization.
For entity (de)serialization, MessageBodyReader/MessageBodyWriters are used. Jackson provides one such implementation, in its JacksonJsonProvider/JacksonJaxbJsonProvider, which you are currently using, whether you know it or not. So the configuration for the LocalDataTime needs to somehow be configured with that provider.
The only way to configure Jackson support for the LocalDateTime serialization, is through it's ObjectMapper. You can create a custom Json(De)Serializer, as mentioned in this post (Option two), or you can use the JSR310Module (that already has custom Json(De)Serializer for LocalDateTime), mentioned in this post.
To actually configure the JacksonJsonProvider/JacksonJaxbJsonProvider to use your configured ObjectMapper, you can use a ContextResolver, as mentioned in both of the previous links, or you can construct the JacksonJsonProvider with the ObjectMapper, and register that provider
ObjectMapper mapper = new ObjectMapper();
mapper..configurModuleOrSerializer
JacksonJsonProvider provider = new JacksonJsonProvider(mapper);
registerProvier(provider)
Personally I would just go with the ContextResolver, as mentioned in the links. The difference is that with the above code, the ObjectMapper is provided to the provider explicitly, while with the ContextResolver, during runtime, the provider will search the JAX-RS registry for a ContextResolver of type ObjectMapper, and then obtain it that way.
In my case, adding the following works for me.
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<version>2.8.9</version
</dependency>
and configure the ObjectMapper like this:
ObjectMapper mapper = new ObjectMapper().registerModule(new JavaTimeModule());