mirror of
https://github.com/enso-org/enso.git
synced 2024-11-22 22:10:15 +03:00
Add missing API for Jackson_Object (#9591)
`Jackson_Object` supported parsing but not creating JSON from text. With this change, `Jackson_Object` is on par with `JS_Object` API and replaces the latter. The most visible differences come from more detailed parsing exception's messages. Had to add some special cases for corner cases like `NaN` or infinity. Closes #9473.
This commit is contained in:
parent
c37fffe33d
commit
a83c75f8ec
@ -1,226 +0,0 @@
|
||||
import project.Any.Any
|
||||
import project.Data.Array_Proxy.Array_Proxy
|
||||
import project.Data.Json.Invalid_JSON
|
||||
import project.Data.Json.JS_Object
|
||||
import project.Data.Numbers.Integer
|
||||
import project.Data.Numbers.Number
|
||||
import project.Data.Pair.Pair
|
||||
import project.Data.Text.Text
|
||||
import project.Data.Vector.Vector
|
||||
import project.Error.Error
|
||||
import project.Errors.No_Such_Key.No_Such_Key
|
||||
import project.Metadata.Display
|
||||
import project.Metadata.Widget
|
||||
import project.Nothing.Nothing
|
||||
import project.Panic.Panic
|
||||
from project.Data.Boolean import Boolean, False, True
|
||||
from project.Data.Json.Extensions import all
|
||||
from project.Data.Ordering import all
|
||||
from project.Data.Text.Extensions import all
|
||||
from project.Metadata.Choice import Option
|
||||
from project.Metadata.Widget import Single_Choice
|
||||
|
||||
polyglot java import com.fasterxml.jackson.core.JsonProcessingException
|
||||
polyglot java import com.fasterxml.jackson.databind.JsonNode
|
||||
polyglot java import com.fasterxml.jackson.databind.node.ArrayNode
|
||||
polyglot java import com.fasterxml.jackson.databind.node.JsonNodeType
|
||||
polyglot java import com.fasterxml.jackson.databind.node.ObjectNode
|
||||
polyglot java import com.fasterxml.jackson.databind.ObjectMapper
|
||||
|
||||
## PRIVATE
|
||||
Jackson-based JSON Parser
|
||||
type Java_Json
|
||||
## ALIAS from text
|
||||
GROUP Conversions
|
||||
ICON convert
|
||||
|
||||
Parse a Text value into a `Jackson_Object` or an Enso primitive value
|
||||
(like `Text`, `Number`, `Boolean`, `Nothing`), or a `Vector` of values.
|
||||
parse : Text -> Nothing | Boolean | Number | Text | Vector | Jackson_Object
|
||||
parse text:Text =
|
||||
error_handler js_exception =
|
||||
Error.throw (Invalid_JSON.Error js_exception.payload.message)
|
||||
|
||||
Panic.catch JsonProcessingException handler=error_handler <|
|
||||
node = ObjectMapper.new.readTree text
|
||||
read_json_node node
|
||||
|
||||
## PRIVATE
|
||||
Read a JsonNode to an Enso type
|
||||
read_json_node : JsonNode -> Nothing | Boolean | Number | Text | Vector | Jackson_Object
|
||||
read_json_node node = case node.getNodeType of
|
||||
JsonNodeType.NULL -> Nothing
|
||||
JsonNodeType.BOOLEAN -> node.asBoolean
|
||||
JsonNodeType.STRING -> node.asText
|
||||
JsonNodeType.NUMBER ->
|
||||
if node.isFloatingPointNumber then node.asDouble else node.asLong
|
||||
JsonNodeType.ARRAY -> read_json_array node
|
||||
JsonNodeType.OBJECT -> Jackson_Object.new node
|
||||
|
||||
## PRIVATE
|
||||
Read a JsonNode to a Vector
|
||||
read_json_array : ArrayNode -> Vector
|
||||
read_json_array node =
|
||||
proxy = Array_Proxy.new node.size i-> (read_json_node (node.get i))
|
||||
Vector.from_polyglot_array proxy
|
||||
|
||||
## PRIVATE
|
||||
type Jackson_Object
|
||||
## PRIVATE
|
||||
Creates a new `Jackson_Object` from an `ObjectNode`.
|
||||
new : ObjectNode -> Jackson_Object
|
||||
new object_node =
|
||||
make_field_names object =
|
||||
name_iterator = object.fieldNames
|
||||
builder = Vector.new_builder object.size
|
||||
loop iterator builder =
|
||||
if iterator.hasNext.not then builder.to_vector else
|
||||
builder.append iterator.next
|
||||
@Tail_Call loop iterator builder
|
||||
loop name_iterator builder
|
||||
Jackson_Object.Value object_node (make_field_names object_node)
|
||||
|
||||
## PRIVATE
|
||||
Value object_node ~field_array
|
||||
|
||||
## GROUP Logical
|
||||
ICON preparation
|
||||
Returns True iff the objects contains the given `key`.
|
||||
contains_key : Text -> Boolean
|
||||
contains_key self key:Text = self.object_node.has key
|
||||
|
||||
## ICON select_row
|
||||
Get a value for a key of the object, or a default value if that key is not present.
|
||||
|
||||
Arguments:
|
||||
- key: The key to get.
|
||||
- if_missing: The value to return if the key is not found.
|
||||
@key make_field_name_selector
|
||||
get : Text -> Any -> Nothing | Boolean | Number | Text | Vector | Jackson_Object
|
||||
get self key:Text ~if_missing=Nothing =
|
||||
if self.contains_key key . not then if_missing else
|
||||
child = self.object_node.get key
|
||||
read_json_node child
|
||||
|
||||
## GROUP Selections
|
||||
ICON select_row
|
||||
Get a value for a key of the object.
|
||||
If the key is not found, throws a `No_Such_Key` error.
|
||||
|
||||
Arguments:
|
||||
- key: The key to get.
|
||||
@key make_field_name_selector
|
||||
at : Text -> Jackson_Object | Boolean | Number | Nothing | Text | Vector ! No_Such_Key
|
||||
at self key:Text = self.get key (Error.throw (No_Such_Key.Error self key))
|
||||
|
||||
## GROUP Metadata
|
||||
ICON metadata
|
||||
Get the keys of the object.
|
||||
field_names : Vector
|
||||
field_names self = self.field_array
|
||||
|
||||
## ICON dataframe_map_column
|
||||
Maps a function over each value in this object
|
||||
|
||||
Arguments:
|
||||
- function: The function to apply to each value in the map, taking a
|
||||
value and returning a value.
|
||||
map : (Any->Any) -> Vector
|
||||
map self function =
|
||||
kv_func = _ -> function
|
||||
self.map_with_key kv_func
|
||||
|
||||
## Maps a function over each field-value pair in the object.
|
||||
|
||||
Arguments:
|
||||
- function: The function to apply to each key and value in the map,
|
||||
taking a key and a value and returning a value.
|
||||
map_with_key : (Any -> Any -> Any) -> Vector
|
||||
map_with_key self function =
|
||||
self.field_names.map key->
|
||||
value = self.get key
|
||||
function key value
|
||||
|
||||
## GROUP Conversions
|
||||
ICON convert
|
||||
Convert the object to a Vector of Pairs.
|
||||
to_vector : Vector
|
||||
to_vector self =
|
||||
keys = self.field_array
|
||||
proxy = Array_Proxy.new keys.length (i-> [(keys.at i), (self.get (keys.at i))])
|
||||
Vector.from_polyglot_array proxy
|
||||
|
||||
## GROUP Metadata
|
||||
ICON metadata
|
||||
Gets the number of keys in the object.
|
||||
length : Number
|
||||
length self = self.object_node.size
|
||||
|
||||
## GROUP Logical
|
||||
ICON metadata
|
||||
Returns True iff the Map is empty, i.e., does not have any entries.
|
||||
is_empty : Boolean
|
||||
is_empty self = self.length == 0
|
||||
|
||||
## GROUP Logical
|
||||
ICON metadata
|
||||
Returns True iff the Map is not empty, i.e., has at least one entry.
|
||||
not_empty : Boolean
|
||||
not_empty self = self.is_empty.not
|
||||
|
||||
## PRIVATE
|
||||
Convert the object to a JS_Object.
|
||||
to_js_object : JS_Object
|
||||
to_js_object self =
|
||||
pairs = self.field_names.map name-> [name, self.at name . to_js_object]
|
||||
JS_Object.from_pairs pairs
|
||||
|
||||
## PRIVATE
|
||||
Convert to a Text.
|
||||
to_text : Text
|
||||
to_text self = self.to_json
|
||||
|
||||
## PRIVATE
|
||||
Convert JS_Object to a friendly string.
|
||||
to_display_text : Text
|
||||
to_display_text self =
|
||||
self.to_text.to_display_text
|
||||
|
||||
## PRIVATE
|
||||
Convert to a JSON representation.
|
||||
to_json : Text
|
||||
to_json self = self.object_node.toString
|
||||
|
||||
## PRIVATE
|
||||
Make a field name selector
|
||||
make_field_name_selector : Jackson_Object -> Display -> Widget
|
||||
make_field_name_selector js_object display=Display.Always =
|
||||
Single_Choice display=display values=(js_object.field_names.map n->(Option n n.pretty))
|
||||
|
||||
## ICON convert
|
||||
Extension for Text to allow use.
|
||||
Text.parse_fast_json : Nothing | Boolean | Number | Text | Vector | Jackson_Object
|
||||
Text.parse_fast_json self = Java_Json.parse self
|
||||
|
||||
## PRIVATE
|
||||
type Jackson_Object_Comparator
|
||||
## PRIVATE
|
||||
compare : Jackson_Object -> Jackson_Object -> (Ordering|Nothing)
|
||||
compare obj1 obj2 =
|
||||
obj1_keys = obj1.field_names
|
||||
obj2_keys = obj2.field_names
|
||||
same_values = obj1_keys.length == obj2_keys.length && obj1_keys.all key->
|
||||
(obj1.get key == obj2.at key).catch No_Such_Key _->False
|
||||
if same_values then Ordering.Equal else Nothing
|
||||
|
||||
## PRIVATE
|
||||
hash : Jackson_Object -> Integer
|
||||
hash obj =
|
||||
values_hashes = obj.field_names.map field_name->
|
||||
val = obj.get field_name
|
||||
Comparable.from val . hash val
|
||||
# Return sum, as we don't care about ordering of field names
|
||||
values_hashes.fold 0 (+)
|
||||
|
||||
## PRIVATE
|
||||
Comparable.from (_:Jackson_Object) = Jackson_Object_Comparator
|
@ -2,6 +2,7 @@ import project.Any.Any
|
||||
import project.Data.Array.Array
|
||||
import project.Data.Array_Proxy.Array_Proxy
|
||||
import project.Data.Map.Map
|
||||
import project.Data.Numbers.Float
|
||||
import project.Data.Numbers.Integer
|
||||
import project.Data.Numbers.Number
|
||||
import project.Data.Pair.Pair
|
||||
@ -29,6 +30,19 @@ from project.Data.Text.Extensions import all
|
||||
from project.Metadata.Choice import Option
|
||||
from project.Metadata.Widget import Single_Choice
|
||||
|
||||
polyglot java import com.fasterxml.jackson.core.JsonProcessingException
|
||||
polyglot java import com.fasterxml.jackson.databind.JsonNode
|
||||
polyglot java import com.fasterxml.jackson.databind.node.ArrayNode
|
||||
polyglot java import com.fasterxml.jackson.databind.node.BooleanNode
|
||||
polyglot java import com.fasterxml.jackson.databind.node.DoubleNode
|
||||
polyglot java import com.fasterxml.jackson.databind.node.JsonNodeFactory
|
||||
polyglot java import com.fasterxml.jackson.databind.node.JsonNodeType
|
||||
polyglot java import com.fasterxml.jackson.databind.node.LongNode
|
||||
polyglot java import com.fasterxml.jackson.databind.node.NullNode
|
||||
polyglot java import com.fasterxml.jackson.databind.node.ObjectNode
|
||||
polyglot java import com.fasterxml.jackson.databind.node.TextNode
|
||||
polyglot java import com.fasterxml.jackson.databind.ObjectMapper
|
||||
|
||||
## Methods for serializing from and to JSON.
|
||||
type Json
|
||||
## ALIAS from text
|
||||
@ -42,21 +56,24 @@ type Json
|
||||
Parse the text "[null, null, true, false]".
|
||||
|
||||
Json.parse "[null, null, true, false]"
|
||||
parse : Text -> JS_Object | Boolean | Number | Nothing | Text | Vector ! Invalid_JSON
|
||||
parse : Text -> Nothing | Boolean | Number | Text | Vector | Date | Date_Time | Time_Of_Day | JS_Object ! Invalid_JSON
|
||||
parse json =
|
||||
error_handler js_exception =
|
||||
Error.throw (Invalid_JSON.Error js_exception.payload.message)
|
||||
line = js_exception.payload.getLocation.getLineNr.to_text
|
||||
col = js_exception.payload.getLocation.getColumnNr.to_text
|
||||
pos = '[line: '+line+', column: '+col+']'
|
||||
Error.throw (Invalid_JSON.Error js_exception.payload.getOriginalMessage+' at position '+pos)
|
||||
|
||||
Panic.catch Any handler=error_handler <|
|
||||
parsed = json_parse json
|
||||
make_enso parsed
|
||||
Panic.catch JsonProcessingException handler=error_handler <|
|
||||
node = ObjectMapper.new.readTree json
|
||||
make_enso (read_json_node node)
|
||||
|
||||
## PRIVATE
|
||||
ADVANCED
|
||||
Serialize an Object to JSON.
|
||||
stringify : (JS_Object | Boolean | Number | Nothing | Text | Vector) -> Text
|
||||
stringify : Any -> Text
|
||||
stringify object =
|
||||
json_stringify (make_javascript object.to_js_object)
|
||||
to_json_node object . toString
|
||||
|
||||
## PRIVATE
|
||||
Convert a Vector of Keys and Values to JSON.
|
||||
@ -78,69 +95,91 @@ type Invalid_JSON
|
||||
to_display_text self =
|
||||
"Parse error in parsing JSON: " + self.message.to_text + "."
|
||||
|
||||
## A failure indicating the inability to marshall a `Json` object into the
|
||||
specified format.
|
||||
type Marshalling_Error
|
||||
## PRIVATE
|
||||
## PRIVATE
|
||||
Read a JsonNode to an Enso type
|
||||
read_json_node : JsonNode -> Nothing | Boolean | Number | Text | Vector | JS_Object
|
||||
read_json_node node = case node.getNodeType of
|
||||
JsonNodeType.NULL -> Nothing
|
||||
JsonNodeType.BOOLEAN -> node.asBoolean
|
||||
JsonNodeType.STRING -> node.asText
|
||||
JsonNodeType.NUMBER ->
|
||||
if node.isFloatingPointNumber then node.asDouble else node.asLong
|
||||
JsonNodeType.ARRAY -> read_json_array node
|
||||
JsonNodeType.OBJECT -> JS_Object.new node
|
||||
|
||||
The `json` object could not be converted into `format`, due to a type
|
||||
mismatch.
|
||||
## PRIVATE
|
||||
Read a JsonNode to a Vector
|
||||
read_json_array : ArrayNode -> Vector
|
||||
read_json_array node =
|
||||
proxy = Array_Proxy.new node.size i-> (read_json_node (node.get i))
|
||||
Vector.from_polyglot_array proxy
|
||||
|
||||
Arguments:
|
||||
- json: The JSON that could not be marshalled.
|
||||
- format: The type format that did not match.
|
||||
|
||||
This can occur e.g. when trying to reinterpret a number as a `Text`, etc.
|
||||
Type_Mismatch json format
|
||||
|
||||
## PRIVATE
|
||||
The `json` object could not be converted into `format`, due to a field
|
||||
missing in the `json` structure.
|
||||
|
||||
Arguments:
|
||||
- json: The json that had a missing field.
|
||||
- field: The field name that was missing.
|
||||
- format: The type format that diud not match.
|
||||
|
||||
This can occure when trying to reinterpret a JSON object into an atom,
|
||||
when the JSON does not contain all the fields required by the atom.
|
||||
Missing_Field json field format
|
||||
|
||||
## PRIVATE
|
||||
Convert the marshalling error into a human-readable format.
|
||||
to_display_text : Text
|
||||
to_display_text self = case self of
|
||||
Marshalling_Error.Type_Mismatch json format ->
|
||||
json_text = Meta.get_simple_type_name json
|
||||
format_text = Meta.get_simple_type_name format
|
||||
"Type mismatch error: the json with type `" + json_text + "` did not match the format `" + format_text + "`."
|
||||
Marshalling_Error.Missing_Field _ field _ ->
|
||||
"Missing field in Json: the field `" + field.to_text "` was missing in the json."
|
||||
## PRIVATE
|
||||
Convert a value to a JsonNode
|
||||
to_json_node : Any -> Any
|
||||
to_json_node value =
|
||||
case value of
|
||||
Nothing -> NullNode.getInstance
|
||||
b : Boolean -> BooleanNode.valueOf b
|
||||
n : Integer -> LongNode.valueOf(n)
|
||||
f : Float ->
|
||||
if f.is_nan || f.is_infinite then NullNode.getInstance else DoubleNode.valueOf(f)
|
||||
t : Text ->
|
||||
TextNode.valueOf(t)
|
||||
v : Vector ->
|
||||
n = ArrayNode.new JsonNodeFactory.instance
|
||||
v.each e-> n.add (to_json_node e)
|
||||
n
|
||||
a : Array ->
|
||||
n = ArrayNode.new JsonNodeFactory.instance
|
||||
a.each e-> n.add (to_json_node e)
|
||||
n
|
||||
j : JS_Object -> j.object_node
|
||||
other ->
|
||||
to_json_node other.to_js_object
|
||||
|
||||
## PRIVATE
|
||||
type JS_Object
|
||||
## PRIVATE
|
||||
Creates a JS_Object from a list of key-value pairs.
|
||||
Creates a new `JS_Object` from an `ObjectNode`.
|
||||
new : ObjectNode -> JS_Object
|
||||
new object_node =
|
||||
make_field_names object =
|
||||
name_iterator = object.fieldNames
|
||||
builder = Vector.new_builder object.size
|
||||
loop iterator builder =
|
||||
if iterator.hasNext.not then builder.to_vector else
|
||||
builder.append iterator.next
|
||||
@Tail_Call loop iterator builder
|
||||
loop name_iterator builder
|
||||
JS_Object.Value object_node (make_field_names object_node)
|
||||
|
||||
## PRIVATE
|
||||
Creates a Jackon_Object from a list of key-value pairs.
|
||||
Keys must be `Text` values.
|
||||
Values will be recursively converted to JSON serializable as needed.
|
||||
from_pairs : Vector -> JS_Object
|
||||
from_pairs pairs =
|
||||
js_object = pairs.fold new_object current->pair->
|
||||
mapper = ObjectMapper.new
|
||||
new_object = mapper.createObjectNode
|
||||
keys = pairs.fold Vector.new_builder current->pair->
|
||||
case pair.first of
|
||||
text : Text ->
|
||||
js_value = pair.second.to_js_object
|
||||
set_value current text js_value
|
||||
jackson_value_node = to_json_node pair.second.to_js_object
|
||||
new_object.set text jackson_value_node
|
||||
current.append text
|
||||
_ -> Error.throw (Illegal_Argument.Error "JS_Object.from_pairs: key must be a Text value")
|
||||
JS_Object.Value js_object
|
||||
JS_Object.Value new_object keys.to_vector
|
||||
|
||||
## PRIVATE
|
||||
Value js_object
|
||||
Value object_node ~field_array
|
||||
|
||||
## GROUP Logical
|
||||
ICON preparation
|
||||
Returns True iff the objects contains the given `key`.
|
||||
contains_key : Text -> Boolean
|
||||
contains_key self key:Text = has_property self.js_object key
|
||||
contains_key self key:Text = self.object_node.has key
|
||||
|
||||
## ICON select_row
|
||||
Get a value for a key of the object, or a default value if that key is not present.
|
||||
@ -149,11 +188,11 @@ type JS_Object
|
||||
- key: The key to get.
|
||||
- if_missing: The value to return if the key is not found.
|
||||
@key make_field_name_selector
|
||||
get : Text -> Any -> JS_Object | Boolean | Number | Nothing | Text | Vector
|
||||
get : Text -> Any -> Nothing | Boolean | Number | Text | Vector | JS_Object
|
||||
get self key:Text ~if_missing=Nothing =
|
||||
if (has_property self.js_object key) . not then if_missing else
|
||||
value = get_value self.js_object key
|
||||
make_enso value
|
||||
if self.contains_key key . not then if_missing else
|
||||
child = self.object_node.get key
|
||||
make_enso (read_json_node child)
|
||||
|
||||
## GROUP Selections
|
||||
ICON select_row
|
||||
@ -170,8 +209,7 @@ type JS_Object
|
||||
ICON metadata
|
||||
Get the keys of the object.
|
||||
field_names : Vector
|
||||
field_names self =
|
||||
Vector.from_polyglot_array (get_property_names self.js_object)
|
||||
field_names self = self.field_array
|
||||
|
||||
## ICON dataframe_map_column
|
||||
Maps a function over each value in this object
|
||||
@ -199,8 +237,7 @@ type JS_Object
|
||||
ICON metadata
|
||||
Gets the number of keys in the object.
|
||||
length : Number
|
||||
length self =
|
||||
get_property_names self.js_object . length
|
||||
length self = self.object_node.size
|
||||
|
||||
## GROUP Logical
|
||||
ICON metadata
|
||||
@ -216,10 +253,10 @@ type JS_Object
|
||||
|
||||
## GROUP Conversions
|
||||
ICON convert
|
||||
Convert the object to a Vector of Key and Values.
|
||||
Convert the object to a Vector of Pairs.
|
||||
to_vector : Vector
|
||||
to_vector self =
|
||||
keys = get_property_names self.js_object
|
||||
keys = self.field_array
|
||||
proxy = Array_Proxy.new keys.length (i-> [(keys.at i), (self.get (keys.at i))])
|
||||
Vector.from_polyglot_array proxy
|
||||
|
||||
@ -231,7 +268,7 @@ type JS_Object
|
||||
## PRIVATE
|
||||
Convert to a Text.
|
||||
to_text : Text
|
||||
to_text self = Json.stringify self
|
||||
to_text self = self.to_json
|
||||
|
||||
## PRIVATE
|
||||
Convert JS_Object to a friendly string.
|
||||
@ -242,7 +279,7 @@ type JS_Object
|
||||
## PRIVATE
|
||||
Convert to a JSON representation.
|
||||
to_json : Text
|
||||
to_json self = self.to_text
|
||||
to_json self = self.object_node.toString
|
||||
|
||||
## PRIVATE
|
||||
Transform the vector into text for displaying as part of its default
|
||||
@ -257,9 +294,9 @@ type JS_Object
|
||||
into : Any -> Any
|
||||
into self target_type = case target_type of
|
||||
JS_Object -> self
|
||||
Vector -> self.to_vector
|
||||
Map -> Map.from_vector self.to_vector
|
||||
_ ->
|
||||
Vector -> self.to_vector
|
||||
Map -> Map.from_vector self.to_vector
|
||||
_ ->
|
||||
## First try a conversion
|
||||
Panic.catch No_Such_Conversion (self.to target_type) _->
|
||||
## If that fails, try to construct the type
|
||||
@ -277,7 +314,7 @@ type JS_Object
|
||||
Error.throw (Illegal_Argument.Error "Unable to build a "+target_type.to_text+" - the constructor "+constructor.name+"'s fields "+missing_fields.to_display_text+" not found in the source object.")
|
||||
_ -> Error.throw (Illegal_Argument.Error "Unable to build a "+target_type.to_text+" cannot find unique constructor.")
|
||||
|
||||
## Modify a field of a JSON object, returing the updated object.
|
||||
## Modify a field of a JSON object, returning a new updated object.
|
||||
|
||||
Arguments:
|
||||
- key: the name of the field to modify.
|
||||
@ -290,7 +327,17 @@ type JS_Object
|
||||
obj.set_value "foo" "asdf"
|
||||
# => {"foo":"asdf","baz":"quux"}
|
||||
set_value : Text -> Any -> JS_Object
|
||||
set_value self key:Text value = JS_Object.Value (set_value self.js_object key value.to_js_object)
|
||||
set_value self key:Text value =
|
||||
copied_object_node = self.object_node.deepCopy
|
||||
jackson_value_node = to_json_node value.to_js_object
|
||||
copied_object_node.set key jackson_value_node
|
||||
JS_Object.Value copied_object_node self.field_array
|
||||
|
||||
## PRIVATE
|
||||
Make a field name selector
|
||||
make_field_name_selector : JS_Object -> Display -> Widget
|
||||
make_field_name_selector js_object display=Display.Always =
|
||||
Single_Choice display=display values=(js_object.field_names.map n->(Option n n.pretty))
|
||||
|
||||
## PRIVATE
|
||||
type JS_Object_Comparator
|
||||
@ -336,91 +383,28 @@ render object depth=0 max_depth=5 max_length=100 = case object of
|
||||
_ : Text -> object.to_json
|
||||
_ : Boolean -> object.to_json
|
||||
_ : Number -> object.to_json
|
||||
_ -> @Tail_Call render (JS_Object.Value object) depth max_depth max_length
|
||||
_ -> @Tail_Call render (JS_Object.new object) depth max_depth max_length
|
||||
|
||||
## PRIVATE
|
||||
Internal function ensuring that a JavaScript object is presented appropriately to Enso.
|
||||
js_object must be a Native JS Object, Text, Boolean, Nothing, Number, Vector, Array, JS_Object.
|
||||
Children of these types will be recursively passed through this function.
|
||||
make_enso js_object =
|
||||
case js_object of
|
||||
Nothing -> Nothing
|
||||
_ : Text -> js_object
|
||||
_ : Boolean -> js_object
|
||||
_ : Number -> js_object
|
||||
_ : Vector ->
|
||||
proxy = Array_Proxy.new js_object.length (i-> make_enso (js_object.at i))
|
||||
Internal function ensuring that a Jackson object is presented appropriately to Enso.
|
||||
make_enso object =
|
||||
case object of
|
||||
Nothing -> Nothing
|
||||
_ : Text -> object
|
||||
_ : Boolean -> object
|
||||
_ : Number -> object
|
||||
v : Vector ->
|
||||
proxy = Array_Proxy.new v.length (i-> make_enso (v.at i))
|
||||
Vector.from_polyglot_array proxy
|
||||
_ : Array ->
|
||||
proxy = Array_Proxy.new js_object.length (i-> make_enso (js_object.at i))
|
||||
a : Array ->
|
||||
proxy = Array_Proxy.new a.length (i-> make_enso (a.at i))
|
||||
Vector.from_polyglot_array proxy
|
||||
_ : JS_Object -> js_object
|
||||
_ ->
|
||||
wrapped = JS_Object.Value js_object
|
||||
|
||||
js_object : JS_Object ->
|
||||
## Handle deserializing date and time types.
|
||||
type_name = wrapped.get "type"
|
||||
parsed = if type_name == "Date" then Date.from wrapped else
|
||||
if type_name == "Date_Time" then Date_Time.from wrapped else
|
||||
if type_name == "Time_Of_Day" then Time_Of_Day.from wrapped else
|
||||
wrapped
|
||||
type_name = js_object.get "type"
|
||||
parsed = if type_name == "Date" then Date.from js_object else
|
||||
if type_name == "Date_Time" then Date_Time.from js_object else
|
||||
if type_name == "Time_Of_Day" then Time_Of_Day.from js_object else
|
||||
js_object
|
||||
|
||||
if parsed.is_error then wrapped else parsed
|
||||
|
||||
|
||||
## PRIVATE
|
||||
Internal function to convert any JS_Objects into their native JS objects before passing to JS.
|
||||
enso_object must be Text, Boolean, Nothing, Number, Vector, Array, JS_Object (and children as well).
|
||||
make_javascript enso_object =
|
||||
## Have to remove warnings before passing into JavaScript otherwise will be an Object.
|
||||
if Warning.get_all enso_object != [] then make_javascript (Warning.clear enso_object) else
|
||||
case enso_object of
|
||||
_ : JS_Object ->
|
||||
enso_object.field_names.fold new_object current->key->
|
||||
value = enso_object.get key
|
||||
js_value = make_javascript value
|
||||
set_value current key js_value
|
||||
_ : Vector -> enso_object.map make_javascript
|
||||
_ : Array -> Vector.from_polyglot_array enso_object . map make_javascript
|
||||
_ -> enso_object
|
||||
|
||||
## PRIVATE
|
||||
Make a field name selector
|
||||
make_field_name_selector : JS_Object -> Display -> Widget
|
||||
make_field_name_selector js_object display=Display.Always =
|
||||
Single_Choice display=display values=(js_object.field_names.map n->(Option n n.pretty))
|
||||
|
||||
## PRIVATE
|
||||
Make a new JavaScript object.
|
||||
foreign js new_object = """
|
||||
return {}
|
||||
|
||||
## PRIVATE
|
||||
Parse a text value into JavaScript object.
|
||||
foreign js json_parse text = """
|
||||
return JSON.parse(text)
|
||||
|
||||
## PRIVATE
|
||||
Convert a JavaScript object to a text value.
|
||||
foreign js json_stringify js_object = """
|
||||
return JSON.stringify(js_object)
|
||||
|
||||
## PRIVATE
|
||||
Check a JavaScript object has a given property.
|
||||
foreign js has_property js_object key = """
|
||||
return js_object.hasOwnProperty(key)
|
||||
|
||||
## PRIVATE
|
||||
Get a value from a JavaScript object.
|
||||
foreign js get_value object key = """
|
||||
return object[key]
|
||||
|
||||
## PRIVATE
|
||||
Set a value on a JavaScript object and return the new object.
|
||||
foreign js set_value object key value = """
|
||||
return {...object, [key]: value}
|
||||
|
||||
## PRIVATE
|
||||
Gets all the property names of a JavaScript object.
|
||||
foreign js get_property_names object = """
|
||||
return Object.getOwnPropertyNames(object)
|
||||
if parsed.is_error then js_object else parsed
|
||||
|
@ -12,6 +12,7 @@ import project.Data.Text.Text
|
||||
import project.Data.Text.Text_Sub_Range.Text_Sub_Range
|
||||
import project.Data.Vector.Vector
|
||||
import project.Error.Error
|
||||
import project.Errors.Deprecated.Deprecated
|
||||
import project.Meta
|
||||
import project.Nothing.Nothing
|
||||
import project.Warning.Warning
|
||||
@ -36,9 +37,10 @@ Error.to_json self = self.to_js_object.to_text
|
||||
For arrays or vectors, the elements are converted recursively.
|
||||
For atoms and maps, the object is converted to a JS_Object.
|
||||
Text.to_js_object : JS_Object | Text
|
||||
Text.to_js_object self = case self of
|
||||
Text -> JS_Object.from_pairs [["type", "Text"]]
|
||||
_ -> self
|
||||
Text.to_js_object self =
|
||||
case self of
|
||||
Text -> JS_Object.from_pairs [["type", "Text"]]
|
||||
_ -> self
|
||||
|
||||
## PRIVATE
|
||||
Converts the given value to a JSON serializable object.
|
||||
@ -83,7 +85,7 @@ Nothing.to_js_object self = self
|
||||
Array.to_js_object : Vector
|
||||
Array.to_js_object self =
|
||||
stripped = Warning.clear self
|
||||
proxy = Array_Proxy.new stripped.length i-> stripped.at i . to_js_object
|
||||
proxy = Array_Proxy.new stripped.length i-> stripped.at i .to_js_object
|
||||
Vector.from_polyglot_array proxy
|
||||
|
||||
## PRIVATE
|
||||
@ -159,3 +161,10 @@ Map.to_js_object : JS_Object
|
||||
Map.to_js_object self =
|
||||
map_vector = self.to_vector
|
||||
map_vector.map p-> [p.first.to_js_object, p.second.to_js_object]
|
||||
|
||||
## ICON convert
|
||||
Extension for Text to allow use.
|
||||
Text.parse_fast_json : Nothing | Boolean | Number | Text | Vector | JS_Object
|
||||
Text.parse_fast_json self =
|
||||
warning = Deprecated.Warning "Standard.Base.Data.Text.Text" "parse_fast_json" "Deprecated: The `parse_json` method uses a faster implementation now by default"
|
||||
Warning.attach warning self.parse_json
|
||||
|
@ -198,4 +198,3 @@ type Time_Zone
|
||||
Time_Zone.from (that:JS_Object) =
|
||||
if that.get "type" == "Time_Zone" && ["id"].all that.contains_key then Time_Zone.parse (that.get "id") else
|
||||
Error.throw (Illegal_Argument.Error "Invalid JS_Object for Time_Zone.from.")
|
||||
|
||||
|
@ -1,5 +1,4 @@
|
||||
from Standard.Base import all
|
||||
import Standard.Base.Data.Java_Json.Jackson_Object
|
||||
import Standard.Base.Errors.Illegal_Argument.Illegal_Argument
|
||||
|
||||
import project.Column.Column
|
||||
@ -20,10 +19,6 @@ type Convertible_To_Columns
|
||||
Convertible_To_Columns.from (that:JS_Object) =
|
||||
Convertible_To_Columns.Value that.field_names that.get
|
||||
|
||||
## PRIVATE
|
||||
Convertible_To_Columns.from (that:Jackson_Object) =
|
||||
Convertible_To_Columns.Value that.field_names that.get
|
||||
|
||||
## PRIVATE
|
||||
Convertible_To_Columns.from (that:Map) =
|
||||
pairs = that.keys.map k-> [k.to_text, k]
|
||||
|
@ -1,5 +1,4 @@
|
||||
from Standard.Base import all
|
||||
import Standard.Base.Data.Java_Json.Jackson_Object
|
||||
|
||||
import project.Column.Column
|
||||
import project.Conversions.Convertible_To_Columns.Convertible_To_Columns
|
||||
@ -64,11 +63,6 @@ Convertible_To_Rows.from that:JS_Object =
|
||||
vals = that.map_with_key k->v-> Key_Value.Pair k v
|
||||
Convertible_To_Rows.Value vals.length vals.get ["Key", "Value"]
|
||||
|
||||
## PRIVATE
|
||||
Convertible_To_Rows.from that:Jackson_Object =
|
||||
vals = that.map_with_key k->v-> Key_Value.Pair k v
|
||||
Convertible_To_Rows.Value vals.length vals.get ["Key", "Value"]
|
||||
|
||||
## PRIVATE
|
||||
Convertible_To_Rows.from (that:Any) =
|
||||
Convertible_To_Rows.Value 1 (n-> if n==0 then that else Nothing)
|
||||
|
@ -61,12 +61,12 @@ add_specs suite_builder =
|
||||
JS_Object.from_pairs [["foo", "bar"], ["baz", ["foo", "x", False]], ["y", y_v]]
|
||||
|
||||
group_builder.specify "should report meaningful parsing errors" <|
|
||||
"foo".should_fail_parsing_with "Unexpected token f in JSON at position 0"
|
||||
"[,]".should_fail_parsing_with "Unexpected token , in JSON at position 1"
|
||||
"{,}".should_fail_parsing_with "Unexpected token , in JSON at position 1"
|
||||
"foo".should_fail_parsing_with "Unrecognized token 'foo': was expecting (JSON String, Number, Array, Object or token 'null', 'true' or 'false') at position [line: 1, column: 4]"
|
||||
"[,]".should_fail_parsing_with "Unexpected character (',' (code 44)): expected a valid value (JSON String, Number, Array, Object or token 'null', 'true' or 'false') at position [line: 1, column: 3]"
|
||||
"{,}".should_fail_parsing_with "Unexpected character (',' (code 44)): was expecting double-quote to start field name at position [line: 1, column: 3]"
|
||||
deep_err = '{ "foo": "bar", "baz": ["foo", "x"", false], "y": {"z": null, "w": null} }'
|
||||
deep_err.should_fail_parsing_with "closing quote ] expected at position 34"
|
||||
"123 4".should_fail_parsing_with "JSON cannot be fully parsed at position 4"
|
||||
deep_err.should_fail_parsing_with 'Unexpected character (\'\"\' (code 34)): was expecting comma to separate Array entries at position [line: 1, column: 36]'
|
||||
"123 4".should_parse_as 123 # Jackson parser assumes this is "correct"
|
||||
|
||||
group_builder.specify "should be able to deserialize using into via conversion" <|
|
||||
Json.parse '{"type":"Time_Zone","constructor":"parse","id":"Europe/Moscow"}' . into Time_Zone . should_equal (Time_Zone.parse "Europe/Moscow")
|
||||
@ -220,4 +220,3 @@ main filter=Nothing =
|
||||
suite = Test.build suite_builder->
|
||||
add_specs suite_builder
|
||||
suite.run_with_filter filter
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user