I can extract a value from a json string in an Option with this code:
import { identity, pipe } from "fp-ts/lib/function";
import * as E from "fp-ts/Either";
import * as O from "fp-ts/lib/Option";
import * as R from "fp-ts/lib/Record";
const jsonString = '{"a": 1}';
const a = pipe(
jsonString,
(x) =>
E.tryCatch(
() => JSON.parse(x),
(reason) => new Error(String(reason))
),
O.fromEither,
O.chain(R.lookup("a"))
);
console.log(a);
The type coming out of the E.tryCatch
call is Either<Error, any>
and R.lookup
seems to accept any
.
Instead of using JSON.parse
directly, I would like to useparse
from the Json
module in fp-ts
.
import { identity, pipe } from "fp-ts/lib/function";
import * as J from "fp-ts/lib/Json";
import * as O from "fp-ts/lib/Option";
import * as R from "fp-ts/lib/Record";
const jsonString = '{"a": 1}';
const a = pipe(
jsonString,
J.parse,
O.fromEither,
O.chain(R.lookup("a")) <-- Error
);
Trying to use =R.lookup= on =Json= gives the following error:
Argument of type '<A>(r: Record<string, A>) => Option<A>'
is not assignable to parameter of type '(a: Json) => Option<Json>'
I guess it is because Json
can have lots of values ( boolean | number | string | null | JsonArray | JsonRecord
) and only JsonRecord
would be compatible with Record
.
How can I convert Json
to Record
(or anything letting me "lookup" value for key)? Or perhaps narrow my Json
object to JsonRecord
?
Following the zipObject
example for fromFoldableMap
, I tried to do something like below without success:
import { identity, pipe } from "fp-ts/lib/function";
import * as J from "fp-ts/lib/Json";
import * as O from "fp-ts/lib/Option";
import * as R from "fp-ts/lib/Record";
const jsonString = '{"a": 1}';
const a = pipe(
jsonString,
J.parse,
O.fromEither,
O.map((kvs) => R.fromFoldableMap(last<J.Json>(), R.Foldable)(kvs, identity)), <-- Not working
O.chain(R.lookup("a"))
);
The reason there's an error is because the Json
type isn't necessarily assignable to a Record
. For example, the string '11'
would be valid input to JSON.parse
or J.parse
and would result in the number
11
. That number cannot be used like a Record
. It could likewise be an array, or a string, or null, etc. You'll need to further parse the returned value before you can use it with R.lookup
.
The most straightforward thing I can think of to solve that problem is to add io-ts
* into the mix.
Simplest I suspect is something like:
import { pipe } from "fp-ts/lib/function";
import * as J from "fp-ts/lib/Json";
import * as E from "fp-ts/lib/Either";
import * as O from "fp-ts/lib/Option";
import * as R from "fp-ts/lib/Record";
import * as t from "io-ts";
const jsonString = '{"a": 1}';
const a = pipe(
jsonString,
J.parse,
E.chainW(t.UnknownRecord.decode), // Different Error type so chainW
O.fromEither,
O.chain(R.lookup("a"))
);
If you're going to leverage io-ts
and you know the rough shape of the object you're parsing, then you can improve on this approach with something like:
import { pipe } from "fp-ts/lib/function";
import * as J from "fp-ts/lib/Json";
import * as E from "fp-ts/lib/Either";
import * as O from "fp-ts/lib/Option";
import * as t from "io-ts";
const jsonString = '{"a": 1}';
// Here you can define the exact shape of what you
// think J.parse will have returned.
const schema = t.type({
a: t.number
});
const a = pipe(
jsonString,
J.parse,
// Decode will then match that shape or return a `E.left` if it fails to.
E.chainW(schema.decode),
O.fromEither,
// The type of innerA is number and no need to do `lookup`
// because the entire shape of the object has been validated
O.map(({ a: innerA }) => innerA)
);
For absolute completeness just using fp-ts
I think you could get something working like:
import { pipe } from "fp-ts/lib/function";
import * as J from "fp-ts/lib/Json";
import * as R from "fp-ts/lib/Record";
import * as O from "fp-ts/lib/Option";
const jsonString = '{"a": 1}';
// Note that the return type here is going to be `Option<Json>` which
// may still be difficult to work with in your code for the same reason
// that you couldn't use `R.lookup` directly. Parsing with `io-ts` will
// give you easier types to work with.
const a = pipe(
jsonString,
J.parse,
O.fromEither,
O.chain((json) => {
// This is basically just inlining what `io-ts` would do for you so
// I again would recommend that library.
if (typeof json === "object" && json !== null && !(json instanceof Array)) {
return pipe(json, R.lookup("a"));
} else {
return O.none;
}
})
);
The last thing I'll mention is that, if you really want this function to work with as little changes as possible and you're ok with the O.Option<J.Json>
return type, and if you can assert that the string will always be a JSON object, and you really don't want to use io-ts
, you could achieve what you want with a type assertion like:
import { pipe } from "fp-ts/lib/function";
import * as J from "fp-ts/lib/Json";
import * as O from "fp-ts/lib/Option";
import * as R from "fp-ts/lib/Record";
const jsonString = '{"a": 1}';
const a = pipe(
jsonString,
J.parse,
O.fromEither,
// Again this is unsafe because the value might be an Array or a primitive
// value. You're basically just escaping from the type system.
O.map((json: J.Json): J.JsonRecord => json as J.JsonRecord),
O.chain(R.lookup("a"))
);
But I don't recommend this approach as I think it will be difficult to work with the return value and you open yourself up to errors if the input json string isn't an object.
* io-ts
is a library from the same people that make fp-ts
that is used for validating the shape of unknown
data in TypeScript.
You could just parse the string right when you get it and use the parsed object in your program .
const jsonString = '{"a": 1}';
var parsedJsonString = JSON.parse(jsonString);
console.log( "Parsed JSON: ", parsedJsonString );
console.log( "a:", parsedJsonString.a );
To be a little bit safer, you could check if it is really a json string like this:
function isJsonString(jsonString){
try {
JSON.parse(jsonString);
} catch (e) {
return false;
}
return true;
}
const jsonString = '{"a": 1}';
var isValidJson = isJsonString( jsonString );
if( isValidJson ){
var parsedJsonString = JSON.parse(jsonString);
console.log( "Parsed JSON: ", parsedJsonString );
console.log( "a:", parsedJsonString.a );
}else{
console.log("jsonString is not a valid json");
}
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.