Datatransformer and json validation in symfony2 form - json

I'm building a Rest API and I receive a json_encoded string from the clients.
I want this string to be decoded before saving my entity, because it's going into a jsonb field in PostgreSQL.
The behavior I want is :
Validate that the string is valid json, if not, add a violation in the form via a custom validator
Automatically decode the string and set the json object in the entity property
I've tried two different strategies
In the entity setMetadata($value) method, if $value is a string, I decode it
I created a DataTransformer that json_decode the value received in the form
But both these solutions don't work because the custom validator I created is called after, and it calls directly $lesson->getMetadata(). Since the value has already been decoded (either in the setMetadata() method or in the DataTransformer, the validator receive either a json object or null. So I can't add a violation to the form, since I have no way to know if the value received was actually null, or if the string was malformed.
Here is the lesson entity:
class Lesson extends BaseContent
{
[…]
/**
* #var jsonb
*
* #ORM\Column(name="metadata", type="jsonb", nullable=true)
* #KreactiveAssert\Json
*/
private $metadata;
[…]
}
Here is the custom validator:
class JsonValidator extends ConstraintValidator
{
public function validate($value, Constraint $constraint)
{
if ($value && !json_decode($value)) {
$this->context->addViolation($constraint->message, array('%string%' => $value));
}
}
}
And here is the DataTransformer:
class StringToJsonTransformer implements DataTransformerInterface
{
/**
* Transform a json object to a string
* #param Json|null $json
* #return String
*/
public function transform($json)
{
if (null === $json) {
return "";
}
return json_encode($json);
}
/**
* Transform a string to a json object
* #param String $string
* #return Object
*/
public function reverseTransform($string)
{
if (!$string) {
return null;
}
throw new TransformationFailedException('error transforming');
return json_decode($string);
}
}
Is there any way I can validate the input data in the form, and then set the metadata as a json object?

I've found this (I don't know how come I didn't find it earlier):
Combine constraints and data transformers
I'm going to make an ugly workaround as suggested, even though I don't like that solution.
<?php
class StringToJsonTransformer implements DataTransformerInterface
{
/**
* Transform a string to a json object
* #param String $string
* #return Object
*/
public function reverseTransform($string)
{
if (!$string) {
return null;
}
/*
* UGLY WORKAROUND
* we return -1 if the json_decode fail
* so the validator can add a violation in the form telling
* the json string was not valid
* If we don't do this, the validator will receive either
* null or a json object. In case of null, there is no way to
* tell if the client sent null, or if the decoding failed
*/
$value = json_decode($string);
return $value ? $value : -1;
}
}
I'm still not sure if I'm going to return -1 or something else. In the custom validator, I get an error if I try to compare a jsonObject with -1 (which is normal).

Related

Unable to get jsonDecode in magento 2

Magento 1.x has its own JSON decode functions:
Mage::helper('core')->jsonDecode($array);
So how to use JSON Decode in Magento 2.
In the current version of Magento2 (2.2.3) the \Magento\Framework\Json\Helper\Data class is deprecated and may no longer be used in future versions. Therefore it is highly recommended to use the \Magento\Framework\Serialize\Serializer\Json class instead to decode a json string or encode an array to a json string.
/**
*
* #var \Magento\Framework\Serialize\Serializer\Json
*/
protected $_jsonSerializer;
public function __construct(
...
\Magento\Framework\Serialize\Serializer\Json $jsonSerializer,
...
) {
...
$this->_jsonSerializer = $jsonSerializer;
...
}
public function decodeJsonString($jsonString)
{
return $this->_jsonSerializer->unserialize($jsonString);
}
public function encodeArray($array)
{
return $this->_jsonSerializer->serialize($array);
}
Try below code with magento 2 for json decode
$objectManager = \Magento\Framework\App\ObjectManager::getInstance();
$jsonManager = $objectManager->get('\Magento\Framework\Json\Decoder');
return $jsonManager->decode($data);
According to This Answer
Method 1:
echo $this->helper(\Magento\Framework\Json\Helper\Data::class)->jsonDecode($array);
Or
$jsonHelper = $this->helper('Magento\Framework\Json\Helper\Data');
echo $jsonHelper->jsonDecode($array);
Method 2:
/**
* Constructor.
*
* #param \Magento\Framework\Json\Helper\Data $jsonHelper
*/
public function __construct(\Magento\Framework\Json\Helper\Data $jsonHelper)
{
$this->jsonHelper = $jsonHelper;
}
/**
* #param array $dataToDecode
* #return string
*/
public function decodeSomething(array $dataToDecode)
{
$decodedData= $this->jsonHelper->jsonDecode($dataToDecode);
return $decodedData;
}

MongoDB date format in strict mode

Using MongoDB java driver, applying toJson() method on Document will get a JSON representation of this document with JsonMode set to STRICT.
The following epoch format is used for dates: { "$date" : "dateAsMilliseconds" }
Using mongoexport, we get an ISO-8601 format.
Seen in official doc ( https://docs.mongodb.com/manual/reference/mongodb-extended-json/ ) :
In Strict mode, date is an ISO-8601 date format with a mandatory time zone field following the template YYYY-MM-DDTHH:mm:ss.mmm<+/-Offset>.
The MongoDB JSON parser currently does not support loading ISO-8601 strings representing dates prior to the Unix epoch. When formatting pre-epoch dates and dates past what your system’s time_t type can hold, the following format is used:
{ "$date" : { "$numberLong" : "dateAsMilliseconds" } }
I would appreciate if someone can explain me why there is no common format used between MongoDB java driver, mongoexport tool and official docs?
Thanks.
Obviously there is NO good reason for the Java driver to deviate from the official specification. The only exception are for those dates which cannot be expressed in the ISO8601 format (like B.C. dates...)
As a work around I have extended the JsonWriter class and provided two toJson static methods as an example of how to use it:
package whatever.package.you.like;
import java.io.IOException;
import java.io.StringWriter;
import java.io.Writer;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.TimeZone;
import org.bson.BSONException;
import org.bson.BsonContextType;
import org.bson.BsonDocument;
import org.bson.codecs.BsonDocumentCodec;
import org.bson.codecs.EncoderContext;
import org.bson.conversions.Bson;
import org.bson.json.JsonMode;
import org.bson.json.JsonWriter;
import org.bson.json.JsonWriterSettings;
import com.mongodb.MongoClient;
/**
* A {#link JsonWriter} extension that conforms to the "strict" JSON format
* specified by MongoDB for data/time values.
*
* The {#link JsonWriter} class provided in the MongoDB Java driver (version
* 3.2.2) does not conform to official MongoDB specification for strict mode
* JSON (see https://docs.mongodb.com/manual/reference/mongodb-extended-json/).
* This is specifically a problem with the date/time values which get filled
* with a milliseconds value (i.e. {$date: 309249234098}) instead of the ISO8601
* date/time (i.e. {$date: "2016-07-14T08:44:23.234Z"}) value which the
* specification calls for. This extension of {#link JsonWriter} conforms to the
* MongoDb specification in this regard.
*/
public class ConformingJsonWriter extends JsonWriter {
private final JsonWriterSettings settings;
private final Writer writer;
private boolean writingIndentedDateTime = false;
/**
* Creates a new instance which uses {#code writer} to write JSON to.
*
* #param writer
* the writer to write JSON to.
*/
public ConformingJsonWriter(final Writer writer) {
this(writer, new JsonWriterSettings());
}
/**
* Creates a new instance which uses {#code writer} to write JSON to and uses
* the given settings.
*
* #param writer
* the writer to write JSON to.
* #param settings
* the settings to apply to this writer.
*/
public ConformingJsonWriter(final Writer writer,
final JsonWriterSettings settings) {
super(writer, settings);
this.writer = writer;
this.settings = settings;
setContext(new Context(null, BsonContextType.TOP_LEVEL, ""));
}
private void writeIndentation(int skip) throws IOException {
for (Context context = getContext()
.getParentContext(); context != null; context = context
.getParentContext()) {
if (skip-- <= 0) {
writer.write(settings.getIndentCharacters());
}
}
}
private static String millisToIso8601(long millis) throws IOException {
SimpleDateFormat dateFormat = new SimpleDateFormat(
"yyyy-MM-dd\'T\'HH:mm:ss.SSS\'Z\'");
dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
return dateFormat.format(new Date(millis));
}
#Override
protected void doWriteDateTime(final long value) {
if ((settings.getOutputMode() == JsonMode.STRICT)
&& (value >= -59014396800000L && value <= 253399536000000L)) {
try {
writeStartDocument();
if (settings.isIndent()) {
writingIndentedDateTime = true;
writer.write(settings.getNewLineCharacters());
writeIndentation(0);
} else {
writer.write(" ");
}
writer.write("\"$date\" : ");
writer.write("\"");
writer.write(millisToIso8601(value));
writer.write("\"");
writeEndDocument();
writingIndentedDateTime = false;
} catch (IOException e) {
throw new BSONException("Wrapping IOException", e);
}
} else {
super.doWriteDateTime(value);
}
}
#Override
protected void doWriteEndDocument() {
if (writingIndentedDateTime) {
try {
writer.write(settings.getNewLineCharacters());
writeIndentation(1);
writer.write("}");
if (getContext()
.getContextType() == BsonContextType.SCOPE_DOCUMENT) {
setContext(getContext().getParentContext());
writeEndDocument();
} else {
setContext(getContext().getParentContext());
}
} catch (IOException e) {
throw new BSONException("Wrapping IOException", e);
}
} else {
super.doWriteEndDocument();
}
}
/**
* Take a {#link Bson} instance and convert it to "strict" JSON
* representation with no indentation (read, "NOT pretty printed").
*
* #param bson
* The {#link Bson} instance to convert
* #return The JSON representation.
*/
public static String toJson(Bson bson) {
return toJson(bson, new JsonWriterSettings());
}
/**
* Take a {#link Bson} instance and convert it to JSON representation.
*
* #param bson
* The {#link Bson} instance to convert
* #param writerSettings
* {#link JsonWriterSettings} that specify details about how the
* JSON output should look.
* #return The JSON representation.
*/
public static String toJson(Bson bson,
final JsonWriterSettings writerSettings) {
BsonDocumentCodec encoder = new BsonDocumentCodec();
ConformingJsonWriter writer = new ConformingJsonWriter(new StringWriter(),
writerSettings);
encoder.encode(writer,
bson.toBsonDocument(BsonDocument.class,
MongoClient.getDefaultCodecRegistry()),
EncoderContext.builder().isEncodingCollectibleDocument(true)
.build());
return writer.getWriter().toString();
}
}

Json.NET to GSON, probably newbie issue

I have an app in .NET that I want to be able to talk to an Android app. Some research suggested not using XML, my usual choice, but instead using JSON which I've never touched. I'm using Json.NET and GSON.
I have a simple class in .NET;
public enum eCommandType
{
UNKNOWN,
PING,
GET_TIMINGS,
GET_EVENT_ID,
NEW_EVENT,
LOAD_PEOPLE,
LOAD_TAGS,
SET_LOCATION
}
public class CommandBase
{
[JsonConverter(typeof(StringEnumConverter))]
public eCommandType CommandType { get; set; }
}
I generate a schema using
//Encode enums as strings, not integers.
JSchemaGenerator stringEnumGenerator = new JSchemaGenerator();
stringEnumGenerator.GenerationProviders.Add
(new StringEnumGenerationProvider());
JSchema schema = stringEnumGenerator.Generate(typeof(CommandBase));
string s = schema.ToString();
That schema is
{
"type": "object",
"properties": {
"CommandType": {
"type": "string",
"enum": [
"UNKNOWN",
"PING",
"GET_TIMINGS",
"GET_EVENT_ID",
"NEW_EVENT",
"LOAD_PEOPLE",
"LOAD_TAGS",
"SET_LOCATION"
]
}
},
"required": [
"CommandType"
]
}
I then go to jsonschema2pojo and create a POJO, selecting "Gson" as the annotation style since it seems the thing to do. I press Preview and get a class that doesn't contain those enum values;
#Generated("org.jsonschema2pojo")
public class CommandType {
#Expose
private String type;
#SerializedName("enum")
#Expose
private List<String> _enum = new ArrayList<String>();
/**
*
* #return
* The type
*/
public String getType() {
return type;
}
/**
*
* #param type
* The type
*/
public void setType(String type) {
this.type = type;
}
/**
*
* #return
* The _enum
*/
public List<String> getEnum() {
return _enum;
}
/**
*
* #param _enum
* The enum
*/
public void setEnum(List<String> _enum) {
this._enum = _enum;
}
}
Unsurprisingly, if I go on to try and deserialize something using this in an Android app, I get an object that just has default values for the fields. Since there's obvious problems at this stage I've not included details of the Android side.
Aside from the missing enum values, there's no property in the POJO called "CommandType." The class itself has taken that name, the original name being lost in schema generation.
If I try the generated schema against jsonschemavalidator I get the error
Found 1 error(s)
Message: Required properties are missing from object: CommandType.
Schema path: #/required
So it seems I'm using Json.NET wrongly. However my usage seems pretty simple; I want enum strings rather than an int to make the eventual protocol robust as things change, and I want a type indication so I can deserialise to the correct POJO on the Android side, since I'll be having several different classes for a multitype query/response protocol. Is there something obviously wrong in my attempt to do this?
Select JSON Schema rather than JSON as your Source type at http://www.jsonschema2pojo.org/
Results:
#Generated("org.jsonschema2pojo")
public static enum CommandType {
UNKNOWN("UNKNOWN"),
PING("PING"),
GET_TIMINGS("GET_TIMINGS"),
GET_EVENT_ID("GET_EVENT_ID"),
NEW_EVENT("NEW_EVENT"),
LOAD_PEOPLE("LOAD_PEOPLE"),
LOAD_TAGS("LOAD_TAGS"),
SET_LOCATION("SET_LOCATION");
private final String value;
...etc

What is the difference between JSONObject accumulate and put?

I have been dabbling with JSON and I see that in the documentation (JAVA), JSONObject's put() and accumulate() pretty much do the same thing?
What is that about?
I saw the Java Source Code for JSONObject and the difference between accumulate and put is that with accumulate(String key,Object Value), if there exists some value for "key" then the Object is checked for being an array, if it is an array then the "value" is added to the array else an array is created for this key.
In put, however, the key if it exists, it's value is replaced by the value - "value"
Here is the source of JSONObject accumulate(String key, Object Value)
/**
* Appends {#code value} to the array already mapped to {#code name}. If
* this object has no mapping for {#code name}, this inserts a new mapping.
* If the mapping exists but its value is not an array, the existing
* and new values are inserted in order into a new array which is itself
* mapped to {#code name}. In aggregate, this allows values to be added to a
* mapping one at a time.
*
* <p> Note that {#code append(String, Object)} provides better semantics.
* In particular, the mapping for {#code name} will <b>always</b> be a
* {#link JSONArray}. Using {#code accumulate} will result in either a
* {#link JSONArray} or a mapping whose type is the type of {#code value}
* depending on the number of calls to it.
*
* #param value a {#link JSONObject}, {#link JSONArray}, String, Boolean,
* Integer, Long, Double, {#link #NULL} or null. May not be {#link
* Double#isNaN() NaNs} or {#link Double#isInfinite() infinities}.
*/
public JSONObject accumulate(String name, Object value) throws JSONException {
Object current = nameValuePairs.get(checkName(name));
if (current == null) {
return put(name, value);
}
if (current instanceof JSONArray) {
JSONArray array = (JSONArray) current;
array.checkedPut(value);
} else {
JSONArray array = new JSONArray();
array.checkedPut(current);
array.checkedPut(value);
nameValuePairs.put(name, array);
}
return this;
}
And here is the code for JSONObject put (String key, Object value)
/**
* Maps {#code name} to {#code value}, clobbering any existing name/value
* mapping with the same name.
*
* #return this object.
*/
public JSONObject put(String name, boolean value) throws JSONException {
nameValuePairs.put(checkName(name), value);
return this;
}

Zend Framework 1.9.2+ Zend_Rest_Route Examples

With the introduction of Zend_Rest_Route in Zend Framework 1.9 (and its update in 1.9.2) we now have a standardized RESTful solution for routing requests. As of August 2009 there are no examples of its usage, only the basic documentation found in the reference guide.
While it is perhaps far more simple than I assume, I was hoping those more competent than I might provide some examples illustrating the use of the Zend_Rest_Controller in a scenario where:
Some controllers (such as indexController.php) operate normally
Others operate as rest-based services (returning json)
It appears the JSON Action Helper now fully automates and optimizes the json response to a request, making its use along with Zend_Rest_Route an ideal combination.
Appears it was rather simple. I've put together a Restful Controller template using the Zend_Rest_Controller Abstract. Simply replace the no_results return values with a native php object containing the data you want returned. Comments welcome.
<?php
/**
* Restful Controller
*
* #copyright Copyright (c) 2009 ? (http://www.?.com)
*/
class RestfulController extends Zend_Rest_Controller
{
public function init()
{
$config = Zend_Registry::get('config');
$this->db = Zend_Db::factory($config->resources->db);
$this->no_results = array('status' => 'NO_RESULTS');
}
/**
* List
*
* The index action handles index/list requests; it responds with a
* list of the requested resources.
*
* #return json
*/
public function indexAction()
{
// do some processing...
// Send the JSON response:
$this->_helper->json($this->no_results);
}
// 1.9.2 fix
public function listAction() { return $this->_forward('index'); }
/**
* View
*
* The get action handles GET requests and receives an 'id' parameter; it
* responds with the server resource state of the resource identified
* by the 'id' value.
*
* #param integer $id
* #return json
*/
public function getAction()
{
$id = $this->_getParam('id', 0);
// do some processing...
// Send the JSON response:
$this->_helper->json($this->no_results);
}
/**
* Create
*
* The post action handles POST requests; it accepts and digests a
* POSTed resource representation and persists the resource state.
*
* #param integer $id
* #return json
*/
public function postAction()
{
$id = $this->_getParam('id', 0);
$my = $this->_getAllParams();
// do some processing...
// Send the JSON response:
$this->_helper->json($this->no_results);
}
/**
* Update
*
* The put action handles PUT requests and receives an 'id' parameter; it
* updates the server resource state of the resource identified by
* the 'id' value.
*
* #param integer $id
* #return json
*/
public function putAction()
{
$id = $this->_getParam('id', 0);
$my = $this->_getAllParams();
// do some processing...
// Send the JSON response:
$this->_helper->json($this->no_results);
}
/**
* Delete
*
* The delete action handles DELETE requests and receives an 'id'
* parameter; it updates the server resource state of the resource
* identified by the 'id' value.
*
* #param integer $id
* #return json
*/
public function deleteAction()
{
$id = $this->_getParam('id', 0);
// do some processing...
// Send the JSON response:
$this->_helper->json($this->no_results);
}
}
great post, but I would have thought the Zend_Rest_Controller would route the request to the right action with respect to the HTTP method used. It'd be neat if a POST request to http://<app URL>/Restful would automatically _forward to postAction for example.
I'll go ahead and provide another strategy below, but maybe I'm missing the point behind Zend_Rest_Controller ... please comment.
My strategy:
class RestfulController extends Zend_Rest_Controller
{
public function init()
{
$this->_helper->viewRenderer->setNoRender();
$this->_helper->layout->disableLayout();
}
public function indexAction()
{
if($this->getRequest()->getMethod() === 'POST')
{return $this->_forward('post');}
if($this->getRequest()->getMethod() === 'GET')
{return $this->_forward('get');}
if($this->getRequest()->getMethod() === 'PUT')
{return $this->_forward('put');}
if($this->getRequest()->getMethod() === 'DELETE')
{return $this->_forward('delete');}
$this->_helper->json($listMyCustomObjects);
}
// 1.9.2 fix
public function listAction() { return $this->_forward('index'); }
[the rest of the code with action functions]