I have a JSON string that I need to map into a Model in C++. My current practice is define data type of the variable and get the value of JSON via key then assign to model one by one. Here is the code:
auto code = BaseModel::intValue(data, "code");
int BaseModel::intValue( const std::unordered_map<std::string, Value>& value, const std::string& key )
{
return (value.find( key ) != value.end() && BaseModel::isBasicType(value.at(key))) ? value.at(key).asInt() : 0;
}
Is there any easy way to convert JSON to model? Maybe I just need define a header file with the model‘s data type, then it can help me convert JSON to model object.Instead of creating a model object, assign value to the model object from JSON one by one. I know there is a way in Objective-C:
// JSON:
{
"uid":123456,
"name":"Harry",
"created":"1965-07-31T00:00:00+0000"
}
// Model:
#interface User : NSObject
#property UInt64 uid;
#property NSString *name;
#property NSDate *created;
#end
#implementation User
#end
// Convert json to model:
User *user = [User yy_modelWithJSON:json];
// Convert model to json:
NSDictionary *json = [user yy_modelToJSONObject];
I've had great success with the Nlohmann Json library, which allows you to define a single set of serialization and deserialization functions, and then freely convert from your chosen structure to json and back.
Related
I use Spatie's enums in my symfony project and I made a custom DBAL type for those objects. When I save an enum object to the database, I save it in a special string format. The conversion function in my EnumType looks like this:
public function convertToDatabaseValue($value, AbstractPlatform $platform)
{
if ($value === null) {
return null;
}
return get_class($value) . '::' . $value->getIndex() . '::' . $value->getValue();
}
So for example I have a transaction status enum that looks like this:
namespace App\Enum;
use Spatie\Enum\Enum;
/**
* #method static self failed()
* #method static self pending()
* #method static self completed()
*/
final class TransactionStatus extends Enum {}
And when I save it to the database it can turn into any one of these strings respectively:
App\Enum\TransactionStatus::0::failed
App\Enum\TransactionStatus::1::pending
App\Enum\TransactionStatus::2::completed
This helps my EnumType to know what enum to transform it back into. And the reason I use the index number in the string is because that helps with sorting.
Now this all works very well for fetching and saving my entities to the database. But when I try to use an enum in a where clause of a DQL statement, it doesn't work at all.
namespace App\Repository;
use App\Entity\Transaction;
use App\Enum\TransactionStatus;
use Symfony\Bridge\Doctrine\RegistryInterface;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
class TransactionRepository extends ServiceEntityRepository
{
public function __construct(RegistryInterface $registry)
{
parent::__construct($registry, Transaction::class);
}
public function findByStatus(TransactionStatus $status)
{
return $this->createQueryBuilder('t')
->andWhere('t.status = :status')
->setParameter('status', $status)
->getQuery()->getResult();
}
}
Because for some reason doctrine ignores my conversion function and just uses the __toString() function that's built into Spatie's enum. So doctrine is looking for the string "pending" instead of "App\Enum\TransactionStatus::1::pending".
How do I make sure that my enums always get converted correctly in a DQL where clause?
Okay I found a way to do it, even though it is super hacky. I just check from where the __toString() method is called and if it's called from Doctrine's DBAL then I use the DB format of the enum.
namespace App\Enum;
use Spatie\Enum\Enum as BaseEnum;
abstract class Enum extends BaseEnum
{
public function __toString(): string
{
if (debug_backtrace()[1]['class'] === 'PDOStatement') {
return get_class($this) . "::$this->index::$this->value";
}
return parent::__toString();
}
}
NodeEntity:
#NodeEntity(label = "User")
public class UserNode {
#GraphId
private Long _gid;
#Index(unique = true, primary = true)
#Convert(ObjectIdConverter.class)
private ObjectId id;
}
Converter:
public class ObjectIdConverter implements AttributeConverter<ObjectId, String>{
#Override
public String toGraphProperty(ObjectId value) {
return ObjectIdUtils.compressed(value);
}
#Override
public ObjectId toEntityAttribute(String value) {
return ObjectIdUtils.uncompress(value);
}
}
Repository:
public interface UserNodeRepository extends GraphRepository<UserNode> {
#Query("MATCH (user:User) WHERE user.id IN {0} RETURN user")
List<UserNode> findByIdIn(List<ObjectId> ids);
}
UserNodeRepository#findByIdIn is custom query. but the request parameter directly convert to json without using ObjectIdConverter.
Log:
o.n.o.drivers.bolt.request.BoltRequest : Request: MATCH (user:User) WHERE user.id IN {0} RETURN user with params {0=[{timestamp=1500442853, machineIdentifier=11302907, processIdentifier=11906, counter=4709865, time=1500442853000, date=1500442853000, timeSecond=1500442853}, {timestamp=1500445335, machineIdentifier=11302907, processIdentifier=11906, counter=4709946, time=1500445335000, date=1500445335000, timeSecond=1500445335}, {timestamp=1500447522, machineIdentifier=11302907, processIdentifier=11906, counter=4710014, time=1500447522000, date=1500447522000, timeSecond=1500447522}, {timestamp=1500448399, machineIdentifier=11302907, processIdentifier=11906, counter=4710092, time=1500448399000, date=1500448399000, timeSecond=1500448399}]}
Is this the SDN expectation or what concepts have I lost?
This is not possible for finders with custom #Query. There is no way for OGM to know that your parameter relates to a property with #Convert annotation (the method name is not used at all). To solve this convert to your property type manually instead and use that as a parameter:
#Query("MATCH (user:User) WHERE user.id IN {0} RETURN user")
List<UserNode> findByIdIn(List<String> ids);
However this works fine for derived finders - OGM knows the field, and the #Convert annotation with the right converter, from the method name.
Example with single parameter:
List<UserNode> findById(ObjectId id);
Unfortunately there seems to be a bug for the IN operator and a collection parameter with:
// doesn't work
List<UserNode> findByIdIn(List<ObjectId> ids);
I have created a jira issue.
I have clients passing in IDs like this: /v1/path?id=1,2,3
What I have and want
I have a resource class for Dropwizard/Jersey.
I'd like to show up the query-parameter id=1,2,3 as a List parameter in my resource's GET method
// Resource class
public List<Something> getFilteredList(#QueryParam("id") List<String> ids) {
// filter the List<Something> based on a list of ids
}
Right now, the ids list contains 1 string which is "1,2,3".
What I tried
I tried a filter but the query parameters given by Jersey's
ContainerRequestContext.getUriInfo().getQueryParameters()
is immutable.
Questions
I would like to apply a filter and change any comma separated query parameters into multi-valued parameters so that the resource method gets a list instead.
Is there a way to change the existing query parameters using a Jersey filter?
What's a good way to solve this problem?
The best way I can think of is to just create a wrapper class for the list. This makes it easier to take advantage of the specified functionality of Jersey. You can see what I mean at Passing custom type query parameter.
For example
public class IdFilter {
private List<String> ids = new ArrayList<>();
public List<String> getIds() { return ids; }
public static IdFilter valueOf(String param) {
IdFilter filter = new IdFilter();
for (String id: param.split(",") {
filter.getIds().add(id);
}
}
}
getFilteredList(#QueryParam("id") IdFilter ids) {
We don't need to do anything else. Just having the static valueOf is enough for Jersey to know how to parse the query string.
3 ways to solve it:
use the generic context-parameter UriInfo , which is not very expressive
add an explicit custom type that can parse a comma-separated list
stay with #QueryParam List<String> requiring a concatenated query like ?id=1&id=2&id=3 given as URI
I would prefer the second as most-expressive, like answered already by Paul. This way you can concisely pass a single CSV like ?id=1,2,3,3 and also use a Set to ensure unique ID values, e.g. resulting in only [1, 2, 3].
Generic context-param UriInfo
One way would be to use a generic parameter #Context UriInfo to get the list in the method's body:
public List<Something> getFilteredList( #Context UriInfo uriInfo ) {
List<String> idList = uriInfo.getQueryParameters().get("id"); // before was #QueryParam("id")
System.out.println("idList: " + idList);
// filter a given list by ids
var somethingFiltered = getSomethingList().stream()
.filter(s -> idList.contains(s.getId()))
.collect(toList());
return Response.status(Status.OK).entity(somethingFiltered).build();
}
See the tutorial in Java Vogue(2015): QueryParam Annotation In Jersey -
Custom type with static valueOf(String) factory-method
The other way is to design a custom type which can be constructed using a String:
class IdSet {
Set<String> values;
// a factory method, can also be named valueOf
public static IdSet fromString(String commaSeparated) {
return new HashSet( Arrays.asList( commaSeparated.split(",") ) );
}
}
public List<Something> getFilteredList(#QueryParam("id") IdSet ids) {
System.out.println("ids (Set): " + ids.values);
// filter a given list by ids
var somethingFiltered = getSomethingList().stream()
.filter(s -> ids.values.contains(s.getId()))
.collect(toList());
return Response.status(Status.OK).entity(somethingFiltered).build();
}
See Jersey's JavaDocs for #QueryParam:
The type T of the annotated parameter, field or property must either:
Be a primitive type
Have a constructor that accepts a single String argument
Have a static method named valueOf or fromString that accepts a single String argument (see, for example, Integer.valueOf(String))
Have a registered implementation of ParamConverterProvider that returns a ParamConverter instance capable of a "from string" conversion for the type.
Be List<T>, Set<T> or SortedSet<T>, where T satisfies 2, 3 or 4 above. The resulting collection is read-only.
Use a collection interface with multiple key-value pairs
When the calling client uses following URI pattern: /something?id=1&id=2&id=3 then JAX-RS can deserialize them to a single parameter of List<String> id having given multiple elements:
public List<Something> getFilteredList(#QueryParam("id") List<String> ids) {
System.out.println("ids : "+ids);
// filter a given list by ids
var somethingFiltered = getSomethingList().stream()
.filter(s -> ids.contains(s.getId()))
.collect(toList());
return Response.status(Status.OK).entity(somethingFiltered).build();
}
See Mkyong: JAX-RS #QueryParam example where explained the multiple occurrences of orderBy in the GET query:
#QueryParam will convert the query parameter “orderBy=age&orderBy=name” into java.util.List automatically.
See also
Handling Multiple Query Parameters in Jersey
Deserializing List<Map<String, String>> QueryParam in jersey 1
Jersey, #QueryParam List<String>
In connecting my C++ data model to my Cocoa table column UI, I'm wondering if I can provide the a C++ class instance at the id (identifier) to initWithIdentifier
// what magic needs to occur to create an id from a CPP class?
id someIDMadeFromAClassInstance = a_ptr_to_a_cpp_class_instance;
NSTableColumn *col = [[NSTableColumn alloc] initWithIdentifier:someIDMadeFromAClassInstance"];
The whole point of this is so that when the NSTable's datasource method objectValueForTableColumn gets called, I can retrieve the id and somehow convert if back to a valid C++ class instance:
id columnIdentifer = [aTableColumn identifier];
MyCPPClass* pAValidClass = [someMagicOnTheID columnIdentifer];
pAValidClass->AClassMethod();
I'm guessing there's a more traditional method of doing this, but I wanted to simplify the connection between the Cocoa UI and a pure C++ model.
A C++ object pointer cannot be stored in an id type variable. In Objective-C, id is a pointer to an Objective-C object of unknown type. Since a C++ object is not an Objective-C object, it is not safe to store the pointer to a C++ object in an id.
The solution is to add a property to your Objective-C class that will store a C++ object pointer. If you must use an id, you could make an Objective-C class that wraps a property that stores the C++ object pointer, for example:
#interface MyCPPClassWrapper : NSObject
#property (nonatomic, assign) MyCPPClass *myCPPClass;
#end
// ...
MyCPPClassWrapper *wrapper = [[MyCPPClassWrapper alloc] initWithMyCPPClass:myCPPClass];
// Hand wrapper off to your NSTable
Take a look at NSValue as well. It provides a storage mechanism for C-style pointers. For NSValue, you could do something like this:
NSValue *someIDMadeFromAClassInstance = [NSValue valueWithPointer:a_ptr_to_a_cpp_class_instance];
NSTableColumn *col = [[NSTableColumn alloc] initWithIdentifier:someIDMadeFromAClassInstance"];
// ...
NSValue *columnIdentifer = (NSValue *)[aTableColumn identifier];
MyCPPClass* pAValidClass = (MyCPPClass *)[columnIdentifer pointerValue];
pAValidClass->AClassMethod();
I have a custom template ~/Views/Shared/EditorTemplate/String.cshtml and it seems to be causing the Exception:
The model item passed into the dictionary is of type 'Proj.Models.EnumType', but this dictionary requires a model item of type 'System.String'.
It seems to only happen to Enums. It also goes away if I remove the template. The template doesn't seem to cause it, I don't think it's even making it that far. I can put ANYTHING in there and the exception is the same.
So... can I not use an #Html.EditorFor with a model with an enum if I have a custom template?
Some context:
Model:
namespace Proj.Models
{
public enum EnumType
{
A = 0,
B = 1,
C = 2,
}
public class Mod
{
[Required]
public String Name;
[Required]
public EnumType Letter;
}
}
View:
#model Proj.Models.Mod
#Html.EditorFor(m=>m) // Exception happens here
Here is what I found to work for me.
In your template, make sure you declare your model as nullable enum type. Then, in code, check to see if it has a value and based on that do appropriate formatting.
#inherits System.Web.Mvc.WebViewPage<Proj.Models.EnumType?>
#{
Proj.Models.EnumType v = Model.HasValue ? Model.Value : {*some-default-value*};
#Html.{*Your-formatter-here*};
}