Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

parse Expected JSON #61

Closed
halcwb opened this issue Dec 18, 2020 · 9 comments
Closed

parse Expected JSON #61

halcwb opened this issue Dec 18, 2020 · 9 comments

Comments

@halcwb
Copy link
Sponsor

halcwb commented Dec 18, 2020

When trying to parse this string:

json string

{
	"Generic": "abacavir",
	"Shapes": [{
		"Shape": "drank",
		"Routes": [{
			"Route": "or",
			"Indications": [{
				"Indication": "hiv",
				"Patient": {
					"Case": "Category",
					"Fields": [{
						"Case": "RootCategory"
					}, {
						"Case": "Categories",
						"Fields": [
							[{
								"Case": "Category",
								"Fields": [{
									"Case": "Age",
									"Fields": [{
										"Min": null,
										"Max": {
											"Case": "Some",
											"Fields": [{
												"Case": "MaxExcl",
												"Fields": [1.0]
											}]
										}
									}]
								}, {
									"Case": "Categories",
									"Fields": [
										[]
									]
								}]
							}, {
								"Case": "Category",
								"Fields": [{
									"Case": "Age",
									"Fields": [{
										"Min": {
											"Case": "Some",
											"Fields": [{
												"Case": "MinIncl",
												"Fields": [1.0]
											}]
										},
										"Max": {
											"Case": "Some",
											"Fields": [{
												"Case": "MaxExcl",
												"Fields": [240.0]
											}]
										}
									}]
								}, {
									"Case": "Dose",
									"Fields": [{
										"Case": "Some",
										"Fields": [{
											"Generic": "abacavir",
											"Shape": "drank",
											"Route": "or",
											"Indication": "hiv",
											"Specialty": "",
											"Gender": {
												"Case": "Unknown",
												"Fields": [""]
											},
											"MinAgeMo": {
												"Case": "Some",
												"Fields": [1.0]
											},
											"MaxAgeMo": {
												"Case": "Some",
												"Fields": [240.0]
											},
											"MinWeightKg": null,
											"MaxWeightKg": null,
											"MinGestAgeDays": null,
											"MaxGestAgeDays": null,
											"MinPMAgeDays": null,
											"MaxPMAgeDays": null,
											"Freqs": [{
												"Count": 1,
												"Time": {
													"Item1": 1,
													"Item2": "dag"
												}
											}, {
												"Count": 2,
												"Time": {
													"Item1": 1,
													"Item2": "dag"
												}
											}],
											"Unit": "mg",
											"NormDose": {
												"Case": "Some",
												"Fields": [{
													"Case": "QuantityPerKg",
													"Fields": [16.0]
												}]
											},
											"MinDose": null,
											"MaxDose": null,
											"AbsMaxDose": {
												"Case": "Some",
												"Fields": [{
													"Case": "Quantity",
													"Fields": [600.0]
												}]
											},
											"MaxPerDose": null,
											"StartDose": null,
											"Products": []
										}]
									}]
								}]
							}, {
								"Case": "Category",
								"Fields": [{
									"Case": "Age",
									"Fields": [{
										"Min": {
											"Case": "Some",
											"Fields": [{
												"Case": "MinIncl",
												"Fields": [240.0]
											}]
										},
										"Max": null
									}]
								}, {
									"Case": "Categories",
									"Fields": [
										[]
									]
								}]
							}]
						]
					}]
				}
			}]
		}]
	}, {
		"Shape": "tablet",
		"Routes": [{
			"Route": "or",
			"Indications": [{
				"Indication": "hiv",
				"Patient": {
					"Case": "Category",
					"Fields": [{
						"Case": "RootCategory"
					}, {
						"Case": "Categories",
						"Fields": [
							[{
								"Case": "Category",
								"Fields": [{
									"Case": "Age",
									"Fields": [{
										"Min": null,
										"Max": {
											"Case": "Some",
											"Fields": [{
												"Case": "MaxExcl",
												"Fields": [1.0]
											}]
										}
									}]
								}, {
									"Case": "Categories",
									"Fields": [
										[]
									]
								}]
							}, {
								"Case": "Category",
								"Fields": [{
									"Case": "Age",
									"Fields": [{
										"Min": {
											"Case": "Some",
											"Fields": [{
												"Case": "MinIncl",
												"Fields": [1.0]
											}]
										},
										"Max": {
											"Case": "Some",
											"Fields": [{
												"Case": "MaxExcl",
												"Fields": [240.0]
											}]
										}
									}]
								}, {
									"Case": "Dose",
									"Fields": [{
										"Case": "Some",
										"Fields": [{
											"Generic": "abacavir",
											"Shape": "tablet",
											"Route": "or",
											"Indication": "hiv",
											"Specialty": "",
											"Gender": {
												"Case": "Unknown",
												"Fields": [""]
											},
											"MinAgeMo": {
												"Case": "Some",
												"Fields": [1.0]
											},
											"MaxAgeMo": {
												"Case": "Some",
												"Fields": [240.0]
											},
											"MinWeightKg": null,
											"MaxWeightKg": null,
											"MinGestAgeDays": null,
											"MaxGestAgeDays": null,
											"MinPMAgeDays": null,
											"MaxPMAgeDays": null,
											"Freqs": [{
												"Count": 1,
												"Time": {
													"Item1": 1,
													"Item2": "dag"
												}
											}, {
												"Count": 2,
												"Time": {
													"Item1": 1,
													"Item2": "dag"
												}
											}],
											"Unit": "mg",
											"NormDose": {
												"Case": "Some",
												"Fields": [{
													"Case": "QuantityPerKg",
													"Fields": [16.0]
												}]
											},
											"MinDose": null,
											"MaxDose": null,
											"AbsMaxDose": {
												"Case": "Some",
												"Fields": [{
													"Case": "Quantity",
													"Fields": [600.0]
												}]
											},
											"MaxPerDose": null,
											"StartDose": null,
											"Products": []
										}]
									}]
								}]
							}, {
								"Case": "Category",
								"Fields": [{
									"Case": "Age",
									"Fields": [{
										"Min": {
											"Case": "Some",
											"Fields": [{
												"Case": "MinIncl",
												"Fields": [240.0]
											}]
										},
										"Max": null
									}]
								}, {
									"Case": "Categories",
									"Fields": [
										[]
									]
								}]
							}]
						]
					}]
				}
			}]
		}]
	}]
}

I get the following error:

error message

parse Expected JSON: [["Case",["JString","Category"]],["Fields",["JArray",[["JObject",{"comparer":{},"tree":["MapOne","Case",["JString","RootCategory"]]}],["JObject",{"comparer":{},"tree":["MapNode","Fields",["JArray",[["JArray",[["JObject",{"comparer":{},"tree":["MapNode","Fields",["JArray",[["JObject",{"comparer":{},"tree":["MapNode","Fields",["JArray",[["JObject",{"comparer":{},"tree":["MapNode","Max",["JObject",{"comparer":{},"tree":["MapNode","Fields",["JArray",[["JObject",{"comparer":{},"tree":["MapNode","Fields",["JArray",[["JNumber",1]]],["MapOne","Case",["JString","MaxExcl"]],"MapEmpty",2]}]]],["MapOne","Case",["JString","Some"]],"MapEmpty",2]}],"MapEmpty",["MapOne","Min","JNull"],2]}]]],["MapOne","Case",["JString","Age"]],"MapEmpty",2]}],["JObject",{"comparer":{},"tree":["MapNode","Fields",["JArray",[["JArray",[]]]],["MapOne","Case",["JString","Categories"]],"MapEmpty",2]}]]],["MapOne","Case",["JString","Category"]],"MapEmpty",2]}],["JObject",{"comparer":{},"tree":["MapNode","Fields",["JArray",[["JObject",{"comparer":{},"tree":["MapNode","Fields",["JArray",[["JObject",{"comparer":{},"tree":["MapNode","Max",["JObject",{"comparer":{},"tree":["MapNode","Fields",["JArray",[["JObject",{"comparer":{},"tree":["MapNode","Fields",["JArray",[["JNumber",240]]],["MapOne","Case",["JString","MaxExcl"]],"MapEmpty",2]}]]],["MapOne","Case",["JString","Some"]],"MapEmpty",2]}],"MapEmpty",["MapOne","Min",["JObject",{"comparer":{},"tree":["MapNode","Fields",["JArray",[["JObject",{"comparer":{},"tree":["MapNode","Fields",["JArray",[["JNumber",1]]],["MapOne","Case",["JString","MinIncl"]],"MapEmpty",2]}]]],["MapOne","Case",["JString","Some"]],"MapEmpty",2]}]],2]}]]],["MapOne","Case",["JString","Age"]],"MapEmpty",2]}],["JObject",{"comparer":{},"tree":["MapNode","Fields",["JArray",[["JObject",{"comparer":{},"tree":["MapNode","Fields",["JArray",[["JObject",{"comparer":{},"tree":["MapNode","MaxWeightKg","JNull",["MapNode","Indication",["JString","hiv"],["MapNode","Gender",["JObject",{"comparer":{},"tree":["MapNode","Fields",["JArray",[["JString",""]]],["MapOne","Case",["JString","Unknown"]],"MapEmpty",2]}],["MapNode","AbsMaxDose",["JObject",{"comparer":{},"tree":["MapNode","Fields",["JArray",[["JObject",{"comparer":{},"tree":["MapNode","Fields",["JArray",[["JNumber",600]]],["MapOne","Case",["JString","Quantity"]],"MapEmpty",2]}]]],["MapOne","Case",["JString","Some"]],"MapEmpty",2]}],"MapEmpty",["MapOne","Freqs",["JArray",[["JObject",{"comparer":{},"tree":["MapNode","Time",["JObject",{"comparer":{},"tree":["MapNode","Item2",["JString","dag"],["MapOne","Item1",["JNumber",1]],"MapEmpty",2]}],["MapOne","Count",["JNumber",1]],"MapEmpty",2]}],["JObject",{"comparer":{},"tree":["MapNode","Time",["JObject",{"comparer":{},"tree":["MapNode","Item2",["JString","dag"],["MapOne","Item1",["JNumber",1]],"MapEmpty",2]}],["MapOne","Count",["JNumber",2]],"MapEmpty",2]}]]]],2],["MapOne","Generic",["JString","abacavir"]],3],["MapNode","MaxGestAgeDays","JNull",["MapNode","MaxDose","JNull",["MapOne","MaxAgeMo",["JObject",{"comparer":{},"tree":["MapNode","Fields",["JArray",[["JNumber",240]]],["MapOne","Case",["JString","Some"]],"MapEmpty",2]}]],"MapEmpty",2],["MapNode","MaxPerDose","JNull",["MapOne","MaxPMAgeDays","JNull"],"MapEmpty",2],3],4],["MapNode","Route",["JString","or"],["MapNode","MinWeightKg","JNull",["MapNode","MinGestAgeDays","JNull",["MapNode","MinDose","JNull",["MapOne","MinAgeMo",["JObject",{"comparer":{},"tree":["MapNode","Fields",["JArray",[["JNumber",1]]],["MapOne","Case",["JString","Some"]],"MapEmpty",2]}]],"MapEmpty",2],["MapOne","MinPMAgeDays","JNull"],3],["MapNode","Products",["JArray",[]],["MapOne","NormDose",["JObject",{"comparer":{},"tree":["MapNode","Fields",["JArray",[["JObject",{"comparer":{},"tree":["MapNode","Fields",["JArray",[["JNumber",16]]],["MapOne","Case",["JString","QuantityPerKg"]],"MapEmpty",2]}]]],["MapOne","Case",["JString","Some"]],"MapEmpty",2]}]],"MapEmpty",2],4],["MapNode","Specialty",["JString",""],["MapOne","Shape",["JString","drank"]],["MapNode","StartDose","JNull","MapEmpty",["MapOne","Unit",["JString","mg"]],2],3],5],6]}]]],["MapOne","Case",["JString","Some"]],"MapEmpty",2]}]]],["MapOne","Case",["JString","Dose"]],"MapEmpty",2]}]]],["MapOne","Case",["JString","Category"]],"MapEmpty",2]}],["JObject",{"comparer":{},"tree":["MapNode","Fields",["JArray",[["JObject",{"comparer":{},"tree":["MapNode","Fields",["JArray",[["JObject",{"comparer":{},"tree":["MapNode","Max","JNull","MapEmpty",["MapOne","Min",["JObject",{"comparer":{},"tree":["MapNode","Fields",["JArray",[["JObject",{"comparer":{},"tree":["MapNode","Fields",["JArray",[["JNumber",240]]],["MapOne","Case",["JString","MinIncl"]],"MapEmpty",2]}]]],["MapOne","Case",["JString","Some"]],"MapEmpty",2]}]],2]}]]],["MapOne","Case",["JString","Age"]],"MapEmpty",2]}],["JObject",{"comparer":{},"tree":["MapNode","Fields",["JArray",[["JArray",[]]]],["MapOne","Case",["JString","Categories"]],"MapEmpty",2]}]]],["MapOne","Case",["JString","Category"]],"MapEmpty",2]}]]]]],["MapOne","Case",["JString","Categories"]],"MapEmpty",2]}]]]]] to match the type [{"CaseName":"Category","CaseTypes":[["Union",null],["Union",null]],"Info":{"declaringType":{"fullname":"Informedica.Formulary.Shared.Types.Category","generics":[]},"tag":0,"name":"Category","fields":[["Item1",{"fullname":"Informedica.Formulary.Shared.Types.PatientCategory","generics":[]}],["Item2",{"fullname":"Informedica.Formulary.Shared.Types.CategoriesOrDose","generics":[]}]]}}]

On the server side I use Newtonsoft to create a json string:

Json.JsonConvert.SerializeObject

On the client side I use SimpleJson to deserialize:

Json.tryParseNativeAs<CategorizedGeneric>

See this repo: https://github.com/halcwb/GenPedForm/blob/420b91c173fc5e9aec2ea9964abe0b2198830267/src/Informedica.Formulary.Client/App.fs#L129.

@Zaid-Ajaj
Copy link
Owner

Hi Casper, I was just looking at the code in the repository. SimpleJson does not parse JSON that is directly coming from Json.JsonConvert.SerializeObject because on the server, we use Fable.Remoting.Json internally in Fable.Remoting which extends the default newtonsoft converter with F# specific transformations on the resulting JSON.

Did you try to run getCategorizedAsString just to create the repro of the issue? Instead of serializing the data yourself. Just look the network tab while sending the original request that didn't work to inspect the request body send from client and and response content that is being sent back from the server and let paste the JSON here.

I see also that your solution is using an older version of Fable (v2.4.6) and latest is v2.13.0 in the 2.x family of versions. There is also Fable 3 but that should not have been the problem because Fable.Remoting and SimpleJson are backward compatible

@halcwb
Copy link
Sponsor Author

halcwb commented Dec 20, 2020

@Zaid-Ajaj As you suggested I first tried starting to update to the new fable compiler. However I run into the following issue:

Following the upgrade recipe, everything seems to work, only I get error messages from everything related to material ui theme functions like:

VM6191 log.js:24 [HMR] Waiting for update signal from WDS...
prelude.fs.js?bee5:10 Initial state: State {SelectedGeneric: undefined, SelectedIndication: undefined, SelectedRoute: undefined, SelectedPatient: undefined, Versions: Deferred$1, …}
getStylesCreator.js?0908:28 Uncaught TypeError: theme.Feliz.MaterialUI.Theme.spacingZ524259A4 is not a function
at eval (TitleBar.fs.js?c8cf:22)
at Object.create (getStylesCreator.js?0908:19)
at attach (makeStyles.js?443e:94)
at eval (makeStyles.js?443e:236)
at useSynchronousEffect (makeStyles.js?443e:188)
at useStyles (makeStyles.js?443e:228)
at titlebar (TitleBar.fs.js?c8cf:30)
at renderWithHooks (react-dom.development.js?61bb:14803)
at mountIndeterminateComponent (react-dom.development.js?61bb:17482)
at beginWork (react-dom.development.js?61bb:18596)
eval @ TitleBar.fs.js?c8cf:22
create @ getStylesCreator.js?0908:19
attach @ makeStyles.js?443e:94
eval @ makeStyles.js?443e:236
useSynchronousEffect @ makeStyles.js?443e:188
useStyles @ makeStyles.js?443e:228
titlebar @ TitleBar.fs.js?c8cf:30
renderWithHooks @ react-dom.development.js?61bb:14803
mountIndeterminateComponent @ react-dom.development.js?61bb:17482
beginWork @ react-dom.development.js?61bb:18596
callCallback @ react-dom.development.js?61bb:188
invokeGuardedCallbackDev @ react-dom.development.js?61bb:237
invokeGuardedCallback @ react-dom.development.js?61bb:292
beginWork$1 @ react-dom.development.js?61bb:23203
performUnitOfWork @ react-dom.development.js?61bb:22154
workLoopSync @ react-dom.development.js?61bb:22130
performSyncWorkOnRoot @ react-dom.development.js?61bb:21756
scheduleUpdateOnFiber @ react-dom.development.js?61bb:21188
updateContainer @ react-dom.development.js?61bb:24373
eval @ react-dom.development.js?61bb:24758
unbatchedUpdates @ react-dom.development.js?61bb:21903
legacyRenderSubtreeIntoContainer @ react-dom.development.js?61bb:24757
render @ react-dom.development.js?61bb:24840
eval @ react.fs.js?f36f:14
requestAnimationFrame (async)
setState @ react.fs.js?f36f:13
eval @ Util.js?e1e1:535
uncurriedFn @ Util.js?e1e1:500
mapSetState @ Main.fs.js?0c19:174
eval @ Util.js?e1e1:571
uncurriedFn @ Util.js?e1e1:500
ProgramModule_runWith @ program.fs.js?3f3e:152
eval @ Main.fs.js?0c19:205
eval @ Main.fs.js?0c19:22
./src/Informedica.Formulary.Client/Main.fs.js @ app.js:1921
webpack_require @ app.js:849
fn @ app.js:151
0 @ app.js:1982
webpack_require @ app.js:849
checkDeferredModules @ app.js:46
(anonymous) @ app.js:925
(anonymous) @ app.js:928
react-dom.development.js?61bb:19527 The above error occurred in the component:
in titlebar (created by Components_LazyView$1)
in div (created by Components_LazyView$1)
in ThemeProvider (created by Components_LazyView$1)
in Components_LazyView$1

@Zaid-Ajaj
Copy link
Owner

Is this Fable v2.13.0 or Fable 3? Because Fable 3 might be more complicated to migrate

@halcwb
Copy link
Sponsor Author

halcwb commented Dec 20, 2020

@Zaid-Ajaj it's the brand new Fable 3 Nagareyama, I think it is the same issue as: Shmew/Feliz.MaterialUI#59. It seems constants or arrays are translated to functions?: MangelMaxime/fulma-demo#43.

I will for now revert back to the js fable setup.

As for the original issue. To clarify:

  1. I have to use an external type from a library that is not compatible with browser compiled F# (because of data access).
  2. I run into the old problem: How to share code and or types from other projects or libs? Fable.Remoting#162.
  3. The quick and dirty solution is to manually map the external type to the shared type. But lot's of tedious coding and manual maintenance.
  4. The I tried a trick of serializing and deserializing from external type to shared type. Works great, but is a bit slow for a large amount of objects. Also, this is duplicate work as there is serialization from server to browser anyway.
  5. Then I thought, well if types are serialized anyway from server to browser, why not send the serialized object as a string to the browser and do the explicit deserialization in the browser. This saves an extra serialize/deserialize step of the 4. option.

That's the reasoning behind it. I have been looking at your code, but it seems that you use Newtonsoft, with only an extra feature to enable F# maps to be serialized?

@Zaid-Ajaj
Copy link
Owner

Then I thought, well if types are serialized anyway from server to browser, why not send the serialized object as a string to the browser and do the explicit deserialization in the browser. This saves an extra serialize/deserialize step of the 4. option.

It better to leave as much as possible serialization/deserialization work to Fable.Remoting instead of sending strings back and forth because there are lots of edge cases that have been specifically handled to Just Work across the stack.

with only an extra feature to enable F# maps to be serialized?

I wish it was that simple, the converter handles all the edge cases I mentioned above on a per-type basis. See the implementation here

@halcwb
Copy link
Sponsor Author

halcwb commented Dec 20, 2020

And it is not possible to use your converter on the server side explicitly and deserialize on the client side explicitly as well? Seemed like a simple solution to me. Only, I cannot discover how you perform the actual serialization and deserialization in Fable.Remoting.

@halcwb
Copy link
Sponsor Author

halcwb commented Dec 20, 2020

I think this is going to work:

# load ".paket/load/netstandard2.1/Server/Fable.Remoting.Json.fsx"


open Fable.Remoting.Json
open Newtonsoft.Json
open System.IO
open System.Collections.Concurrent
open System.Text


type MyType =
    | EndType of string
    | MyType of MyType

let testType = 
    "test"
    |> EndType
    |> MyType

let private fableConverter = new FableJsonConverter() :> JsonConverter

let private settings = JsonSerializerSettings(DateParseHandling = DateParseHandling.None)

let private fableSerializer =
    let serializer = JsonSerializer()
    serializer.Converters.Add fableConverter
    serializer

let private jsonEncoding = UTF8Encoding false

let jsonSerialize (o: 'a) (stream: Stream) =
    use sw = new StreamWriter (stream, jsonEncoding, 1024, true)
    use writer = new JsonTextWriter (sw, CloseOutput = false)
    fableSerializer.Serialize (writer, o)

let serialize o =
    use stream = new MemoryStream()
    use reader = new StreamReader(stream)
    jsonSerialize o stream
    stream.Position <- 0L
    reader.ReadToEnd()

testType
|> serialize

@halcwb
Copy link
Sponsor Author

halcwb commented Dec 20, 2020

It's working thanks.

@halcwb halcwb closed this as completed Dec 20, 2020
@Zaid-Ajaj
Copy link
Owner

Awesome! glad to hear you were able to figure it out 😄

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants